@okta/okta-auth-js
Version:
The Okta Auth SDK
373 lines (362 loc) • 14.2 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.mixinOAuth = mixinOAuth;
var _http = require("../../http");
var _util = require("../../util");
var crypto = _interopRequireWildcard(require("../../crypto"));
var _pkce = _interopRequireDefault(require("../util/pkce"));
var _api = require("../factory/api");
var _TokenManager = require("../TokenManager");
var _util2 = require("../util");
var _dpop = require("../dpop");
var _errors = require("../../errors");
var _node = require("./node");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
function mixinOAuth(Base, TransactionManagerConstructor) {
const WithOriginalUri = (0, _node.provideOriginalUri)(Base);
return class OktaAuthOAuth extends WithOriginalUri {
static crypto = crypto;
constructor(...args) {
super(...args);
this.transactionManager = new TransactionManagerConstructor(Object.assign({
storageManager: this.storageManager
}, this.options.transactionManager));
this.pkce = {
DEFAULT_CODE_CHALLENGE_METHOD: _pkce.default.DEFAULT_CODE_CHALLENGE_METHOD,
generateVerifier: _pkce.default.generateVerifier,
computeChallenge: _pkce.default.computeChallenge
};
this._pending = {
handleLogin: false
};
this._tokenQueue = new _util.PromiseQueue();
this.token = (0, _api.createTokenAPI)(this, this._tokenQueue);
// TokenManager
this.tokenManager = new _TokenManager.TokenManager(this, this.options.tokenManager);
this.endpoints = (0, _api.createEndpoints)(this);
}
// inherited from subclass
clearStorage() {
super.clearStorage();
// Clear all local tokens
this.tokenManager.clear();
}
// Returns true if both accessToken and idToken are not expired
// If `autoRenew` option is set, will attempt to renew expired tokens before returning.
// eslint-disable-next-line complexity
async isAuthenticated(options = {}) {
// TODO: remove dependency on tokenManager options in next major version - OKTA-473815
const {
autoRenew,
autoRemove
} = this.tokenManager.getOptions();
const shouldRenew = options.onExpiredToken ? options.onExpiredToken === 'renew' : autoRenew;
const shouldRemove = options.onExpiredToken ? options.onExpiredToken === 'remove' : autoRemove;
let {
accessToken
} = this.tokenManager.getTokensSync();
if (accessToken && this.tokenManager.hasExpired(accessToken)) {
accessToken = undefined;
if (shouldRenew) {
try {
accessToken = await this.tokenManager.renew('accessToken');
} catch {
// Renew errors will emit an "error" event
}
} else if (shouldRemove) {
this.tokenManager.remove('accessToken');
}
}
let {
idToken
} = this.tokenManager.getTokensSync();
if (idToken && this.tokenManager.hasExpired(idToken)) {
idToken = undefined;
if (shouldRenew) {
try {
idToken = await this.tokenManager.renew('idToken');
} catch {
// Renew errors will emit an "error" event
}
} else if (shouldRemove) {
this.tokenManager.remove('idToken');
}
}
return !!(accessToken && idToken);
}
async signInWithRedirect(opts = {}) {
const {
originalUri,
...additionalParams
} = opts;
if (this._pending.handleLogin) {
// Don't trigger second round
return;
}
this._pending.handleLogin = true;
try {
// Trigger default signIn redirect flow
if (originalUri) {
this.setOriginalUri(originalUri);
}
const params = Object.assign({
// TODO: remove this line when default scopes are changed OKTA-343294
scopes: this.options.scopes || ['openid', 'email', 'profile']
}, additionalParams);
await this.token.getWithRedirect(params);
} finally {
this._pending.handleLogin = false;
}
}
async getUser() {
const {
idToken,
accessToken
} = this.tokenManager.getTokensSync();
return this.token.getUserInfo(accessToken, idToken);
}
getIdToken() {
const {
idToken
} = this.tokenManager.getTokensSync();
return idToken ? idToken.idToken : undefined;
}
getAccessToken() {
const {
accessToken
} = this.tokenManager.getTokensSync();
return accessToken ? accessToken.accessToken : undefined;
}
getRefreshToken() {
const {
refreshToken
} = this.tokenManager.getTokensSync();
return refreshToken ? refreshToken.refreshToken : undefined;
}
async getOrRenewAccessToken() {
const {
accessToken
} = this.tokenManager.getTokensSync();
if (accessToken && !this.tokenManager.hasExpired(accessToken)) {
return accessToken.accessToken;
}
try {
const key = this.tokenManager.getStorageKeyByType('accessToken');
const token = await this.tokenManager.renew(key ?? 'accessToken');
return token?.accessToken ?? null;
} catch (err) {
this.emitter.emit('error', err);
return null;
}
}
/**
* Store parsed tokens from redirect url
*/
async storeTokensFromRedirect() {
const {
tokens,
responseType
} = await this.token.parseFromUrl();
if (responseType !== 'none') {
this.tokenManager.setTokens(tokens);
}
}
isLoginRedirect() {
return (0, _util2.isLoginRedirect)(this);
}
isPKCE() {
return !!this.options.pkce;
}
hasResponseType(responseType) {
return (0, _util2.hasResponseType)(responseType, this.options);
}
isAuthorizationCodeFlow() {
return this.hasResponseType('code');
}
// Escape hatch method to make arbitrary OKTA API call
async invokeApiMethod(options) {
if (!options.accessToken) {
const accessToken = (await this.tokenManager.getTokens()).accessToken;
options.accessToken = accessToken?.accessToken;
}
return (0, _http.httpRequest)(this, options);
}
// Revokes the access token for the application session
async revokeAccessToken(accessToken) {
if (!accessToken) {
const tokens = await this.tokenManager.getTokens();
accessToken = tokens.accessToken;
const accessTokenKey = this.tokenManager.getStorageKeyByType('accessToken');
this.tokenManager.remove(accessTokenKey);
if (this.options.dpop) {
await (0, _dpop.clearDPoPKeyPairAfterRevoke)('access', tokens);
}
}
// Access token may have been removed. In this case, we will silently succeed.
if (!accessToken) {
return Promise.resolve(null);
}
return this.token.revoke(accessToken);
}
// Revokes the refresh token for the application session
async revokeRefreshToken(refreshToken) {
if (!refreshToken) {
const tokens = await this.tokenManager.getTokens();
refreshToken = tokens.refreshToken;
const refreshTokenKey = this.tokenManager.getStorageKeyByType('refreshToken');
this.tokenManager.remove(refreshTokenKey);
if (this.options.dpop) {
await (0, _dpop.clearDPoPKeyPairAfterRevoke)('refresh', tokens);
}
}
// Refresh token may have been removed. In this case, we will silently succeed.
if (!refreshToken) {
return Promise.resolve(null);
}
return this.token.revoke(refreshToken);
}
getSignOutRedirectUrl(options = {}) {
let {
idToken,
postLogoutRedirectUri,
state
} = options;
if (!idToken) {
idToken = this.tokenManager.getTokensSync().idToken;
}
if (!idToken) {
return '';
}
if (postLogoutRedirectUri === undefined) {
postLogoutRedirectUri = this.options.postLogoutRedirectUri;
}
const logoutUrl = (0, _util2.getOAuthUrls)(this).logoutUrl;
const idTokenHint = idToken.idToken; // a string
let logoutUri = logoutUrl + '?id_token_hint=' + encodeURIComponent(idTokenHint);
if (postLogoutRedirectUri) {
logoutUri += '&post_logout_redirect_uri=' + encodeURIComponent(postLogoutRedirectUri);
}
// State allows option parameters to be passed to logout redirect uri
if (state) {
logoutUri += '&state=' + encodeURIComponent(state);
}
return logoutUri;
}
// Revokes refreshToken or accessToken, clears all local tokens, then redirects to Okta to end the SSO session.
// eslint-disable-next-line complexity, max-statements
async signOut(options) {
options = Object.assign({}, options);
// postLogoutRedirectUri must be whitelisted in Okta Admin UI
const defaultUri = window.location.origin;
const currentUri = window.location.href;
// Fix for issue/1410 - allow for no postLogoutRedirectUri to be passed, resulting in /logout default behavior
// "If no Okta session exists, this endpoint has no effect and the browser is redirected immediately to the
// Okta sign-in page or the post_logout_redirect_uri (if specified)."
// - https://developer.okta.com/docs/reference/api/oidc/#logout
const postLogoutRedirectUri = options.postLogoutRedirectUri === null ? null : options.postLogoutRedirectUri || this.options.postLogoutRedirectUri || defaultUri;
const state = options?.state;
let accessToken = options.accessToken;
let refreshToken = options.refreshToken;
const revokeAccessToken = options.revokeAccessToken !== false;
const revokeRefreshToken = options.revokeRefreshToken !== false;
if (revokeRefreshToken && typeof refreshToken === 'undefined') {
refreshToken = this.tokenManager.getTokensSync().refreshToken;
}
if (revokeAccessToken && typeof accessToken === 'undefined') {
accessToken = this.tokenManager.getTokensSync().accessToken;
}
if (!options.idToken) {
options.idToken = this.tokenManager.getTokensSync().idToken;
}
if (revokeRefreshToken && refreshToken) {
await this.revokeRefreshToken(refreshToken);
}
if (revokeAccessToken && accessToken) {
await this.revokeAccessToken(accessToken);
}
const dpopPairId = accessToken?.dpopPairId ?? refreshToken?.dpopPairId;
if (this.options.dpop && dpopPairId) {
await (0, _dpop.clearDPoPKeyPair)(dpopPairId);
}
const logoutUri = this.getSignOutRedirectUrl({
...options,
postLogoutRedirectUri
});
// No logoutUri? This can happen if the storage was cleared.
// Fallback to XHR signOut, then simulate a redirect to the post logout uri
if (!logoutUri) {
// local tokens are cleared once session is closed
const sessionClosed = await this.closeSession(); // can throw if the user cannot be signed out
const redirectUri = new URL(postLogoutRedirectUri || defaultUri); // during fallback, redirectUri cannot be null
if (state) {
redirectUri.searchParams.append('state', state);
}
if (postLogoutRedirectUri === currentUri) {
// window.location.reload(); // force a hard reload if URI is not changing
window.location.href = redirectUri.href;
} else {
window.location.assign(redirectUri.href);
}
return sessionClosed;
} else {
if (options.clearTokensBeforeRedirect) {
// Clear all local tokens
this.tokenManager.clear();
} else {
this.tokenManager.addPendingRemoveFlags();
}
// Flow ends with logout redirect
window.location.assign(logoutUri);
return true;
}
}
async getDPoPAuthorizationHeaders(params) {
if (!this.options.dpop) {
throw new _errors.AuthSdkError('DPoP is not configured for this client instance');
}
let {
accessToken
} = params;
if (!accessToken) {
accessToken = this.tokenManager.getTokensSync().accessToken;
}
if (!accessToken) {
throw new _errors.AuthSdkError('AccessToken is required to generate a DPoP Proof');
}
const keyPair = await (0, _dpop.findKeyPair)(accessToken?.dpopPairId);
const proof = await (0, _dpop.generateDPoPProof)({
...params,
keyPair,
accessToken: accessToken.accessToken
});
return {
Authorization: `DPoP ${accessToken.accessToken}`,
Dpop: proof
};
}
async clearDPoPStorage(clearAll = false) {
if (clearAll) {
return (0, _dpop.clearAllDPoPKeyPairs)();
}
const tokens = await this.tokenManager.getTokens();
const keyPair = tokens.accessToken?.dpopPairId || tokens.refreshToken?.dpopPairId;
if (keyPair) {
await (0, _dpop.clearDPoPKeyPair)(keyPair);
}
}
parseUseDPoPNonceError(headers) {
const wwwAuth = _errors.WWWAuthError.getWWWAuthenticateHeader(headers);
const wwwErr = _errors.WWWAuthError.parseHeader(wwwAuth ?? '');
if ((0, _dpop.isDPoPNonceError)(wwwErr)) {
let nonce = null;
if ((0, _util.isFunction)(headers?.get)) {
nonce = headers.get('DPoP-Nonce');
}
nonce = nonce ?? headers['dpop-nonce'] ?? headers['DPoP-Nonce'];
return nonce;
}
return null;
}
};
}
//# sourceMappingURL=index.js.map