@aws-amplify/auth
Version:
Auth category of aws-amplify
171 lines (169 loc) • 7.14 kB
JavaScript
// 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
;