LookAtMySuitBot/js/node_modules/@azure/msal-common/dist/response/ResponseHandler.mjs

334 lines
18 KiB
JavaScript

/*! @azure/msal-common v14.4.0 2023-11-07 */
'use strict';
import { createClientAuthError } from '../error/ClientAuthError.mjs';
import { ServerError } from '../error/ServerError.mjs';
import { ScopeSet } from '../request/ScopeSet.mjs';
import { AccountEntity } from '../cache/entities/AccountEntity.mjs';
import { isInteractionRequiredError, InteractionRequiredAuthError } from '../error/InteractionRequiredAuthError.mjs';
import { CacheRecord } from '../cache/entities/CacheRecord.mjs';
import { ProtocolUtils } from '../utils/ProtocolUtils.mjs';
import { HttpStatus, Constants, AuthenticationScheme, THE_FAMILY_ID } from '../utils/Constants.mjs';
import { PopTokenGenerator } from '../crypto/PopTokenGenerator.mjs';
import { AppMetadataEntity } from '../cache/entities/AppMetadataEntity.mjs';
import { TokenCacheContext } from '../cache/persistence/TokenCacheContext.mjs';
import { PerformanceEvents } from '../telemetry/performance/PerformanceEvent.mjs';
import { extractTokenClaims, checkMaxAge } from '../account/AuthToken.mjs';
import { createAccessTokenEntity, createRefreshTokenEntity, createIdTokenEntity } from '../cache/utils/CacheHelpers.mjs';
import { stateNotFound, invalidState, stateMismatch, nonceMismatch, authTimeNotFound, invalidCacheEnvironment, keyIdMissing } from '../error/ClientAuthErrorCodes.mjs';
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Class that handles response parsing.
* @internal
*/
class ResponseHandler {
constructor(clientId, cacheStorage, cryptoObj, logger, serializableCache, persistencePlugin, performanceClient) {
this.clientId = clientId;
this.cacheStorage = cacheStorage;
this.cryptoObj = cryptoObj;
this.logger = logger;
this.serializableCache = serializableCache;
this.persistencePlugin = persistencePlugin;
this.performanceClient = performanceClient;
}
/**
* Function which validates server authorization code response.
* @param serverResponseHash
* @param requestState
* @param cryptoObj
*/
validateServerAuthorizationCodeResponse(serverResponse, requestState) {
if (!serverResponse.state || !requestState) {
throw serverResponse.state
? createClientAuthError(stateNotFound, "Cached State")
: createClientAuthError(stateNotFound, "Server State");
}
let decodedServerResponseState;
let decodedRequestState;
try {
decodedServerResponseState = decodeURIComponent(serverResponse.state);
}
catch (e) {
throw createClientAuthError(invalidState, serverResponse.state);
}
try {
decodedRequestState = decodeURIComponent(requestState);
}
catch (e) {
throw createClientAuthError(invalidState, serverResponse.state);
}
if (decodedServerResponseState !== decodedRequestState) {
throw createClientAuthError(stateMismatch);
}
// Check for error
if (serverResponse.error ||
serverResponse.error_description ||
serverResponse.suberror) {
if (isInteractionRequiredError(serverResponse.error, serverResponse.error_description, serverResponse.suberror)) {
throw new InteractionRequiredAuthError(serverResponse.error || "", serverResponse.error_description, serverResponse.suberror, serverResponse.timestamp || "", serverResponse.trace_id || "", serverResponse.correlation_id || "", serverResponse.claims || "");
}
throw new ServerError(serverResponse.error || "", serverResponse.error_description, serverResponse.suberror);
}
}
/**
* Function which validates server authorization token response.
* @param serverResponse
* @param refreshAccessToken
*/
validateTokenResponse(serverResponse, refreshAccessToken) {
// Check for error
if (serverResponse.error ||
serverResponse.error_description ||
serverResponse.suberror) {
const errString = `${serverResponse.error_codes} - [${serverResponse.timestamp}]: ${serverResponse.error_description} - Correlation ID: ${serverResponse.correlation_id} - Trace ID: ${serverResponse.trace_id}`;
const serverError = new ServerError(serverResponse.error, errString, serverResponse.suberror);
// check if 500 error
if (refreshAccessToken &&
serverResponse.status &&
serverResponse.status >= HttpStatus.SERVER_ERROR_RANGE_START &&
serverResponse.status <= HttpStatus.SERVER_ERROR_RANGE_END) {
this.logger.warning(`executeTokenRequest:validateTokenResponse - AAD is currently unavailable and the access token is unable to be refreshed.\n${serverError}`);
// don't throw an exception, but alert the user via a log that the token was unable to be refreshed
return;
// check if 400 error
}
else if (refreshAccessToken &&
serverResponse.status &&
serverResponse.status >= HttpStatus.CLIENT_ERROR_RANGE_START &&
serverResponse.status <= HttpStatus.CLIENT_ERROR_RANGE_END) {
this.logger.warning(`executeTokenRequest:validateTokenResponse - AAD is currently available but is unable to refresh the access token.\n${serverError}`);
// don't throw an exception, but alert the user via a log that the token was unable to be refreshed
return;
}
if (isInteractionRequiredError(serverResponse.error, serverResponse.error_description, serverResponse.suberror)) {
throw new InteractionRequiredAuthError(serverResponse.error, serverResponse.error_description, serverResponse.suberror, serverResponse.timestamp || Constants.EMPTY_STRING, serverResponse.trace_id || Constants.EMPTY_STRING, serverResponse.correlation_id || Constants.EMPTY_STRING, serverResponse.claims || Constants.EMPTY_STRING);
}
throw serverError;
}
}
/**
* Returns a constructed token response based on given string. Also manages the cache updates and cleanups.
* @param serverTokenResponse
* @param authority
*/
async handleServerTokenResponse(serverTokenResponse, authority, reqTimestamp, request, authCodePayload, userAssertionHash, handlingRefreshTokenResponse, forceCacheRefreshTokenResponse, serverRequestId) {
this.performanceClient?.addQueueMeasurement(PerformanceEvents.HandleServerTokenResponse, serverTokenResponse.correlation_id);
// create an idToken object (not entity)
let idTokenClaims;
if (serverTokenResponse.id_token) {
idTokenClaims = extractTokenClaims(serverTokenResponse.id_token || Constants.EMPTY_STRING, this.cryptoObj.base64Decode);
// token nonce check (TODO: Add a warning if no nonce is given?)
if (authCodePayload && authCodePayload.nonce) {
if (idTokenClaims.nonce !== authCodePayload.nonce) {
throw createClientAuthError(nonceMismatch);
}
}
// token max_age check
if (request.maxAge || request.maxAge === 0) {
const authTime = idTokenClaims.auth_time;
if (!authTime) {
throw createClientAuthError(authTimeNotFound);
}
checkMaxAge(authTime, request.maxAge);
}
}
// generate homeAccountId
this.homeAccountIdentifier = AccountEntity.generateHomeAccountId(serverTokenResponse.client_info || Constants.EMPTY_STRING, authority.authorityType, this.logger, this.cryptoObj, idTokenClaims);
// save the response tokens
let requestStateObj;
if (!!authCodePayload && !!authCodePayload.state) {
requestStateObj = ProtocolUtils.parseRequestState(this.cryptoObj, authCodePayload.state);
}
// Add keyId from request to serverTokenResponse if defined
serverTokenResponse.key_id =
serverTokenResponse.key_id || request.sshKid || undefined;
const cacheRecord = this.generateCacheRecord(serverTokenResponse, authority, reqTimestamp, request, idTokenClaims, userAssertionHash, authCodePayload);
let cacheContext;
try {
if (this.persistencePlugin && this.serializableCache) {
this.logger.verbose("Persistence enabled, calling beforeCacheAccess");
cacheContext = new TokenCacheContext(this.serializableCache, true);
await this.persistencePlugin.beforeCacheAccess(cacheContext);
}
/*
* When saving a refreshed tokens to the cache, it is expected that the account that was used is present in the cache.
* If not present, we should return null, as it's the case that another application called removeAccount in between
* the calls to getAllAccounts and acquireTokenSilent. We should not overwrite that removal, unless explicitly flagged by
* the developer, as in the case of refresh token flow used in ADAL Node to MSAL Node migration.
*/
if (handlingRefreshTokenResponse &&
!forceCacheRefreshTokenResponse &&
cacheRecord.account) {
const key = cacheRecord.account.generateAccountKey();
const account = this.cacheStorage.getAccount(key);
if (!account) {
this.logger.warning("Account used to refresh tokens not in persistence, refreshed tokens will not be stored in the cache");
return ResponseHandler.generateAuthenticationResult(this.cryptoObj, authority, cacheRecord, false, request, idTokenClaims, requestStateObj, undefined, serverRequestId);
}
}
await this.cacheStorage.saveCacheRecord(cacheRecord, request.storeInCache);
}
finally {
if (this.persistencePlugin &&
this.serializableCache &&
cacheContext) {
this.logger.verbose("Persistence enabled, calling afterCacheAccess");
await this.persistencePlugin.afterCacheAccess(cacheContext);
}
}
return ResponseHandler.generateAuthenticationResult(this.cryptoObj, authority, cacheRecord, false, request, idTokenClaims, requestStateObj, serverTokenResponse, serverRequestId);
}
/**
* Generates CacheRecord
* @param serverTokenResponse
* @param idTokenObj
* @param authority
*/
generateCacheRecord(serverTokenResponse, authority, reqTimestamp, request, idTokenClaims, userAssertionHash, authCodePayload) {
const env = authority.getPreferredCache();
if (!env) {
throw createClientAuthError(invalidCacheEnvironment);
}
// IdToken: non AAD scenarios can have empty realm
let cachedIdToken;
let cachedAccount;
if (serverTokenResponse.id_token && !!idTokenClaims) {
cachedIdToken = createIdTokenEntity(this.homeAccountIdentifier, env, serverTokenResponse.id_token, this.clientId, idTokenClaims.tid || "");
cachedAccount = AccountEntity.createAccount({
homeAccountId: this.homeAccountIdentifier,
idTokenClaims: idTokenClaims,
clientInfo: serverTokenResponse.client_info,
cloudGraphHostName: authCodePayload?.cloud_graph_host_name,
msGraphHost: authCodePayload?.msgraph_host,
}, authority);
}
// AccessToken
let cachedAccessToken = null;
if (serverTokenResponse.access_token) {
// If scopes not returned in server response, use request scopes
const responseScopes = serverTokenResponse.scope
? ScopeSet.fromString(serverTokenResponse.scope)
: new ScopeSet(request.scopes || []);
/*
* Use timestamp calculated before request
* Server may return timestamps as strings, parse to numbers if so.
*/
const expiresIn = (typeof serverTokenResponse.expires_in === "string"
? parseInt(serverTokenResponse.expires_in, 10)
: serverTokenResponse.expires_in) || 0;
const extExpiresIn = (typeof serverTokenResponse.ext_expires_in === "string"
? parseInt(serverTokenResponse.ext_expires_in, 10)
: serverTokenResponse.ext_expires_in) || 0;
const refreshIn = (typeof serverTokenResponse.refresh_in === "string"
? parseInt(serverTokenResponse.refresh_in, 10)
: serverTokenResponse.refresh_in) || undefined;
const tokenExpirationSeconds = reqTimestamp + expiresIn;
const extendedTokenExpirationSeconds = tokenExpirationSeconds + extExpiresIn;
const refreshOnSeconds = refreshIn && refreshIn > 0
? reqTimestamp + refreshIn
: undefined;
// non AAD scenarios can have empty realm
cachedAccessToken = createAccessTokenEntity(this.homeAccountIdentifier, env, serverTokenResponse.access_token, this.clientId, idTokenClaims?.tid || authority.tenant, responseScopes.printScopes(), tokenExpirationSeconds, extendedTokenExpirationSeconds, this.cryptoObj.base64Decode, refreshOnSeconds, serverTokenResponse.token_type, userAssertionHash, serverTokenResponse.key_id, request.claims, request.requestedClaimsHash);
}
// refreshToken
let cachedRefreshToken = null;
if (serverTokenResponse.refresh_token) {
cachedRefreshToken = createRefreshTokenEntity(this.homeAccountIdentifier, env, serverTokenResponse.refresh_token, this.clientId, serverTokenResponse.foci, userAssertionHash);
}
// appMetadata
let cachedAppMetadata = null;
if (serverTokenResponse.foci) {
cachedAppMetadata = AppMetadataEntity.createAppMetadataEntity(this.clientId, env, serverTokenResponse.foci);
}
return new CacheRecord(cachedAccount, cachedIdToken, cachedAccessToken, cachedRefreshToken, cachedAppMetadata);
}
/**
* Creates an @AuthenticationResult from @CacheRecord , @IdToken , and a boolean that states whether or not the result is from cache.
*
* Optionally takes a state string that is set as-is in the response.
*
* @param cacheRecord
* @param idTokenObj
* @param fromTokenCache
* @param stateString
*/
static async generateAuthenticationResult(cryptoObj, authority, cacheRecord, fromTokenCache, request, idTokenClaims, requestState, serverTokenResponse, requestId) {
let accessToken = Constants.EMPTY_STRING;
let responseScopes = [];
let expiresOn = null;
let extExpiresOn;
let refreshOn;
let familyId = Constants.EMPTY_STRING;
if (cacheRecord.accessToken) {
if (cacheRecord.accessToken.tokenType === AuthenticationScheme.POP) {
const popTokenGenerator = new PopTokenGenerator(cryptoObj);
const { secret, keyId } = cacheRecord.accessToken;
if (!keyId) {
throw createClientAuthError(keyIdMissing);
}
accessToken = await popTokenGenerator.signPopToken(secret, keyId, request);
}
else {
accessToken = cacheRecord.accessToken.secret;
}
responseScopes = ScopeSet.fromString(cacheRecord.accessToken.target).asArray();
expiresOn = new Date(Number(cacheRecord.accessToken.expiresOn) * 1000);
extExpiresOn = new Date(Number(cacheRecord.accessToken.extendedExpiresOn) * 1000);
if (cacheRecord.accessToken.refreshOn) {
refreshOn = new Date(Number(cacheRecord.accessToken.refreshOn) * 1000);
}
}
if (cacheRecord.appMetadata) {
familyId =
cacheRecord.appMetadata.familyId === THE_FAMILY_ID
? THE_FAMILY_ID
: "";
}
const uid = idTokenClaims?.oid || idTokenClaims?.sub || "";
const tid = idTokenClaims?.tid || "";
// for hybrid + native bridge enablement, send back the native account Id
if (serverTokenResponse?.spa_accountid && !!cacheRecord.account) {
cacheRecord.account.nativeAccountId =
serverTokenResponse?.spa_accountid;
}
const accountInfo = cacheRecord.account
? {
...cacheRecord.account.getAccountInfo(),
idTokenClaims,
}
: null;
return {
authority: authority.canonicalAuthority,
uniqueId: uid,
tenantId: tid,
scopes: responseScopes,
account: accountInfo,
idToken: cacheRecord?.idToken?.secret || "",
idTokenClaims: idTokenClaims || {},
accessToken: accessToken,
fromCache: fromTokenCache,
expiresOn: expiresOn,
extExpiresOn: extExpiresOn,
refreshOn: refreshOn,
correlationId: request.correlationId,
requestId: requestId || Constants.EMPTY_STRING,
familyId: familyId,
tokenType: cacheRecord.accessToken?.tokenType || Constants.EMPTY_STRING,
state: requestState
? requestState.userRequestState
: Constants.EMPTY_STRING,
cloudGraphHostName: cacheRecord.account?.cloudGraphHostName ||
Constants.EMPTY_STRING,
msGraphHost: cacheRecord.account?.msGraphHost || Constants.EMPTY_STRING,
code: serverTokenResponse?.spa_code,
fromNativeBroker: false,
};
}
}
export { ResponseHandler };
//# sourceMappingURL=ResponseHandler.mjs.map