@okta/okta-auth-js
Version:
The Okta Auth SDK
404 lines (400 loc) • 14.1 kB
JavaScript
/*!
* Copyright (c) 2015-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/
import { removeNils, clone } from '../util/object.js';
import AuthSdkError from '../errors/AuthSdkError.js';
import '../errors/WWWAuthError.js';
import { isLocalhost, isIE11OrLess } from '../features.js';
import { REFRESH_TOKEN_STORAGE_KEY, TOKEN_STORAGE_NAME } from '../constants.js';
import '../http/request.js';
import 'tiny-emitter';
import 'js-cookie';
import 'cross-fetch';
import { validateToken } from './util/validateToken.js';
import SdkClock from '../clock.js';
import { isRefreshToken, isAccessToken, isIDToken } from './types/Token.js';
import { EVENT_EXPIRED, EVENT_RENEWED, EVENT_ADDED, EVENT_REMOVED, EVENT_ERROR, EVENT_SET_STORAGE } from './types/TokenManager.js';
const DEFAULT_OPTIONS = {
autoRenew: true,
autoRemove: true,
syncStorage: true,
clearPendingRemoveTokens: true,
storage: undefined,
expireEarlySeconds: 30,
storageKey: TOKEN_STORAGE_NAME
};
function defaultState() {
return {
expireTimeouts: {},
renewPromise: null
};
}
class TokenManager {
constructor(sdk, options = {}) {
this.sdk = sdk;
this.emitter = sdk.emitter;
if (!this.emitter) {
throw new AuthSdkError('Emitter should be initialized before TokenManager');
}
options = Object.assign({}, DEFAULT_OPTIONS, removeNils(options));
if (!isLocalhost()) {
options.expireEarlySeconds = DEFAULT_OPTIONS.expireEarlySeconds;
}
this.options = options;
const storageOptions = removeNils({
storageKey: options.storageKey,
secure: options.secure,
});
if (typeof options.storage === 'object') {
storageOptions.storageProvider = options.storage;
}
else if (options.storage) {
storageOptions.storageType = options.storage;
}
this.storage = sdk.storageManager.getTokenStorage(Object.assign(Object.assign({}, storageOptions), { useSeparateCookies: true }));
this.clock = SdkClock.create( );
this.state = defaultState();
}
on(event, handler, context) {
if (context) {
this.emitter.on(event, handler, context);
}
else {
this.emitter.on(event, handler);
}
}
off(event, handler) {
if (handler) {
this.emitter.off(event, handler);
}
else {
this.emitter.off(event);
}
}
start() {
if (this.options.clearPendingRemoveTokens) {
this.clearPendingRemoveTokens();
}
this.setExpireEventTimeoutAll();
this.state.started = true;
}
stop() {
this.clearExpireEventTimeoutAll();
this.state.started = false;
}
isStarted() {
return !!this.state.started;
}
getOptions() {
return clone(this.options);
}
getExpireTime(token) {
const expireEarlySeconds = this.options.expireEarlySeconds || 0;
var expireTime = token.expiresAt - expireEarlySeconds;
return expireTime;
}
hasExpired(token) {
var expireTime = this.getExpireTime(token);
return expireTime <= this.clock.now();
}
emitExpired(key, token) {
this.emitter.emit(EVENT_EXPIRED, key, token);
}
emitRenewed(key, freshToken, oldToken) {
this.emitter.emit(EVENT_RENEWED, key, freshToken, oldToken);
}
emitAdded(key, token) {
this.emitter.emit(EVENT_ADDED, key, token);
}
emitRemoved(key, token) {
this.emitter.emit(EVENT_REMOVED, key, token);
}
emitError(error) {
this.emitter.emit(EVENT_ERROR, error);
}
clearExpireEventTimeout(key) {
clearTimeout(this.state.expireTimeouts[key]);
delete this.state.expireTimeouts[key];
this.state.renewPromise = null;
}
clearExpireEventTimeoutAll() {
var expireTimeouts = this.state.expireTimeouts;
for (var key in expireTimeouts) {
if (!Object.prototype.hasOwnProperty.call(expireTimeouts, key)) {
continue;
}
this.clearExpireEventTimeout(key);
}
}
setExpireEventTimeout(key, token) {
if (isRefreshToken(token)) {
return;
}
var expireTime = this.getExpireTime(token);
var expireEventWait = Math.max(expireTime - this.clock.now(), 0) * 1000;
this.clearExpireEventTimeout(key);
var expireEventTimeout = setTimeout(() => {
this.emitExpired(key, token);
}, expireEventWait);
this.state.expireTimeouts[key] = expireEventTimeout;
}
setExpireEventTimeoutAll() {
var tokenStorage = this.storage.getStorage();
for (var key in tokenStorage) {
if (!Object.prototype.hasOwnProperty.call(tokenStorage, key)) {
continue;
}
var token = tokenStorage[key];
this.setExpireEventTimeout(key, token);
}
}
resetExpireEventTimeoutAll() {
this.clearExpireEventTimeoutAll();
this.setExpireEventTimeoutAll();
}
add(key, token) {
var tokenStorage = this.storage.getStorage();
validateToken(token);
tokenStorage[key] = token;
this.storage.setStorage(tokenStorage);
this.emitSetStorageEvent();
this.emitAdded(key, token);
this.setExpireEventTimeout(key, token);
}
getSync(key) {
var tokenStorage = this.storage.getStorage();
return tokenStorage[key];
}
async get(key) {
return this.getSync(key);
}
getTokensSync() {
const tokens = {};
const tokenStorage = this.storage.getStorage();
Object.keys(tokenStorage).forEach(key => {
const token = tokenStorage[key];
if (isAccessToken(token)) {
tokens.accessToken = token;
}
else if (isIDToken(token)) {
tokens.idToken = token;
}
else if (isRefreshToken(token)) {
tokens.refreshToken = token;
}
});
return tokens;
}
async getTokens() {
return this.getTokensSync();
}
getStorageKeyByType(type) {
const tokenStorage = this.storage.getStorage();
const key = Object.keys(tokenStorage).filter(key => {
const token = tokenStorage[key];
return (isAccessToken(token) && type === 'accessToken')
|| (isIDToken(token) && type === 'idToken')
|| (isRefreshToken(token) && type === 'refreshToken');
})[0];
return key;
}
getTokenType(token) {
if (isAccessToken(token)) {
return 'accessToken';
}
if (isIDToken(token)) {
return 'idToken';
}
if (isRefreshToken(token)) {
return 'refreshToken';
}
throw new AuthSdkError('Unknown token type');
}
emitSetStorageEvent() {
if (isIE11OrLess()) {
const storage = this.storage.getStorage();
this.emitter.emit(EVENT_SET_STORAGE, storage);
}
}
getStorage() {
return this.storage;
}
setTokens(tokens,
accessTokenCb, idTokenCb, refreshTokenCb) {
const handleTokenCallback = (key, token) => {
const type = this.getTokenType(token);
if (type === 'accessToken') {
accessTokenCb && accessTokenCb(key, token);
}
else if (type === 'idToken') {
idTokenCb && idTokenCb(key, token);
}
else if (type === 'refreshToken') {
refreshTokenCb && refreshTokenCb(key, token);
}
};
const handleAdded = (key, token) => {
this.emitAdded(key, token);
this.setExpireEventTimeout(key, token);
handleTokenCallback(key, token);
};
const handleRenewed = (key, token, oldToken) => {
this.emitRenewed(key, token, oldToken);
this.clearExpireEventTimeout(key);
this.setExpireEventTimeout(key, token);
handleTokenCallback(key, token);
};
const handleRemoved = (key, token) => {
this.clearExpireEventTimeout(key);
this.emitRemoved(key, token);
handleTokenCallback(key, token);
};
const types = ['idToken', 'accessToken', 'refreshToken'];
const existingTokens = this.getTokensSync();
types.forEach((type) => {
const token = tokens[type];
if (token) {
validateToken(token, type);
}
});
const storage = types.reduce((storage, type) => {
const token = tokens[type];
if (token) {
const storageKey = this.getStorageKeyByType(type) || type;
storage[storageKey] = token;
}
return storage;
}, {});
this.storage.setStorage(storage);
this.emitSetStorageEvent();
types.forEach(type => {
const newToken = tokens[type];
const existingToken = existingTokens[type];
const storageKey = this.getStorageKeyByType(type) || type;
if (newToken && existingToken) {
handleRemoved(storageKey, existingToken);
handleAdded(storageKey, newToken);
handleRenewed(storageKey, newToken, existingToken);
}
else if (newToken) {
handleAdded(storageKey, newToken);
}
else if (existingToken) {
handleRemoved(storageKey, existingToken);
}
});
}
remove(key) {
this.clearExpireEventTimeout(key);
var tokenStorage = this.storage.getStorage();
var removedToken = tokenStorage[key];
delete tokenStorage[key];
this.storage.setStorage(tokenStorage);
this.emitSetStorageEvent();
this.emitRemoved(key, removedToken);
}
async renewToken(token) {
var _a;
return (_a = this.sdk.token) === null || _a === void 0 ? void 0 : _a.renew(token);
}
validateToken(token) {
return validateToken(token);
}
renew(key) {
if (this.state.renewPromise) {
return this.state.renewPromise;
}
try {
var token = this.getSync(key);
let shouldRenew = token !== undefined;
if (!token && key === 'accessToken') {
const refreshKey = this.getStorageKeyByType('refreshToken');
const refreshToken = this.getSync(refreshKey);
shouldRenew = refreshToken !== undefined;
}
if (!shouldRenew) {
throw new AuthSdkError('The tokenManager has no token for the key: ' + key);
}
}
catch (err) {
this.emitError(err);
return Promise.reject(err);
}
this.clearExpireEventTimeout(key);
const renewPromise = this.state.renewPromise = this.sdk.token.renewTokens()
.then(tokens => {
this.setTokens(tokens);
if (!token && key === 'accessToken') {
const accessToken = tokens['accessToken'];
this.emitRenewed(key, accessToken, null);
return accessToken;
}
const tokenType = this.getTokenType(token);
return tokens[tokenType];
})
.catch(err => {
this.remove(key);
err.tokenKey = key;
this.emitError(err);
throw err;
})
.finally(() => {
this.state.renewPromise = null;
});
return renewPromise;
}
clear() {
const tokens = this.getTokensSync();
this.clearExpireEventTimeoutAll();
this.storage.clearStorage();
this.emitSetStorageEvent();
Object.keys(tokens).forEach(key => {
this.emitRemoved(key, tokens[key]);
});
}
clearPendingRemoveTokens() {
const tokenStorage = this.storage.getStorage();
const removedTokens = {};
Object.keys(tokenStorage).forEach(key => {
if (tokenStorage[key].pendingRemove) {
removedTokens[key] = tokenStorage[key];
delete tokenStorage[key];
}
});
this.storage.setStorage(tokenStorage);
this.emitSetStorageEvent();
Object.keys(removedTokens).forEach(key => {
this.clearExpireEventTimeout(key);
this.emitRemoved(key, removedTokens[key]);
});
}
updateRefreshToken(token) {
const key = this.getStorageKeyByType('refreshToken') || REFRESH_TOKEN_STORAGE_KEY;
var tokenStorage = this.storage.getStorage();
validateToken(token);
tokenStorage[key] = token;
this.storage.setStorage(tokenStorage);
this.emitSetStorageEvent();
}
removeRefreshToken() {
const key = this.getStorageKeyByType('refreshToken') || REFRESH_TOKEN_STORAGE_KEY;
this.remove(key);
}
addPendingRemoveFlags() {
const tokens = this.getTokensSync();
Object.keys(tokens).forEach(key => {
tokens[key].pendingRemove = true;
});
this.setTokens(tokens);
}
}
export { TokenManager };
//# sourceMappingURL=TokenManager.js.map