@aws-amplify/auth
Version:
Auth category of aws-amplify
245 lines (243 loc) • 11.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.createKeysForAuthStorage = exports.DefaultTokenStore = void 0;
exports.getAuthStorageKeys = getAuthStorageKeys;
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
const core_1 = require("@aws-amplify/core");
const utils_1 = require("@aws-amplify/core/internals/utils");
const AuthError_1 = require("../../../errors/AuthError");
const getCurrentUser_1 = require("../apis/getCurrentUser");
const types_1 = require("./types");
const errorHelpers_1 = require("./errorHelpers");
const constants_1 = require("./constants");
class DefaultTokenStore {
getKeyValueStorage() {
if (!this.keyValueStorage) {
throw new AuthError_1.AuthError({
name: 'KeyValueStorageNotFoundException',
message: 'KeyValueStorage was not found in TokenStore',
});
}
return this.keyValueStorage;
}
setKeyValueStorage(keyValueStorage) {
// If notify is active, detach from the old storage and re-attach to the
// new one so a storage swap (e.g. SSR/adapter cookie storage) does not
// orphan the listener on the previous store.
const wasActive = !!this.stopNotify;
this.teardownNotify();
this.keyValueStorage = keyValueStorage;
if (wasActive) {
this.setupNotify();
}
}
setAuthConfig(authConfig) {
this.authConfig = authConfig;
}
setupNotify() {
// Idempotent: a second call while already subscribed is a no-op rather
// than a second (leaked) listener.
if (this.stopNotify) {
return;
}
this.stopNotify = this.keyValueStorage?.addListener?.(async (e) => {
const key = e.key || '';
// Only react to this provider's auth token keys. Match by
// prefix/suffix instead of a positional `split('.')` so usernames
// that contain dots (e.g. email addresses) are handled correctly —
// keys look like `${AUTH_KEY_PREFIX}.<clientId>.<username>.<type>`.
if (!key.startsWith(`${constants_1.AUTH_KEY_PREFIX}.`)) {
return;
}
const { newValue, oldValue } = e;
if (key.endsWith('.refreshToken')) {
// The refreshToken key drives sign-in / sign-out only. Its
// presence transition (falsy <-> truthy) is the reliable
// cross-tab signal. `oldValue`/`newValue` are compared with
// truthy/falsy checks rather than `=== null` so adapter storages
// that surface an absent value as `undefined` or `''` still work.
// Note: non-rotating pools rewrite an identical refreshToken,
// which fires no storage event, so value→value changes here are
// not observable and must not be relied on for tokenRefresh.
if (newValue && !oldValue) {
core_1.Hub.dispatch('auth', {
event: 'signedIn',
data: await (0, getCurrentUser_1.getCurrentUser)(),
}, 'Auth', utils_1.AMPLIFY_SYMBOL, true);
}
else if (!newValue && oldValue) {
core_1.Hub.dispatch('auth', {
event: 'signedOut',
}, 'Auth', utils_1.AMPLIFY_SYMBOL, true);
}
}
else if (key.endsWith('.accessToken')) {
// tokenRefresh is detected on the accessToken key: it always
// changes value on a refresh for both rotating and non-rotating
// pools. Guard on both values present and actually differing so
// that sign-in's null→value transition does not masquerade as a
// refresh. On rotation pools both keys change, but only this
// branch dispatches tokenRefresh, so there is no double dispatch.
if (oldValue && newValue && oldValue !== newValue) {
core_1.Hub.dispatch('auth', {
event: 'tokenRefresh',
}, 'Auth', utils_1.AMPLIFY_SYMBOL, true);
}
}
});
}
/**
* Detaches the cross-tab storage listener registered by {@link setupNotify}
* and clears the retained unsubscribe handle, allowing a subsequent
* `setupNotify()` to re-register (e.g. after a storage swap, in tests, or
* during HMR). No-op when notify was never set up.
*/
teardownNotify() {
this.stopNotify?.();
this.stopNotify = undefined;
}
async loadTokens() {
// TODO(v6): migration logic should be here
// Reading V5 tokens old format
try {
const authKeys = await this.getAuthKeys();
const accessTokenString = await this.getKeyValueStorage().getItem(authKeys.accessToken);
if (!accessTokenString) {
throw new AuthError_1.AuthError({
name: 'NoSessionFoundException',
message: 'Auth session was not found. Make sure to call signIn.',
});
}
const accessToken = (0, utils_1.decodeJWT)(accessTokenString);
const itString = await this.getKeyValueStorage().getItem(authKeys.idToken);
const idToken = itString ? (0, utils_1.decodeJWT)(itString) : undefined;
const refreshToken = (await this.getKeyValueStorage().getItem(authKeys.refreshToken)) ??
undefined;
const clockDriftString = (await this.getKeyValueStorage().getItem(authKeys.clockDrift)) ?? '0';
const clockDrift = Number.parseInt(clockDriftString);
const signInDetails = await this.getKeyValueStorage().getItem(authKeys.signInDetails);
const tokens = {
accessToken,
idToken,
refreshToken,
deviceMetadata: (await this.getDeviceMetadata()) ?? undefined,
clockDrift,
username: await this.getLastAuthUser(),
};
if (signInDetails) {
tokens.signInDetails = JSON.parse(signInDetails);
}
return tokens;
}
catch (err) {
return null;
}
}
async storeTokens(tokens) {
(0, errorHelpers_1.assert)(tokens !== undefined, errorHelpers_1.TokenProviderErrorCode.InvalidAuthTokens);
const lastAuthUser = tokens.username;
await this.getKeyValueStorage().setItem(this.getLastAuthUserKey(), lastAuthUser);
const authKeys = await this.getAuthKeys();
await this.getKeyValueStorage().setItem(authKeys.accessToken, tokens.accessToken.toString());
if (tokens.idToken) {
await this.getKeyValueStorage().setItem(authKeys.idToken, tokens.idToken.toString());
}
else {
await this.getKeyValueStorage().removeItem(authKeys.idToken);
}
if (tokens.refreshToken) {
await this.getKeyValueStorage().setItem(authKeys.refreshToken, tokens.refreshToken);
}
else {
await this.getKeyValueStorage().removeItem(authKeys.refreshToken);
}
if (tokens.deviceMetadata) {
if (tokens.deviceMetadata.deviceKey) {
await this.getKeyValueStorage().setItem(authKeys.deviceKey, tokens.deviceMetadata.deviceKey);
}
if (tokens.deviceMetadata.deviceGroupKey) {
await this.getKeyValueStorage().setItem(authKeys.deviceGroupKey, tokens.deviceMetadata.deviceGroupKey);
}
await this.getKeyValueStorage().setItem(authKeys.randomPasswordKey, tokens.deviceMetadata.randomPassword);
}
if (tokens.signInDetails) {
await this.getKeyValueStorage().setItem(authKeys.signInDetails, JSON.stringify(tokens.signInDetails));
}
else {
await this.getKeyValueStorage().removeItem(authKeys.signInDetails);
}
await this.getKeyValueStorage().setItem(authKeys.clockDrift, `${tokens.clockDrift}`);
}
async clearTokens() {
const authKeys = await this.getAuthKeys();
// Not calling clear because it can remove data that is not managed by AuthTokenStore
await Promise.all([
this.getKeyValueStorage().removeItem(authKeys.accessToken),
this.getKeyValueStorage().removeItem(authKeys.idToken),
this.getKeyValueStorage().removeItem(authKeys.clockDrift),
this.getKeyValueStorage().removeItem(authKeys.refreshToken),
this.getKeyValueStorage().removeItem(authKeys.signInDetails),
this.getKeyValueStorage().removeItem(this.getLastAuthUserKey()),
this.getKeyValueStorage().removeItem(authKeys.oauthMetadata),
]);
}
async getDeviceMetadata(username) {
const authKeys = await this.getAuthKeys(username);
const deviceKey = await this.getKeyValueStorage().getItem(authKeys.deviceKey);
const deviceGroupKey = await this.getKeyValueStorage().getItem(authKeys.deviceGroupKey);
const randomPassword = await this.getKeyValueStorage().getItem(authKeys.randomPasswordKey);
return randomPassword && deviceGroupKey && deviceKey
? {
deviceKey,
deviceGroupKey,
randomPassword,
}
: null;
}
async clearDeviceMetadata(username) {
const authKeys = await this.getAuthKeys(username);
await Promise.all([
this.getKeyValueStorage().removeItem(authKeys.deviceKey),
this.getKeyValueStorage().removeItem(authKeys.deviceGroupKey),
this.getKeyValueStorage().removeItem(authKeys.randomPasswordKey),
]);
}
async getAuthKeys(username) {
(0, utils_1.assertTokenProviderConfig)(this.authConfig?.Cognito);
const lastAuthUser = username ?? (await this.getLastAuthUser());
return (0, exports.createKeysForAuthStorage)(constants_1.AUTH_KEY_PREFIX, `${this.authConfig.Cognito.userPoolClientId}.${lastAuthUser}`);
}
getLastAuthUserKey() {
(0, utils_1.assertTokenProviderConfig)(this.authConfig?.Cognito);
const identifier = this.authConfig.Cognito.userPoolClientId;
return `${constants_1.AUTH_KEY_PREFIX}.${identifier}.LastAuthUser`;
}
async getLastAuthUser() {
const lastAuthUser = (await this.getKeyValueStorage().getItem(this.getLastAuthUserKey())) ??
'username';
return lastAuthUser;
}
async setOAuthMetadata(metadata) {
const { oauthMetadata: oauthMetadataKey } = await this.getAuthKeys();
await this.getKeyValueStorage().setItem(oauthMetadataKey, JSON.stringify(metadata));
}
async getOAuthMetadata() {
const { oauthMetadata: oauthMetadataKey } = await this.getAuthKeys();
const oauthMetadata = await this.getKeyValueStorage().getItem(oauthMetadataKey);
return oauthMetadata && JSON.parse(oauthMetadata);
}
}
exports.DefaultTokenStore = DefaultTokenStore;
const createKeysForAuthStorage = (provider, identifier) => {
return getAuthStorageKeys(types_1.AuthTokenStorageKeys)(`${provider}`, identifier);
};
exports.createKeysForAuthStorage = createKeysForAuthStorage;
function getAuthStorageKeys(authKeys) {
const keys = Object.values({ ...authKeys });
return (prefix, identifier) => keys.reduce((acc, authKey) => ({
...acc,
[authKey]: `${prefix}.${identifier}.${authKey}`,
}), {});
}
//# sourceMappingURL=TokenStore.js.map