UNPKG

@aws-amplify/auth

Version:
171 lines (169 loc) • 7.14 kB
'use strict'; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 Object.defineProperty(exports, "__esModule", { value: true }); exports.completeOAuthFlow = void 0; const utils_1 = require("@aws-amplify/core/internals/utils"); const core_1 = require("@aws-amplify/core"); const cacheTokens_1 = require("../../tokenProvider/cacheTokens"); const dispatchSignedInHubEvent_1 = require("../dispatchSignedInHubEvent"); const tokenProvider_1 = require("../../tokenProvider"); const createOAuthError_1 = require("./createOAuthError"); const inflightPromise_1 = require("./inflightPromise"); const validateState_1 = require("./validateState"); const oAuthStore_1 = require("./oAuthStore"); const completeOAuthFlow = async ({ currentUrl, userAgentValue, clientId, redirectUri, responseType, domain, preferPrivateSession, }) => { const urlParams = new utils_1.AmplifyUrl(currentUrl); const error = urlParams.searchParams.get('error'); const errorMessage = urlParams.searchParams.get('error_description'); if (error) { throw (0, createOAuthError_1.createOAuthError)(errorMessage ?? error); } if (responseType === 'code') { return handleCodeFlow({ currentUrl, userAgentValue, clientId, redirectUri, domain, preferPrivateSession, }); } return handleImplicitFlow({ currentUrl, redirectUri, preferPrivateSession, }); }; exports.completeOAuthFlow = completeOAuthFlow; const handleCodeFlow = async ({ currentUrl, userAgentValue, clientId, redirectUri, domain, preferPrivateSession, }) => { /* Convert URL into an object with parameters as keys { redirect_uri: 'http://localhost:3000/', response_type: 'code', ...} */ const url = new utils_1.AmplifyUrl(currentUrl); const code = url.searchParams.get('code'); const state = url.searchParams.get('state'); // if `code` or `state` is not presented in the redirect url, most likely // that the end user cancelled the inflight oauth flow by: // 1. clicking the back button of browser // 2. closing the provider hosted UI page and coming back to the app if (!code || !state) { throw (0, createOAuthError_1.createOAuthError)('User cancelled OAuth flow.'); } // may throw error is being caught in attemptCompleteOAuthFlow.ts const validatedState = await (0, validateState_1.validateState)(state); const oAuthTokenEndpoint = 'https://' + domain + '/oauth2/token'; // TODO(v6): check hub events // dispatchAuthEvent( // 'codeFlow', // {}, // `Retrieving tokens from ${oAuthTokenEndpoint}` // ); const codeVerifier = await oAuthStore_1.oAuthStore.loadPKCE(); const oAuthTokenBody = { grant_type: 'authorization_code', code, client_id: clientId, redirect_uri: redirectUri, ...(codeVerifier ? { code_verifier: codeVerifier } : {}), }; const body = Object.entries(oAuthTokenBody) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join('&'); const { access_token, refresh_token: refreshToken, id_token, error, error_message: errorMessage, token_type, expires_in, } = await (await fetch(oAuthTokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', [utils_1.USER_AGENT_HEADER]: userAgentValue, }, body, })).json(); if (error) { // error is being caught in attemptCompleteOAuthFlow.ts throw (0, createOAuthError_1.createOAuthError)(errorMessage ?? error); } const username = (access_token && (0, core_1.decodeJWT)(access_token).payload.username) ?? 'username'; await (0, cacheTokens_1.cacheCognitoTokens)({ username, AccessToken: access_token, IdToken: id_token, RefreshToken: refreshToken, TokenType: token_type, ExpiresIn: expires_in, }); return completeFlow({ redirectUri, state: validatedState, preferPrivateSession, }); }; const handleImplicitFlow = async ({ currentUrl, redirectUri, preferPrivateSession, }) => { // hash is `null` if `#` doesn't exist on URL const url = new utils_1.AmplifyUrl(currentUrl); const { id_token, access_token, state, token_type, expires_in, error_description, error, } = (url.hash ?? '#') .substring(1) // Remove # from returned code .split('&') .map(pairings => pairings.split('=')) .reduce((accum, [k, v]) => ({ ...accum, [k]: v }), { id_token: undefined, access_token: undefined, state: undefined, token_type: undefined, expires_in: undefined, error_description: undefined, error: undefined, }); if (error) { throw (0, createOAuthError_1.createOAuthError)(error_description ?? error); } if (!access_token) { // error is being caught in attemptCompleteOAuthFlow.ts throw (0, createOAuthError_1.createOAuthError)('No access token returned from OAuth flow.'); } const validatedState = await (0, validateState_1.validateState)(state); const username = (access_token && (0, core_1.decodeJWT)(access_token).payload.username) ?? 'username'; await (0, cacheTokens_1.cacheCognitoTokens)({ username, AccessToken: access_token, IdToken: id_token, TokenType: token_type, ExpiresIn: expires_in, }); return completeFlow({ redirectUri, state: validatedState, preferPrivateSession, }); }; const completeFlow = async ({ redirectUri, state, preferPrivateSession, }) => { await tokenProvider_1.tokenOrchestrator.setOAuthMetadata({ oauthSignIn: true, }); await oAuthStore_1.oAuthStore.clearOAuthData(); await oAuthStore_1.oAuthStore.storeOAuthSignIn(true, preferPrivateSession); // this should be called before any call that involves `fetchAuthSession` // e.g. `getCurrentUser()` below, so it allows every inflight async calls to // `fetchAuthSession` can be resolved (0, inflightPromise_1.resolveAndClearInflightPromises)(); // clear history before sending out final Hub events clearHistory(redirectUri); if (isCustomState(state)) { core_1.Hub.dispatch('auth', { event: 'customOAuthState', data: (0, utils_1.urlSafeDecode)(getCustomState(state)), }, 'Auth', utils_1.AMPLIFY_SYMBOL); } core_1.Hub.dispatch('auth', { event: 'signInWithRedirect' }, 'Auth', utils_1.AMPLIFY_SYMBOL); await (0, dispatchSignedInHubEvent_1.dispatchSignedInHubEvent)(); }; const isCustomState = (state) => { return /-/.test(state); }; const getCustomState = (state) => { return state.split('-').splice(1).join('-'); }; const clearHistory = (redirectUri) => { if (typeof window !== 'undefined' && typeof window.history !== 'undefined') { window.history.replaceState(window.history.state, '', redirectUri); } }; //# sourceMappingURL=completeOAuthFlow.js.map