mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-10-02 16:27:15 +05:30
#305: draft otp secret request integration
This commit is contained in:
parent
a8ae0e0c05
commit
ba8b725f9f
8
package-lock.json
generated
8
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ely-by-account",
|
"name": "ely-by-account",
|
||||||
"version": "1.1.19-dev",
|
"version": "1.1.21-dev",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -3714,9 +3714,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"flow-bin": {
|
"flow-bin": {
|
||||||
"version": "0.47.0",
|
"version": "0.51.1",
|
||||||
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.47.0.tgz",
|
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.51.1.tgz",
|
||||||
"integrity": "sha1-oqCKs+DR8ctX0X4nswsRi2L9o2c=",
|
"integrity": "sha1-eSnG8KlOdlQp/LLubkaCePqpxzI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"fontgen-loader": {
|
"fontgen-loader": {
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
"exports-loader": "^0.6.3",
|
"exports-loader": "^0.6.3",
|
||||||
"extract-text-webpack-plugin": "^1.0.0",
|
"extract-text-webpack-plugin": "^1.0.0",
|
||||||
"file-loader": "^0.11.0",
|
"file-loader": "^0.11.0",
|
||||||
"flow-bin": "^0.47.0",
|
"flow-bin": "^0.51.1",
|
||||||
"fontgen-loader": "^0.2.1",
|
"fontgen-loader": "^0.2.1",
|
||||||
"html-loader": "^0.4.3",
|
"html-loader": "^0.4.3",
|
||||||
"html-webpack-plugin": "^2.0.0",
|
"html-webpack-plugin": "^2.0.0",
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"getAlternativeApps": "Get alternative apps",
|
"getAlternativeApps": "Get alternative apps",
|
||||||
"theAppIsInstalled": "The app is installed",
|
"theAppIsInstalled": "The app is installed",
|
||||||
|
|
||||||
"scanQrCode": "Open your favorit QR scanner app and scan the following QR code:",
|
"scanQrCode": "Open your favorite QR scanner app and scan the following QR code:",
|
||||||
"or": "OR",
|
"or": "OR",
|
||||||
"enterKeyManually": "If you can't scan QR code, then enter the secret key manually:",
|
"enterKeyManually": "If you can't scan QR code, then enter the secret key manually:",
|
||||||
"whenKeyEntered": "Go to the next step, after you will see temporary code in your two-factor auth app.",
|
"whenKeyEntered": "Go to the next step, after you will see temporary code in your two-factor auth app.",
|
||||||
|
@ -10,6 +10,7 @@ import styles from 'components/profile/profileForm.scss';
|
|||||||
import helpLinks from 'components/auth/helpLinks.scss';
|
import helpLinks from 'components/auth/helpLinks.scss';
|
||||||
import Stepper from 'components/ui/stepper';
|
import Stepper from 'components/ui/stepper';
|
||||||
import { ScrollMotion } from 'components/ui/motion';
|
import { ScrollMotion } from 'components/ui/motion';
|
||||||
|
import mfa from 'services/api/mfa';
|
||||||
|
|
||||||
import Instructions from './instructions';
|
import Instructions from './instructions';
|
||||||
import KeyForm from './keyForm';
|
import KeyForm from './keyForm';
|
||||||
@ -39,11 +40,17 @@ export default class MultiFactorAuth extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
|
isLoading: bool,
|
||||||
activeStep: number,
|
activeStep: number,
|
||||||
|
secret: string,
|
||||||
|
qrCodeSrc: string,
|
||||||
code: string,
|
code: string,
|
||||||
newEmail: ?string
|
newEmail: ?string
|
||||||
} = {
|
} = {
|
||||||
|
isLoading: false,
|
||||||
activeStep: this.props.step,
|
activeStep: this.props.step,
|
||||||
|
qrCodeSrc: '',
|
||||||
|
secret: '',
|
||||||
code: this.props.code || '',
|
code: this.props.code || '',
|
||||||
newEmail: null
|
newEmail: null
|
||||||
};
|
};
|
||||||
@ -56,7 +63,7 @@ export default class MultiFactorAuth extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {activeStep} = this.state;
|
const {activeStep, isLoading} = this.state;
|
||||||
const form = this.props.stepForm;
|
const form = this.props.stepForm;
|
||||||
|
|
||||||
const stepsData = [
|
const stepsData = [
|
||||||
@ -76,6 +83,7 @@ export default class MultiFactorAuth extends Component {
|
|||||||
return (
|
return (
|
||||||
<Form form={form}
|
<Form form={form}
|
||||||
onSubmit={this.onFormSubmit}
|
onSubmit={this.onFormSubmit}
|
||||||
|
isLoading={isLoading}
|
||||||
onInvalid={() => this.forceUpdate()}
|
onInvalid={() => this.forceUpdate()}
|
||||||
>
|
>
|
||||||
<div className={styles.contentWithBackButton}>
|
<div className={styles.contentWithBackButton}>
|
||||||
@ -128,11 +136,16 @@ export default class MultiFactorAuth extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStepForms() {
|
renderStepForms() {
|
||||||
const {activeStep} = this.state;
|
const {activeStep, secret, qrCodeSrc} = this.state;
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
() => <Instructions key="step1" />,
|
() => <Instructions key="step1" />,
|
||||||
() => <KeyForm key="step2" />,
|
() => (
|
||||||
|
<KeyForm key="step2"
|
||||||
|
secret={secret}
|
||||||
|
qrCodeSrc={qrCodeSrc}
|
||||||
|
/>
|
||||||
|
),
|
||||||
() => (
|
() => (
|
||||||
<Confirmation key="step3"
|
<Confirmation key="step3"
|
||||||
form={this.props.stepForm}
|
form={this.props.stepForm}
|
||||||
@ -183,7 +196,15 @@ export default class MultiFactorAuth extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onFormSubmit = () => {
|
onFormSubmit = () => {
|
||||||
this.nextStep();
|
this.setState({isLoading: true});
|
||||||
|
mfa.getSecret().then((resp) => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
secret: resp.secret,
|
||||||
|
qrCodeSrc: `data:image/svg+xml;base64,${resp.qr}`
|
||||||
|
});
|
||||||
|
this.nextStep();
|
||||||
|
});
|
||||||
// const {activeStep} = this.state;
|
// const {activeStep} = this.state;
|
||||||
// const form = this.props.stepForms[activeStep];
|
// const form = this.props.stepForms[activeStep];
|
||||||
// const promise = this.props.onSubmit(activeStep, form);
|
// const promise = this.props.onSubmit(activeStep, form);
|
||||||
|
@ -10,11 +10,14 @@ import messages from '../MultiFactorAuth.intl.json';
|
|||||||
|
|
||||||
import styles from './key-form.scss';
|
import styles from './key-form.scss';
|
||||||
|
|
||||||
export default function KeyForm() {
|
export default function KeyForm({secret, qrCodeSrc}: {
|
||||||
const key = '123 123 52354 1234';
|
secret: string,
|
||||||
|
qrCodeSrc: string
|
||||||
|
}) {
|
||||||
|
const formattedSecret = formatSecret(secret);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={profileForm.formBody} key="step2">
|
<div className={profileForm.formBody}>
|
||||||
<div className={profileForm.formRow}>
|
<div className={profileForm.formRow}>
|
||||||
<p className={profileForm.description}>
|
<p className={profileForm.description}>
|
||||||
<Message {...messages.scanQrCode} />
|
<Message {...messages.scanQrCode} />
|
||||||
@ -23,22 +26,22 @@ export default function KeyForm() {
|
|||||||
|
|
||||||
<div className={profileForm.formRow}>
|
<div className={profileForm.formRow}>
|
||||||
<div className={styles.qrCode}>
|
<div className={styles.qrCode}>
|
||||||
<img src="//placekitten.com/g/242/242" alt={key} />
|
<img src={qrCodeSrc} alt={secret} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={profileForm.formRow}>
|
<div className={profileForm.formRow}>
|
||||||
<p className={classNames(styles.manualDescription, profileForm.description)}>
|
<p className={classNames(styles.manualDescription, profileForm.description)}>
|
||||||
<div className={styles.or}>
|
<span className={styles.or}>
|
||||||
<Message {...messages.or} />
|
<Message {...messages.or} />
|
||||||
</div>
|
</span>
|
||||||
<Message {...messages.enterKeyManually} />
|
<Message {...messages.enterKeyManually} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={profileForm.formRow}>
|
<div className={profileForm.formRow}>
|
||||||
<div className={styles.key}>
|
<div className={styles.key}>
|
||||||
{key}
|
{formattedSecret}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -50,3 +53,7 @@ export default function KeyForm() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatSecret(secret: string): string {
|
||||||
|
return (secret.match(/.{1,4}/g) || []).join(' ');
|
||||||
|
}
|
||||||
|
9
src/services/api/mfa.js
Normal file
9
src/services/api/mfa.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// @flow
|
||||||
|
import request from 'services/request';
|
||||||
|
import type { Resp } from 'services/request';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getSecret(): Promise<Resp<{qr: string, secret: string, uri: string}>> {
|
||||||
|
return request.get('/api/two-factor-auth');
|
||||||
|
}
|
||||||
|
};
|
@ -1,5 +1,7 @@
|
|||||||
import request from './request';
|
// @flow
|
||||||
import InternalServerError from './InternalServerError';
|
export { default } from './request';
|
||||||
|
export type { Resp } from './request';
|
||||||
|
export { default as InternalServerError } from './InternalServerError';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Usage: Query<'requeired'|'keys'|'names'>
|
* Usage: Query<'requeired'|'keys'|'names'>
|
||||||
@ -9,7 +11,3 @@ export type Query<T: string> = {
|
|||||||
get: (key: T) => ?string,
|
get: (key: T) => ?string,
|
||||||
set: (key: T, value: any) => void,
|
set: (key: T, value: any) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default request;
|
|
||||||
|
|
||||||
export { InternalServerError };
|
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
|
// @flow
|
||||||
import PromiseMiddlewareLayer from './PromiseMiddlewareLayer';
|
import PromiseMiddlewareLayer from './PromiseMiddlewareLayer';
|
||||||
import InternalServerError from './InternalServerError';
|
import InternalServerError from './InternalServerError';
|
||||||
|
|
||||||
const middlewareLayer = new PromiseMiddlewareLayer();
|
const middlewareLayer = new PromiseMiddlewareLayer();
|
||||||
|
|
||||||
|
export type Resp<T> = {
|
||||||
|
originalResponse: Response
|
||||||
|
} & T;
|
||||||
|
|
||||||
|
type Middleware = {
|
||||||
|
before?: () => Promise<*>,
|
||||||
|
after?: () => Promise<*>,
|
||||||
|
catch?: () => Promise<*>
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @param {object} data - request data
|
* @param {object} [data] - request data
|
||||||
* @param {object} options - additional options for fetch or middlewares
|
* @param {object} [options] - additional options for fetch or middlewares
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
post(url, data, options = {}) {
|
post<T>(url: string, data?: Object, options: Object = {}): Promise<Resp<T>> {
|
||||||
return doFetch(url, {
|
return doFetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -24,12 +35,12 @@ export default {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @param {object} data - request data
|
* @param {object} [data] - request data
|
||||||
* @param {object} options - additional options for fetch or middlewares
|
* @param {object} [options] - additional options for fetch or middlewares
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
get(url, data, options = {}) {
|
get<T>(url: string, data?: Object, options: Object = {}): Promise<Resp<T>> {
|
||||||
if (typeof data === 'object' && Object.keys(data).length) {
|
if (typeof data === 'object' && Object.keys(data).length) {
|
||||||
const separator = url.indexOf('?') === -1 ? '?' : '&';
|
const separator = url.indexOf('?') === -1 ? '?' : '&';
|
||||||
url += separator + buildQuery(data);
|
url += separator + buildQuery(data);
|
||||||
@ -59,13 +70,15 @@ export default {
|
|||||||
* get response and callback to restart request as an arguments and should
|
* get response and callback to restart request as an arguments and should
|
||||||
* return a Promise that resolves to the new response.
|
* return a Promise that resolves to the new response.
|
||||||
*/
|
*/
|
||||||
addMiddleware(middleware) {
|
addMiddleware(middleware: Middleware) {
|
||||||
middlewareLayer.add(middleware);
|
middlewareLayer.add(middleware);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const checkStatus = (resp) => Promise[resp.status >= 200 && resp.status < 300 ? 'resolve' : 'reject'](resp);
|
const checkStatus = (resp) => resp.status >= 200 && resp.status < 300
|
||||||
|
? Promise.resolve(resp)
|
||||||
|
: Promise.reject(resp);
|
||||||
const toJSON = (resp = {}) => {
|
const toJSON = (resp = {}) => {
|
||||||
if (!resp.json) {
|
if (!resp.json) {
|
||||||
// e.g. 'TypeError: Failed to fetch' due to CORS
|
// e.g. 'TypeError: Failed to fetch' due to CORS
|
||||||
@ -87,7 +100,9 @@ const rejectWithJSON = (resp) => toJSON(resp).then((resp) => {
|
|||||||
|
|
||||||
throw resp;
|
throw resp;
|
||||||
});
|
});
|
||||||
const handleResponseSuccess = (resp) => Promise[resp.success || typeof resp.success === 'undefined' ? 'resolve' : 'reject'](resp);
|
const handleResponseSuccess = (resp) => resp.success || typeof resp.success === 'undefined'
|
||||||
|
? Promise.resolve(resp)
|
||||||
|
: Promise.reject(resp);
|
||||||
|
|
||||||
function doFetch(url, options = {}) {
|
function doFetch(url, options = {}) {
|
||||||
// NOTE: we are wrapping fetch, because it is returning
|
// NOTE: we are wrapping fetch, because it is returning
|
||||||
@ -136,7 +151,7 @@ function convertQueryValue(value) {
|
|||||||
*
|
*
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
function buildQuery(data = {}) {
|
function buildQuery(data: Object = {}): string {
|
||||||
return Object.keys(data)
|
return Object.keys(data)
|
||||||
.map(
|
.map(
|
||||||
(keyName) =>
|
(keyName) =>
|
||||||
|
Loading…
Reference in New Issue
Block a user