@okta/okta-auth-js
Version:
The Okta Auth SDK
225 lines (182 loc) • 6.69 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.INITIAL_AUTH_STATE = exports.AuthStateManager = void 0;
var _pCancelable = _interopRequireDefault(require("p-cancelable"));
var _errors = require("../errors");
var _oidc = require("../oidc");
var _util = require("../util");
/*!
* 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.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// Do not use this type in code, so it won't be emitted in the declaration output
const INITIAL_AUTH_STATE = null;
exports.INITIAL_AUTH_STATE = INITIAL_AUTH_STATE;
const DEFAULT_PENDING = {
updateAuthStatePromise: null,
canceledTimes: 0
};
const EVENT_AUTH_STATE_CHANGE = 'authStateChange';
const MAX_PROMISE_CANCEL_TIMES = 10; // only compare first level of authState
const isSameAuthState = (prevState, state) => {
// initial state is null
if (!prevState) {
return false;
}
return prevState.isAuthenticated === state.isAuthenticated && JSON.stringify(prevState.idToken) === JSON.stringify(state.idToken) && JSON.stringify(prevState.accessToken) === JSON.stringify(state.accessToken) && prevState.error === state.error;
};
class AuthStateManager {
constructor(sdk) {
if (!sdk.emitter) {
throw new _errors.AuthSdkError('Emitter should be initialized before AuthStateManager');
}
this._sdk = sdk;
this._pending = { ...DEFAULT_PENDING
};
this._authState = INITIAL_AUTH_STATE;
this._logOptions = {};
this._prevAuthState = null;
this._transformQueue = new _util.PromiseQueue({
quiet: true
}); // Listen on tokenManager events to start updateState process
// "added" event is emitted in both add and renew process
// Only listen on "added" event to update auth state
sdk.tokenManager.on(_oidc.EVENT_ADDED, (key, token) => {
this._setLogOptions({
event: _oidc.EVENT_ADDED,
key,
token
});
this.updateAuthState();
});
sdk.tokenManager.on(_oidc.EVENT_REMOVED, (key, token) => {
this._setLogOptions({
event: _oidc.EVENT_REMOVED,
key,
token
});
this.updateAuthState();
});
}
_setLogOptions(options) {
this._logOptions = options;
}
getAuthState() {
return this._authState;
}
getPreviousAuthState() {
return this._prevAuthState;
}
async updateAuthState() {
const {
transformAuthState,
devMode
} = this._sdk.options;
const log = status => {
const {
event,
key,
token
} = this._logOptions;
(0, _util.getConsole)().group(`OKTA-AUTH-JS:updateAuthState: Event:${event} Status:${status}`);
(0, _util.getConsole)().log(key, token);
(0, _util.getConsole)().log('Current authState', this._authState);
(0, _util.getConsole)().groupEnd(); // clear log options after logging
this._logOptions = {};
};
const emitAuthStateChange = authState => {
if (isSameAuthState(this._authState, authState)) {
devMode && log('unchanged');
return;
}
this._prevAuthState = this._authState;
this._authState = authState; // emit new authState object
this._sdk.emitter.emit(EVENT_AUTH_STATE_CHANGE, { ...authState
});
devMode && log('emitted');
};
const finalPromise = origPromise => {
return this._pending.updateAuthStatePromise.then(() => {
const curPromise = this._pending.updateAuthStatePromise;
if (curPromise && curPromise !== origPromise) {
return finalPromise(curPromise);
}
return this.getAuthState();
});
};
if (this._pending.updateAuthStatePromise) {
if (this._pending.canceledTimes >= MAX_PROMISE_CANCEL_TIMES) {
// stop canceling then starting a new promise
// let existing promise finish to prevent running into loops
devMode && log('terminated');
return finalPromise(this._pending.updateAuthStatePromise);
} else {
this._pending.updateAuthStatePromise.cancel();
}
}
/* eslint-disable complexity */
const cancelablePromise = new _pCancelable.default((resolve, _, onCancel) => {
onCancel.shouldReject = false;
onCancel(() => {
this._pending.updateAuthStatePromise = null;
this._pending.canceledTimes = this._pending.canceledTimes + 1;
devMode && log('canceled');
});
const emitAndResolve = authState => {
if (cancelablePromise.isCanceled) {
resolve();
return;
} // emit event and resolve promise
emitAuthStateChange(authState);
resolve(); // clear pending states after resolve
this._pending = { ...DEFAULT_PENDING
};
};
this._sdk.isAuthenticated().then(() => {
if (cancelablePromise.isCanceled) {
resolve();
return;
}
const {
accessToken,
idToken,
refreshToken
} = this._sdk.tokenManager.getTokensSync();
const authState = {
accessToken,
idToken,
refreshToken,
isAuthenticated: !!(accessToken && idToken)
}; // Enqueue transformAuthState so that it does not run concurrently
const promise = transformAuthState ? this._transformQueue.push(transformAuthState, null, this._sdk, authState) : Promise.resolve(authState);
promise.then(authState => emitAndResolve(authState)).catch(error => emitAndResolve({
accessToken,
idToken,
refreshToken,
isAuthenticated: false,
error
}));
});
});
/* eslint-enable complexity */
this._pending.updateAuthStatePromise = cancelablePromise;
return finalPromise(cancelablePromise);
}
subscribe(handler) {
this._sdk.emitter.on(EVENT_AUTH_STATE_CHANGE, handler);
}
unsubscribe(handler) {
this._sdk.emitter.off(EVENT_AUTH_STATE_CHANGE, handler);
}
}
exports.AuthStateManager = AuthStateManager;
//# sourceMappingURL=AuthStateManager.js.map