UNPKG

14.7 kBJavaScriptView Raw
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3import { __assign, __awaiter, __generator, __read } from "tslib";
4import { parse } from 'url'; // Used for OAuth parsing of Cognito Hosted UI
5import { launchUri } from './urlOpener';
6import * as oAuthStorage from './oauthStorage';
7import { Buffer } from 'buffer';
8import { isCognitoHostedOpts, CognitoHostedUIIdentityProvider, } from '../types/Auth';
9import { ConsoleLogger as Logger, Hub, urlSafeEncode } from '@aws-amplify/core';
10import { Sha256 } from '@aws-crypto/sha256-js';
11var AMPLIFY_SYMBOL = (typeof Symbol !== 'undefined' && typeof Symbol.for === 'function'
12 ? Symbol.for('amplify_default')
13 : '@@amplify_default');
14var dispatchAuthEvent = function (event, data, message) {
15 Hub.dispatch('auth', { event: event, data: data, message: message }, 'Auth', AMPLIFY_SYMBOL);
16};
17var logger = new Logger('OAuth');
18var OAuth = /** @class */ (function () {
19 function OAuth(_a) {
20 var config = _a.config, cognitoClientId = _a.cognitoClientId, _b = _a.scopes, scopes = _b === void 0 ? [] : _b;
21 this._urlOpener = config.urlOpener || launchUri;
22 this._config = config;
23 this._cognitoClientId = cognitoClientId;
24 if (!this.isValidScopes(scopes))
25 throw Error('scopes must be a String Array');
26 this._scopes = scopes;
27 }
28 OAuth.prototype.isValidScopes = function (scopes) {
29 return (Array.isArray(scopes) && scopes.every(function (scope) { return typeof scope === 'string'; }));
30 };
31 OAuth.prototype.oauthSignIn = function (responseType, domain, redirectSignIn, clientId, provider, customState) {
32 if (responseType === void 0) { responseType = 'code'; }
33 if (provider === void 0) { provider = CognitoHostedUIIdentityProvider.Cognito; }
34 var generatedState = this._generateState(32);
35 /* encodeURIComponent is not URL safe, use urlSafeEncode instead. Cognito
36 single-encodes/decodes url on first sign in and double-encodes/decodes url
37 when user already signed in. Using encodeURIComponent, Base32, Base64 add
38 characters % or = which on further encoding becomes unsafe. '=' create issue
39 for parsing query params.
40 Refer: https://github.com/aws-amplify/amplify-js/issues/5218 */
41 var state = customState
42 ? generatedState + "-" + urlSafeEncode(customState)
43 : generatedState;
44 oAuthStorage.setState(state);
45 var pkce_key = this._generateRandom(128);
46 oAuthStorage.setPKCE(pkce_key);
47 var code_challenge = this._generateChallenge(pkce_key);
48 var code_challenge_method = 'S256';
49 var scopesString = this._scopes.join(' ');
50 var queryString = Object.entries(__assign(__assign({ redirect_uri: redirectSignIn, response_type: responseType, client_id: clientId, identity_provider: provider, scope: scopesString, state: state }, (responseType === 'code' ? { code_challenge: code_challenge } : {})), (responseType === 'code' ? { code_challenge_method: code_challenge_method } : {})))
51 .map(function (_a) {
52 var _b = __read(_a, 2), k = _b[0], v = _b[1];
53 return encodeURIComponent(k) + "=" + encodeURIComponent(v);
54 })
55 .join('&');
56 var URL = "https://" + domain + "/oauth2/authorize?" + queryString;
57 logger.debug("Redirecting to " + URL);
58 this._urlOpener(URL, redirectSignIn);
59 };
60 OAuth.prototype._handleCodeFlow = function (currentUrl) {
61 return __awaiter(this, void 0, void 0, function () {
62 var code, currentUrlPathname, redirectSignInPathname, oAuthTokenEndpoint, client_id, redirect_uri, code_verifier, oAuthTokenBody, body, _a, access_token, refresh_token, id_token, error;
63 return __generator(this, function (_b) {
64 switch (_b.label) {
65 case 0:
66 code = (parse(currentUrl).query || '')
67 .split('&')
68 .map(function (pairings) { return pairings.split('='); })
69 .reduce(function (accum, _a) {
70 var _b;
71 var _c = __read(_a, 2), k = _c[0], v = _c[1];
72 return (__assign(__assign({}, accum), (_b = {}, _b[k] = v, _b)));
73 }, { code: undefined }).code;
74 currentUrlPathname = parse(currentUrl).pathname || '/';
75 redirectSignInPathname = parse(this._config.redirectSignIn).pathname || '/';
76 if (!code || currentUrlPathname !== redirectSignInPathname) {
77 return [2 /*return*/];
78 }
79 oAuthTokenEndpoint = 'https://' + this._config.domain + '/oauth2/token';
80 dispatchAuthEvent('codeFlow', {}, "Retrieving tokens from " + oAuthTokenEndpoint);
81 client_id = isCognitoHostedOpts(this._config)
82 ? this._cognitoClientId
83 : this._config.clientID;
84 redirect_uri = isCognitoHostedOpts(this._config)
85 ? this._config.redirectSignIn
86 : this._config.redirectUri;
87 code_verifier = oAuthStorage.getPKCE();
88 oAuthTokenBody = __assign({ grant_type: 'authorization_code', code: code,
89 client_id: client_id,
90 redirect_uri: redirect_uri }, (code_verifier ? { code_verifier: code_verifier } : {}));
91 logger.debug("Calling token endpoint: " + oAuthTokenEndpoint + " with", oAuthTokenBody);
92 body = Object.entries(oAuthTokenBody)
93 .map(function (_a) {
94 var _b = __read(_a, 2), k = _b[0], v = _b[1];
95 return encodeURIComponent(k) + "=" + encodeURIComponent(v);
96 })
97 .join('&');
98 return [4 /*yield*/, fetch(oAuthTokenEndpoint, {
99 method: 'POST',
100 headers: {
101 'Content-Type': 'application/x-www-form-urlencoded',
102 },
103 body: body,
104 })];
105 case 1: return [4 /*yield*/, (_b.sent()).json()];
106 case 2:
107 _a = _b.sent(), access_token = _a.access_token, refresh_token = _a.refresh_token, id_token = _a.id_token, error = _a.error;
108 if (error) {
109 throw new Error(error);
110 }
111 return [2 /*return*/, {
112 accessToken: access_token,
113 refreshToken: refresh_token,
114 idToken: id_token,
115 }];
116 }
117 });
118 });
119 };
120 OAuth.prototype._handleImplicitFlow = function (currentUrl) {
121 return __awaiter(this, void 0, void 0, function () {
122 var _a, id_token, access_token;
123 return __generator(this, function (_b) {
124 _a = (parse(currentUrl).hash || '#')
125 .substr(1) // Remove # from returned code
126 .split('&')
127 .map(function (pairings) { return pairings.split('='); })
128 .reduce(function (accum, _a) {
129 var _b;
130 var _c = __read(_a, 2), k = _c[0], v = _c[1];
131 return (__assign(__assign({}, accum), (_b = {}, _b[k] = v, _b)));
132 }, {
133 id_token: undefined,
134 access_token: undefined,
135 }), id_token = _a.id_token, access_token = _a.access_token;
136 dispatchAuthEvent('implicitFlow', {}, "Got tokens from " + currentUrl);
137 logger.debug("Retrieving implicit tokens from " + currentUrl + " with");
138 return [2 /*return*/, {
139 accessToken: access_token,
140 idToken: id_token,
141 refreshToken: null,
142 }];
143 });
144 });
145 };
146 OAuth.prototype.handleAuthResponse = function (currentUrl) {
147 return __awaiter(this, void 0, void 0, function () {
148 var urlParams, error, error_description, state, _a, _b, e_1;
149 return __generator(this, function (_c) {
150 switch (_c.label) {
151 case 0:
152 _c.trys.push([0, 5, , 6]);
153 urlParams = currentUrl
154 ? __assign(__assign({}, (parse(currentUrl).hash || '#')
155 .substr(1)
156 .split('&')
157 .map(function (entry) { return entry.split('='); })
158 .reduce(function (acc, _a) {
159 var _b = __read(_a, 2), k = _b[0], v = _b[1];
160 return ((acc[k] = v), acc);
161 }, {})), (parse(currentUrl).query || '')
162 .split('&')
163 .map(function (entry) { return entry.split('='); })
164 .reduce(function (acc, _a) {
165 var _b = __read(_a, 2), k = _b[0], v = _b[1];
166 return ((acc[k] = v), acc);
167 }, {}))
168 : {};
169 error = urlParams.error, error_description = urlParams.error_description;
170 if (error) {
171 throw new Error(error_description);
172 }
173 state = this._validateState(urlParams);
174 logger.debug("Starting " + this._config.responseType + " flow with " + currentUrl);
175 if (!(this._config.responseType === 'code')) return [3 /*break*/, 2];
176 _a = [{}];
177 return [4 /*yield*/, this._handleCodeFlow(currentUrl)];
178 case 1: return [2 /*return*/, __assign.apply(void 0, [__assign.apply(void 0, _a.concat([(_c.sent())])), { state: state }])];
179 case 2:
180 _b = [{}];
181 return [4 /*yield*/, this._handleImplicitFlow(currentUrl)];
182 case 3: return [2 /*return*/, __assign.apply(void 0, [__assign.apply(void 0, _b.concat([(_c.sent())])), { state: state }])];
183 case 4: return [3 /*break*/, 6];
184 case 5:
185 e_1 = _c.sent();
186 logger.error("Error handling auth response.", e_1);
187 throw e_1;
188 case 6: return [2 /*return*/];
189 }
190 });
191 });
192 };
193 OAuth.prototype._validateState = function (urlParams) {
194 if (!urlParams) {
195 return;
196 }
197 var savedState = oAuthStorage.getState();
198 var returnedState = urlParams.state;
199 // This is because savedState only exists if the flow was initiated by Amplify
200 if (savedState && savedState !== returnedState) {
201 throw new Error('Invalid state in OAuth flow');
202 }
203 return returnedState;
204 };
205 OAuth.prototype.signOut = function () {
206 return __awaiter(this, void 0, void 0, function () {
207 var oAuthLogoutEndpoint, client_id, signout_uri;
208 return __generator(this, function (_a) {
209 oAuthLogoutEndpoint = 'https://' + this._config.domain + '/logout?';
210 client_id = isCognitoHostedOpts(this._config)
211 ? this._cognitoClientId
212 : this._config.oauth.clientID;
213 signout_uri = isCognitoHostedOpts(this._config)
214 ? this._config.redirectSignOut
215 : this._config.returnTo;
216 oAuthLogoutEndpoint += Object.entries({
217 client_id: client_id,
218 logout_uri: encodeURIComponent(signout_uri),
219 })
220 .map(function (_a) {
221 var _b = __read(_a, 2), k = _b[0], v = _b[1];
222 return k + "=" + v;
223 })
224 .join('&');
225 dispatchAuthEvent('oAuthSignOut', { oAuth: 'signOut' }, "Signing out from " + oAuthLogoutEndpoint);
226 logger.debug("Signing out from " + oAuthLogoutEndpoint);
227 return [2 /*return*/, this._urlOpener(oAuthLogoutEndpoint, signout_uri)];
228 });
229 });
230 };
231 OAuth.prototype._generateState = function (length) {
232 var result = '';
233 var i = length;
234 var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
235 for (; i > 0; --i)
236 result += chars[Math.round(Math.random() * (chars.length - 1))];
237 return result;
238 };
239 OAuth.prototype._generateChallenge = function (code) {
240 var awsCryptoHash = new Sha256();
241 awsCryptoHash.update(code);
242 var resultFromAWSCrypto = awsCryptoHash.digestSync();
243 var b64 = Buffer.from(resultFromAWSCrypto).toString('base64');
244 var base64URLFromAWSCrypto = this._base64URL(b64);
245 return base64URLFromAWSCrypto;
246 };
247 OAuth.prototype._base64URL = function (string) {
248 return string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
249 };
250 OAuth.prototype._generateRandom = function (size) {
251 var CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
252 var buffer = new Uint8Array(size);
253 if (typeof window !== 'undefined' && !!window.crypto) {
254 window.crypto.getRandomValues(buffer);
255 }
256 else {
257 for (var i = 0; i < size; i += 1) {
258 buffer[i] = (Math.random() * CHARSET.length) | 0;
259 }
260 }
261 return this._bufferToString(buffer);
262 };
263 OAuth.prototype._bufferToString = function (buffer) {
264 var CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
265 var state = [];
266 for (var i = 0; i < buffer.byteLength; i += 1) {
267 var index = buffer[i] % CHARSET.length;
268 state.push(CHARSET[index]);
269 }
270 return state.join('');
271 };
272 return OAuth;
273}());
274export default OAuth;
275//# sourceMappingURL=OAuth.js.map
\No newline at end of file