mirror of
				https://github.com/elyby/accounts-frontend.git
				synced 2025-05-31 14:11:58 +05:30 
			
		
		
		
	Initial device code flow implementation
This commit is contained in:
		@@ -16,14 +16,18 @@ import {
 | 
			
		||||
    login,
 | 
			
		||||
    setLogin,
 | 
			
		||||
} from 'app/components/auth/actions';
 | 
			
		||||
import { OauthData, OAuthValidateResponse } from '../../services/api/oauth';
 | 
			
		||||
import { OAuthValidateResponse } from 'app/services/api/oauth';
 | 
			
		||||
 | 
			
		||||
const oauthData: OauthData = {
 | 
			
		||||
    clientId: '',
 | 
			
		||||
    redirectUrl: '',
 | 
			
		||||
    responseType: '',
 | 
			
		||||
    scope: '',
 | 
			
		||||
    state: '',
 | 
			
		||||
import { OAuthState } from './reducer';
 | 
			
		||||
 | 
			
		||||
const oauthData: OAuthState = {
 | 
			
		||||
    params: {
 | 
			
		||||
        clientId: '',
 | 
			
		||||
        redirectUrl: '',
 | 
			
		||||
        responseType: '',
 | 
			
		||||
        scope: '',
 | 
			
		||||
        state: '',
 | 
			
		||||
    },
 | 
			
		||||
    prompt: 'none',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -64,9 +68,6 @@ describe('components/auth/actions', () => {
 | 
			
		||||
                    name: '',
 | 
			
		||||
                    description: '',
 | 
			
		||||
                },
 | 
			
		||||
                oAuth: {
 | 
			
		||||
                    state: 123,
 | 
			
		||||
                },
 | 
			
		||||
                session: {
 | 
			
		||||
                    scopes: ['account_info'],
 | 
			
		||||
                },
 | 
			
		||||
@@ -86,7 +87,6 @@ describe('components/auth/actions', () => {
 | 
			
		||||
                    [setClient(resp.client)],
 | 
			
		||||
                    [
 | 
			
		||||
                        setOAuthRequest({
 | 
			
		||||
                            ...resp.oAuth,
 | 
			
		||||
                            prompt: 'none',
 | 
			
		||||
                            loginHint: undefined,
 | 
			
		||||
                        }),
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ import {
 | 
			
		||||
    recoverPassword as recoverPasswordEndpoint,
 | 
			
		||||
    OAuthResponse,
 | 
			
		||||
} from 'app/services/api/authentication';
 | 
			
		||||
import oauth, { OauthData, Scope } from 'app/services/api/oauth';
 | 
			
		||||
import oauth, { OauthRequestData, Scope } from 'app/services/api/oauth';
 | 
			
		||||
import {
 | 
			
		||||
    register as registerEndpoint,
 | 
			
		||||
    activate as activateEndpoint,
 | 
			
		||||
@@ -314,38 +314,17 @@ const KNOWN_SCOPES: ReadonlyArray<string> = [
 | 
			
		||||
    'account_info',
 | 
			
		||||
    'account_email',
 | 
			
		||||
];
 | 
			
		||||
/**
 | 
			
		||||
 * @param {object} oauthData
 | 
			
		||||
 * @param {string} oauthData.clientId
 | 
			
		||||
 * @param {string} oauthData.redirectUrl
 | 
			
		||||
 * @param {string} oauthData.responseType
 | 
			
		||||
 * @param {string} oauthData.description
 | 
			
		||||
 * @param {string} oauthData.scope
 | 
			
		||||
 * @param {string} [oauthData.prompt='none'] - comma-separated list of values to adjust auth flow
 | 
			
		||||
 *                 Posible values:
 | 
			
		||||
 *                  * none - default behaviour
 | 
			
		||||
 *                  * consent - forcibly prompt user for rules acceptance
 | 
			
		||||
 *                  * select_account - force account choosage, even if user has only one
 | 
			
		||||
 * @param {string} oauthData.loginHint - allows to choose the account, which will be used for auth
 | 
			
		||||
 *                        The possible values: account id, email, username
 | 
			
		||||
 * @param {string} oauthData.state
 | 
			
		||||
 *
 | 
			
		||||
 * @returns {Promise}
 | 
			
		||||
 */
 | 
			
		||||
export function oAuthValidate(oauthData: OauthData) {
 | 
			
		||||
 | 
			
		||||
export function oAuthValidate(oauthData: Pick<OAuthState, 'params' | 'description' | 'prompt' | 'loginHint'>) {
 | 
			
		||||
    // TODO: move to oAuth actions?
 | 
			
		||||
    // test request: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session&description=foo
 | 
			
		||||
    // auth code flow: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session&description=foo
 | 
			
		||||
    // device code flow: /code?user_code=XOXOXOXO
 | 
			
		||||
    return wrapInLoader((dispatch) =>
 | 
			
		||||
        oauth
 | 
			
		||||
            .validate(oauthData)
 | 
			
		||||
            .validate(getOAuthRequest(oauthData))
 | 
			
		||||
            .then((resp) => {
 | 
			
		||||
                const { scopes } = resp.session;
 | 
			
		||||
                const invalidScopes = scopes.filter((scope) => !KNOWN_SCOPES.includes(scope));
 | 
			
		||||
                let prompt = (oauthData.prompt || 'none').split(',').map((item) => item.trim());
 | 
			
		||||
 | 
			
		||||
                if (prompt.includes('none')) {
 | 
			
		||||
                    prompt = ['none'];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (invalidScopes.length) {
 | 
			
		||||
                    logger.error('Got invalid scopes after oauth validation', {
 | 
			
		||||
@@ -353,12 +332,19 @@ export function oAuthValidate(oauthData: OauthData) {
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let { prompt } = oauthData;
 | 
			
		||||
 | 
			
		||||
                if (prompt && !Array.isArray(prompt)) {
 | 
			
		||||
                    prompt = prompt.split(',').map((item) => item.trim());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                dispatch(setClient(resp.client));
 | 
			
		||||
                dispatch(
 | 
			
		||||
                    setOAuthRequest({
 | 
			
		||||
                        ...resp.oAuth,
 | 
			
		||||
                        prompt: oauthData.prompt || 'none',
 | 
			
		||||
                        params: oauthData.params,
 | 
			
		||||
                        description: oauthData.description,
 | 
			
		||||
                        loginHint: oauthData.loginHint,
 | 
			
		||||
                        prompt,
 | 
			
		||||
                    }),
 | 
			
		||||
                );
 | 
			
		||||
                dispatch(setScopes(scopes));
 | 
			
		||||
@@ -375,12 +361,6 @@ export function oAuthValidate(oauthData: OauthData) {
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {object} params
 | 
			
		||||
 * @param {bool} params.accept=false
 | 
			
		||||
 * @param params.accept
 | 
			
		||||
 * @returns {Promise}
 | 
			
		||||
 */
 | 
			
		||||
export function oAuthComplete(params: { accept?: boolean } = {}) {
 | 
			
		||||
    return wrapInLoader(
 | 
			
		||||
        async (
 | 
			
		||||
@@ -388,7 +368,7 @@ export function oAuthComplete(params: { accept?: boolean } = {}) {
 | 
			
		||||
            getState,
 | 
			
		||||
        ): Promise<{
 | 
			
		||||
            success: boolean;
 | 
			
		||||
            redirectUri: string;
 | 
			
		||||
            redirectUri?: string;
 | 
			
		||||
        }> => {
 | 
			
		||||
            const oauthData = getState().auth.oauth;
 | 
			
		||||
 | 
			
		||||
@@ -397,11 +377,14 @@ export function oAuthComplete(params: { accept?: boolean } = {}) {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                const resp = await oauth.complete(oauthData, params);
 | 
			
		||||
                const resp = await oauth.complete(getOAuthRequest(oauthData), params);
 | 
			
		||||
 | 
			
		||||
                localStorage.removeItem('oauthData');
 | 
			
		||||
 | 
			
		||||
                if (resp.redirectUri.startsWith('static_page')) {
 | 
			
		||||
                    const displayCode = /static_page_with_code/.test(resp.redirectUri);
 | 
			
		||||
                if (!resp.redirectUri) {
 | 
			
		||||
                    dispatch(setOAuthCode({ success: resp.success && params.accept }));
 | 
			
		||||
                } else if (resp.redirectUri.startsWith('static_page')) {
 | 
			
		||||
                    const displayCode = resp.redirectUri.includes('static_page_with_code');
 | 
			
		||||
 | 
			
		||||
                    const [, code] = resp.redirectUri.match(/code=(.+)&/) || [];
 | 
			
		||||
                    [, resp.redirectUri] = resp.redirectUri.match(/^(.+)\?/) || [];
 | 
			
		||||
@@ -437,13 +420,41 @@ export function oAuthComplete(params: { accept?: boolean } = {}) {
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getOAuthRequest({ params, description }: OAuthState): OauthRequestData {
 | 
			
		||||
    let data: OauthRequestData;
 | 
			
		||||
 | 
			
		||||
    if ('userCode' in params) {
 | 
			
		||||
        data = {
 | 
			
		||||
            user_code: params.userCode,
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        data = {
 | 
			
		||||
            client_id: params.clientId,
 | 
			
		||||
            redirect_uri: params.redirectUrl,
 | 
			
		||||
            response_type: params.responseType,
 | 
			
		||||
            scope: params.scope,
 | 
			
		||||
            state: params.state,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (description) {
 | 
			
		||||
        data.description = description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleOauthParamsValidation(
 | 
			
		||||
    resp: {
 | 
			
		||||
        [key: string]: any;
 | 
			
		||||
        userMessage?: string;
 | 
			
		||||
    } = {},
 | 
			
		||||
) {
 | 
			
		||||
    dispatchBsod();
 | 
			
		||||
    // TODO: it would be better to dispatch BSOD from the initial request performers
 | 
			
		||||
    if (resp.error !== 'invalid_user_code') {
 | 
			
		||||
        dispatchBsod();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    localStorage.removeItem('oauthData');
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line no-alert
 | 
			
		||||
@@ -468,33 +479,16 @@ export type ClientAction = SetClientAction;
 | 
			
		||||
 | 
			
		||||
interface SetOauthAction extends ReduxAction {
 | 
			
		||||
    type: 'set_oauth';
 | 
			
		||||
    payload: Pick<OAuthState, 'clientId' | 'redirectUrl' | 'responseType' | 'scope' | 'prompt' | 'loginHint' | 'state'>;
 | 
			
		||||
    payload: OAuthState | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Input data is coming right from the query string, so the names
 | 
			
		||||
// are the same, as used for initializing OAuth2 request
 | 
			
		||||
export function setOAuthRequest(data: {
 | 
			
		||||
    client_id?: string;
 | 
			
		||||
    redirect_uri?: string;
 | 
			
		||||
    response_type?: string;
 | 
			
		||||
    scope?: string;
 | 
			
		||||
    prompt?: string;
 | 
			
		||||
    loginHint?: string;
 | 
			
		||||
    state?: string;
 | 
			
		||||
}): SetOauthAction {
 | 
			
		||||
// TODO: filter out allowed properties
 | 
			
		||||
export function setOAuthRequest(payload: OAuthState | null): SetOauthAction {
 | 
			
		||||
    return {
 | 
			
		||||
        type: 'set_oauth',
 | 
			
		||||
        payload: {
 | 
			
		||||
            // TODO: there is too much default empty string. Maybe we can somehow validate it
 | 
			
		||||
            //       on the level, where this action is called?
 | 
			
		||||
            clientId: data.client_id || '',
 | 
			
		||||
            redirectUrl: data.redirect_uri || '',
 | 
			
		||||
            responseType: data.response_type || '',
 | 
			
		||||
            scope: data.scope || '',
 | 
			
		||||
            prompt: data.prompt || '',
 | 
			
		||||
            loginHint: data.loginHint || '',
 | 
			
		||||
            state: data.state || '',
 | 
			
		||||
        },
 | 
			
		||||
        payload,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -503,9 +497,7 @@ interface SetOAuthResultAction extends ReduxAction {
 | 
			
		||||
    payload: Pick<OAuthState, 'success' | 'code' | 'displayCode'>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SET_OAUTH_RESULT = 'set_oauth_result'; // TODO: remove
 | 
			
		||||
 | 
			
		||||
export function setOAuthCode(payload: { success: boolean; code: string; displayCode: boolean }): SetOAuthResultAction {
 | 
			
		||||
export function setOAuthCode(payload: Pick<OAuthState, 'success' | 'code' | 'displayCode'>): SetOAuthResultAction {
 | 
			
		||||
    return {
 | 
			
		||||
        type: 'set_oauth_result',
 | 
			
		||||
        payload,
 | 
			
		||||
@@ -515,7 +507,7 @@ export function setOAuthCode(payload: { success: boolean; code: string; displayC
 | 
			
		||||
export function resetOAuth(): AppAction {
 | 
			
		||||
    return (dispatch): void => {
 | 
			
		||||
        localStorage.removeItem('oauthData');
 | 
			
		||||
        dispatch(setOAuthRequest({}));
 | 
			
		||||
        dispatch(setOAuthRequest(null));
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								packages/app/components/auth/deviceCode/DeviceCode.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/app/components/auth/deviceCode/DeviceCode.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { FormattedMessage as Message, defineMessages } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
import factory from '../factory';
 | 
			
		||||
import Body from './DeviceCodeBody';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
    deviceCodeTitle: 'Device code',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default factory({
 | 
			
		||||
    title: messages.deviceCodeTitle,
 | 
			
		||||
    body: Body,
 | 
			
		||||
    footer: {
 | 
			
		||||
        color: 'green',
 | 
			
		||||
        children: <Message key="continueButton" defaultMessage="Continue" />,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										31
									
								
								packages/app/components/auth/deviceCode/DeviceCodeBody.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/app/components/auth/deviceCode/DeviceCodeBody.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { defineMessages } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
import { Input } from 'app/components/ui/form';
 | 
			
		||||
import BaseAuthBody from 'app/components/auth/BaseAuthBody';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
    deviceCode: 'Device code',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default class DeviceCodeBody extends BaseAuthBody {
 | 
			
		||||
    static displayName = 'DeviceCodeBody';
 | 
			
		||||
    static panelId = 'deviceCode';
 | 
			
		||||
 | 
			
		||||
    autoFocusField = 'user_code';
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
                {this.renderErrors()}
 | 
			
		||||
 | 
			
		||||
                <Input
 | 
			
		||||
                    {...this.bindField('user_code')}
 | 
			
		||||
                    icon="key"
 | 
			
		||||
                    required
 | 
			
		||||
                    placeholder={messages.deviceCode}
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/app/components/auth/deviceCode/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/app/components/auth/deviceCode/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export { default } from './DeviceCode';
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
import React, { MouseEventHandler } from 'react';
 | 
			
		||||
import React, { FC, MouseEventHandler, useEffect } from 'react';
 | 
			
		||||
import { Redirect } from 'react-router-dom';
 | 
			
		||||
import { FormattedMessage as Message } from 'react-intl';
 | 
			
		||||
import { Helmet } from 'react-helmet-async';
 | 
			
		||||
 | 
			
		||||
import { connect } from 'app/functions';
 | 
			
		||||
import { useReduxSelector } from 'app/functions';
 | 
			
		||||
import { Button } from 'app/components/ui/form';
 | 
			
		||||
import copy from 'app/services/copy';
 | 
			
		||||
 | 
			
		||||
@@ -16,102 +17,93 @@ interface Props {
 | 
			
		||||
    success?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Finish extends React.Component<Props> {
 | 
			
		||||
    render() {
 | 
			
		||||
        const { appName, code, state, displayCode, success } = this.props;
 | 
			
		||||
        const authData = JSON.stringify({
 | 
			
		||||
            auth_code: code,
 | 
			
		||||
            state,
 | 
			
		||||
const Finish: FC<Props> = () => {
 | 
			
		||||
    const { client, oauth } = useReduxSelector((state) => state.auth);
 | 
			
		||||
 | 
			
		||||
    const onCopyClick: MouseEventHandler = (event) => {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        copy(oauth!.code!);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let authData: string | undefined;
 | 
			
		||||
 | 
			
		||||
    if (oauth && 'state' in oauth.params) {
 | 
			
		||||
        authData = JSON.stringify({
 | 
			
		||||
            auth_code: oauth.code,
 | 
			
		||||
            state: oauth.params.state,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        history.pushState(null, document.title, `#${authData}`);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (authData) {
 | 
			
		||||
            history.pushState(null, document.title, `#${authData}`);
 | 
			
		||||
        }
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div className={styles.finishPage}>
 | 
			
		||||
                <Helmet title={authData} />
 | 
			
		||||
    if (!client || !oauth) {
 | 
			
		||||
        return <Redirect to="/" />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
                {success ? (
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <div className={styles.successBackground} />
 | 
			
		||||
                        <div className={styles.greenTitle}>
 | 
			
		||||
                            <Message
 | 
			
		||||
                                key="authForAppSuccessful"
 | 
			
		||||
                                defaultMessage="Authorization for {appName} was successfully completed"
 | 
			
		||||
                                values={{
 | 
			
		||||
                                    appName: <span className={styles.appName}>{appName}</span>,
 | 
			
		||||
                                }}
 | 
			
		||||
                            />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {displayCode ? (
 | 
			
		||||
                            <div data-testid="oauth-code-container">
 | 
			
		||||
                                <div className={styles.description}>
 | 
			
		||||
                                    <Message
 | 
			
		||||
                                        key="passCodeToApp"
 | 
			
		||||
                                        defaultMessage="To complete authorization process, please, provide the following code to {appName}"
 | 
			
		||||
                                        values={{ appName }}
 | 
			
		||||
                                    />
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className={styles.codeContainer}>
 | 
			
		||||
                                    <div className={styles.code}>{code}</div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <Button color="green" small onClick={this.onCopyClick}>
 | 
			
		||||
                                    <Message key="copy" defaultMessage="Copy" />
 | 
			
		||||
                                </Button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        ) : (
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles.finishPage}>
 | 
			
		||||
            {authData && <Helmet title={authData} />}
 | 
			
		||||
 | 
			
		||||
            {oauth.success ? (
 | 
			
		||||
                <div>
 | 
			
		||||
                    <div className={styles.successBackground} />
 | 
			
		||||
                    <div className={styles.greenTitle}>
 | 
			
		||||
                        <Message
 | 
			
		||||
                            key="authForAppSuccessful"
 | 
			
		||||
                            defaultMessage="Authorization for {appName} was successfully completed"
 | 
			
		||||
                            values={{
 | 
			
		||||
                                appName: <span className={styles.appName}>{client.name}</span>,
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {oauth.displayCode ? (
 | 
			
		||||
                        <div data-testid="oauth-code-container">
 | 
			
		||||
                            <div className={styles.description}>
 | 
			
		||||
                                <Message
 | 
			
		||||
                                    key="waitAppReaction"
 | 
			
		||||
                                    defaultMessage="Please, wait till your application response"
 | 
			
		||||
                                    key="passCodeToApp"
 | 
			
		||||
                                    defaultMessage="To complete authorization process, please, provide the following code to {appName}"
 | 
			
		||||
                                    values={{ appName: client.name }}
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                ) : (
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <div className={styles.failBackground} />
 | 
			
		||||
                        <div className={styles.redTitle}>
 | 
			
		||||
                            <Message
 | 
			
		||||
                                key="authForAppFailed"
 | 
			
		||||
                                defaultMessage="Authorization for {appName} was failed"
 | 
			
		||||
                                values={{
 | 
			
		||||
                                    appName: <span className={styles.appName}>{appName}</span>,
 | 
			
		||||
                                }}
 | 
			
		||||
                            />
 | 
			
		||||
                            <div className={styles.codeContainer}>
 | 
			
		||||
                                <div className={styles.code}>{oauth.code}</div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <Button color="green" small onClick={onCopyClick}>
 | 
			
		||||
                                <Message key="copy" defaultMessage="Copy" />
 | 
			
		||||
                            </Button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        <div className={styles.description}>
 | 
			
		||||
                            <Message
 | 
			
		||||
                                key="waitAppReaction"
 | 
			
		||||
                                defaultMessage="Please, wait till your application response"
 | 
			
		||||
                            />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    )}
 | 
			
		||||
                </div>
 | 
			
		||||
            ) : (
 | 
			
		||||
                <div>
 | 
			
		||||
                    <div className={styles.failBackground} />
 | 
			
		||||
                    <div className={styles.redTitle}>
 | 
			
		||||
                        <Message
 | 
			
		||||
                            key="authForAppFailed"
 | 
			
		||||
                            defaultMessage="Authorization for {appName} was failed"
 | 
			
		||||
                            values={{
 | 
			
		||||
                                appName: <span className={styles.appName}>{client.name}</span>,
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                )}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
                    <div className={styles.description}>
 | 
			
		||||
                        <Message key="waitAppReaction" defaultMessage="Please, wait till your application response" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            )}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    onCopyClick: MouseEventHandler = (event) => {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
 | 
			
		||||
        const { code } = this.props;
 | 
			
		||||
 | 
			
		||||
        if (code) {
 | 
			
		||||
            copy(code);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default connect(({ auth }) => {
 | 
			
		||||
    if (!auth || !auth.client || !auth.oauth) {
 | 
			
		||||
        throw new Error('Can not connect Finish component. No auth data in state');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        appName: auth.client.name,
 | 
			
		||||
        code: auth.oauth.code,
 | 
			
		||||
        displayCode: auth.oauth.displayCode,
 | 
			
		||||
        state: auth.oauth.state,
 | 
			
		||||
        success: auth.oauth.success,
 | 
			
		||||
    };
 | 
			
		||||
})(Finish);
 | 
			
		||||
export default Finish;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/app/components/auth/finish/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/app/components/auth/finish/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export { default } from './Finish';
 | 
			
		||||
@@ -38,15 +38,34 @@ export interface Client {
 | 
			
		||||
    description: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface OAuthState {
 | 
			
		||||
export interface OauthAuthCodeFlowParams {
 | 
			
		||||
    clientId: string;
 | 
			
		||||
    redirectUrl: string;
 | 
			
		||||
    responseType: string;
 | 
			
		||||
    description?: string;
 | 
			
		||||
    scope: string;
 | 
			
		||||
    prompt: string;
 | 
			
		||||
    loginHint: string;
 | 
			
		||||
    state: string;
 | 
			
		||||
    scope: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface OauthDeviceCodeFlowParams {
 | 
			
		||||
    userCode: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface OAuthState {
 | 
			
		||||
    params: OauthAuthCodeFlowParams | OauthDeviceCodeFlowParams;
 | 
			
		||||
    description?: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * Possible values:
 | 
			
		||||
     * - none - default behaviour
 | 
			
		||||
     * - consent - forcibly prompt user for rules acceptance
 | 
			
		||||
     * - select_account - force account choosage, even if user has only one
 | 
			
		||||
     * comma separated list of 'none' | 'consent' | 'select_account';
 | 
			
		||||
     */
 | 
			
		||||
    prompt?: string | Array<string>;
 | 
			
		||||
    /**
 | 
			
		||||
     * Allows to choose the account, which will be used for auth
 | 
			
		||||
     * The possible values: account id, email, username
 | 
			
		||||
     */
 | 
			
		||||
    loginHint?: string;
 | 
			
		||||
    success?: boolean;
 | 
			
		||||
    code?: string;
 | 
			
		||||
    displayCode?: boolean;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user