#305: draft otp secret request integration

This commit is contained in:
SleepWalker 2017-08-01 23:00:02 +03:00
parent a8ae0e0c05
commit ba8b725f9f
8 changed files with 83 additions and 33 deletions

8
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "ely-by-account",
"version": "1.1.19-dev",
"version": "1.1.21-dev",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -3714,9 +3714,9 @@
"dev": true
},
"flow-bin": {
"version": "0.47.0",
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.47.0.tgz",
"integrity": "sha1-oqCKs+DR8ctX0X4nswsRi2L9o2c=",
"version": "0.51.1",
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.51.1.tgz",
"integrity": "sha1-eSnG8KlOdlQp/LLubkaCePqpxzI=",
"dev": true
},
"fontgen-loader": {

View File

@ -72,7 +72,7 @@
"exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.0",
"file-loader": "^0.11.0",
"flow-bin": "^0.47.0",
"flow-bin": "^0.51.1",
"fontgen-loader": "^0.2.1",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.0.0",

View File

@ -7,7 +7,7 @@
"getAlternativeApps": "Get alternative apps",
"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",
"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.",

View File

@ -10,6 +10,7 @@ import styles from 'components/profile/profileForm.scss';
import helpLinks from 'components/auth/helpLinks.scss';
import Stepper from 'components/ui/stepper';
import { ScrollMotion } from 'components/ui/motion';
import mfa from 'services/api/mfa';
import Instructions from './instructions';
import KeyForm from './keyForm';
@ -39,11 +40,17 @@ export default class MultiFactorAuth extends Component {
};
state: {
isLoading: bool,
activeStep: number,
secret: string,
qrCodeSrc: string,
code: string,
newEmail: ?string
} = {
isLoading: false,
activeStep: this.props.step,
qrCodeSrc: '',
secret: '',
code: this.props.code || '',
newEmail: null
};
@ -56,7 +63,7 @@ export default class MultiFactorAuth extends Component {
}
render() {
const {activeStep} = this.state;
const {activeStep, isLoading} = this.state;
const form = this.props.stepForm;
const stepsData = [
@ -76,6 +83,7 @@ export default class MultiFactorAuth extends Component {
return (
<Form form={form}
onSubmit={this.onFormSubmit}
isLoading={isLoading}
onInvalid={() => this.forceUpdate()}
>
<div className={styles.contentWithBackButton}>
@ -128,11 +136,16 @@ export default class MultiFactorAuth extends Component {
}
renderStepForms() {
const {activeStep} = this.state;
const {activeStep, secret, qrCodeSrc} = this.state;
const steps = [
() => <Instructions key="step1" />,
() => <KeyForm key="step2" />,
() => (
<KeyForm key="step2"
secret={secret}
qrCodeSrc={qrCodeSrc}
/>
),
() => (
<Confirmation key="step3"
form={this.props.stepForm}
@ -183,7 +196,15 @@ export default class MultiFactorAuth extends Component {
};
onFormSubmit = () => {
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 form = this.props.stepForms[activeStep];
// const promise = this.props.onSubmit(activeStep, form);

View File

@ -10,11 +10,14 @@ import messages from '../MultiFactorAuth.intl.json';
import styles from './key-form.scss';
export default function KeyForm() {
const key = '123 123 52354 1234';
export default function KeyForm({secret, qrCodeSrc}: {
secret: string,
qrCodeSrc: string
}) {
const formattedSecret = formatSecret(secret);
return (
<div className={profileForm.formBody} key="step2">
<div className={profileForm.formBody}>
<div className={profileForm.formRow}>
<p className={profileForm.description}>
<Message {...messages.scanQrCode} />
@ -23,22 +26,22 @@ export default function KeyForm() {
<div className={profileForm.formRow}>
<div className={styles.qrCode}>
<img src="//placekitten.com/g/242/242" alt={key} />
<img src={qrCodeSrc} alt={secret} />
</div>
</div>
<div className={profileForm.formRow}>
<p className={classNames(styles.manualDescription, profileForm.description)}>
<div className={styles.or}>
<span className={styles.or}>
<Message {...messages.or} />
</div>
</span>
<Message {...messages.enterKeyManually} />
</p>
</div>
<div className={profileForm.formRow}>
<div className={styles.key}>
{key}
{formattedSecret}
</div>
</div>
@ -50,3 +53,7 @@ export default function KeyForm() {
</div>
);
}
function formatSecret(secret: string): string {
return (secret.match(/.{1,4}/g) || []).join(' ');
}

9
src/services/api/mfa.js Normal file
View 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');
}
};

View File

@ -1,5 +1,7 @@
import request from './request';
import InternalServerError from './InternalServerError';
// @flow
export { default } from './request';
export type { Resp } from './request';
export { default as InternalServerError } from './InternalServerError';
/**
* Usage: Query<'requeired'|'keys'|'names'>
@ -9,7 +11,3 @@ export type Query<T: string> = {
get: (key: T) => ?string,
set: (key: T, value: any) => void,
};
export default request;
export { InternalServerError };

View File

@ -1,17 +1,28 @@
// @flow
import PromiseMiddlewareLayer from './PromiseMiddlewareLayer';
import InternalServerError from './InternalServerError';
const middlewareLayer = new PromiseMiddlewareLayer();
export type Resp<T> = {
originalResponse: Response
} & T;
type Middleware = {
before?: () => Promise<*>,
after?: () => Promise<*>,
catch?: () => Promise<*>
};
export default {
/**
* @param {string} url
* @param {object} data - request data
* @param {object} options - additional options for fetch or middlewares
* @param {object} [data] - request data
* @param {object} [options] - additional options for fetch or middlewares
*
* @return {Promise}
*/
post(url, data, options = {}) {
post<T>(url: string, data?: Object, options: Object = {}): Promise<Resp<T>> {
return doFetch(url, {
method: 'POST',
headers: {
@ -24,12 +35,12 @@ export default {
/**
* @param {string} url
* @param {object} data - request data
* @param {object} options - additional options for fetch or middlewares
* @param {object} [data] - request data
* @param {object} [options] - additional options for fetch or middlewares
*
* @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) {
const separator = url.indexOf('?') === -1 ? '?' : '&';
url += separator + buildQuery(data);
@ -59,13 +70,15 @@ export default {
* get response and callback to restart request as an arguments and should
* return a Promise that resolves to the new response.
*/
addMiddleware(middleware) {
addMiddleware(middleware: 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 = {}) => {
if (!resp.json) {
// e.g. 'TypeError: Failed to fetch' due to CORS
@ -87,7 +100,9 @@ const rejectWithJSON = (resp) => toJSON(resp).then((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 = {}) {
// NOTE: we are wrapping fetch, because it is returning
@ -136,7 +151,7 @@ function convertQueryValue(value) {
*
* @return {string}
*/
function buildQuery(data = {}) {
function buildQuery(data: Object = {}): string {
return Object.keys(data)
.map(
(keyName) =>