Estructura inicial del proyecto
This commit is contained in:
366
backend/node_modules/@azure/msal-common/dist/response/ResponseHandler.mjs
generated
vendored
Normal file
366
backend/node_modules/@azure/msal-common/dist/response/ResponseHandler.mjs
generated
vendored
Normal file
@ -0,0 +1,366 @@
|
||||
/*! @azure/msal-common v16.6.2 2026-05-19 */
|
||||
'use strict';
|
||||
import { updateAccountTenantProfileData, buildTenantProfile } from '../account/AccountInfo.mjs';
|
||||
import { extractTokenClaims, checkMaxAge, isKmsi } from '../account/AuthToken.mjs';
|
||||
import { getTenantIdFromIdTokenClaims } from '../account/TokenClaims.mjs';
|
||||
import { TokenCacheContext } from '../cache/persistence/TokenCacheContext.mjs';
|
||||
import { generateHomeAccountId, getAccountInfo, createAccountEntity } from '../cache/utils/AccountEntityUtils.mjs';
|
||||
import { createIdTokenEntity, createAccessTokenEntity, createRefreshTokenEntity } from '../cache/utils/CacheHelpers.mjs';
|
||||
import { PopTokenGenerator } from '../crypto/PopTokenGenerator.mjs';
|
||||
import { createClientAuthError } from '../error/ClientAuthError.mjs';
|
||||
import { isInteractionRequiredError, InteractionRequiredAuthError } from '../error/InteractionRequiredAuthError.mjs';
|
||||
import { ServerError } from '../error/ServerError.mjs';
|
||||
import { ScopeSet } from '../request/ScopeSet.mjs';
|
||||
import { NOT_AVAILABLE, HTTP_SERVER_ERROR_RANGE_START, HTTP_SERVER_ERROR_RANGE_END, HTTP_CLIENT_ERROR_RANGE_START, HTTP_CLIENT_ERROR_RANGE_END, AuthenticationScheme, THE_FAMILY_ID } from '../utils/Constants.mjs';
|
||||
import { parseRequestState } from '../utils/ProtocolUtils.mjs';
|
||||
import { toDateFromSeconds } from '../utils/TimeUtils.mjs';
|
||||
import { 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, performanceClient, serializableCache, persistencePlugin) {
|
||||
this.clientId = clientId;
|
||||
this.cacheStorage = cacheStorage;
|
||||
this.cryptoObj = cryptoObj;
|
||||
this.logger = logger;
|
||||
this.performanceClient = performanceClient;
|
||||
this.serializableCache = serializableCache;
|
||||
this.persistencePlugin = persistencePlugin;
|
||||
}
|
||||
/**
|
||||
* Function which validates server authorization token response.
|
||||
* @param serverResponse
|
||||
* @param correlationId
|
||||
* @param refreshAccessToken
|
||||
*/
|
||||
validateTokenResponse(serverResponse, correlationId, refreshAccessToken) {
|
||||
// Check for error
|
||||
if (serverResponse.error ||
|
||||
serverResponse.error_description ||
|
||||
serverResponse.suberror) {
|
||||
const errString = `Error(s): ${serverResponse.error_codes || NOT_AVAILABLE} - Timestamp: ${serverResponse.timestamp || NOT_AVAILABLE} - Description: ${serverResponse.error_description || NOT_AVAILABLE} - Correlation ID: ${serverResponse.correlation_id || NOT_AVAILABLE} - Trace ID: ${serverResponse.trace_id || NOT_AVAILABLE}`;
|
||||
const serverErrorNo = serverResponse.error_codes?.length
|
||||
? serverResponse.error_codes[0]
|
||||
: undefined;
|
||||
const serverError = new ServerError(serverResponse.error, errString, serverResponse.suberror, serverErrorNo, serverResponse.status);
|
||||
// check if 500 error
|
||||
if (refreshAccessToken &&
|
||||
serverResponse.status &&
|
||||
serverResponse.status >=
|
||||
HTTP_SERVER_ERROR_RANGE_START &&
|
||||
serverResponse.status <= HTTP_SERVER_ERROR_RANGE_END) {
|
||||
this.logger.warning(`executeTokenRequest:validateTokenResponse - AAD is currently unavailable and the access token is unable to be refreshed.\n${serverError}`, correlationId);
|
||||
// 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 >=
|
||||
HTTP_CLIENT_ERROR_RANGE_START &&
|
||||
serverResponse.status <= HTTP_CLIENT_ERROR_RANGE_END) {
|
||||
this.logger.warning(`executeTokenRequest:validateTokenResponse - AAD is currently available but is unable to refresh the access token.\n${serverError}`, correlationId);
|
||||
// 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 || "", serverResponse.trace_id || "", serverResponse.correlation_id || "", serverResponse.claims || "", serverErrorNo);
|
||||
}
|
||||
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, apiId, authCodePayload, userAssertionHash, handlingRefreshTokenResponse, forceCacheRefreshTokenResponse, serverRequestId) {
|
||||
// create an idToken object (not entity)
|
||||
let idTokenClaims;
|
||||
if (serverTokenResponse.id_token) {
|
||||
idTokenClaims = extractTokenClaims(serverTokenResponse.id_token || "", 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 = generateHomeAccountId(serverTokenResponse.client_info || "", authority.authorityType, this.logger, this.cryptoObj, request.correlationId, idTokenClaims);
|
||||
// save the response tokens
|
||||
let requestStateObj;
|
||||
if (!!authCodePayload && !!authCodePayload.state) {
|
||||
requestStateObj = parseRequestState(this.cryptoObj.base64Decode, 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", request.correlationId);
|
||||
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 cachedAccounts = this.cacheStorage.getAllAccounts({
|
||||
homeAccountId: cacheRecord.account.homeAccountId,
|
||||
environment: cacheRecord.account.environment,
|
||||
}, request.correlationId);
|
||||
if (cachedAccounts.length < 1) {
|
||||
this.logger.warning("Account used to refresh tokens not in persistence, refreshed tokens will not be stored in the cache", request.correlationId);
|
||||
this.performanceClient?.addFields({
|
||||
acntLoggedOut: true,
|
||||
}, request.correlationId);
|
||||
return await ResponseHandler.generateAuthenticationResult(this.cryptoObj, authority, cacheRecord, false, request, this.performanceClient, idTokenClaims, requestStateObj, undefined, serverRequestId);
|
||||
}
|
||||
}
|
||||
await this.cacheStorage.saveCacheRecord(cacheRecord, request.correlationId, isKmsi(idTokenClaims || {}), apiId, request.storeInCache);
|
||||
}
|
||||
finally {
|
||||
if (this.persistencePlugin &&
|
||||
this.serializableCache &&
|
||||
cacheContext) {
|
||||
this.logger.verbose("Persistence enabled, calling afterCacheAccess", request.correlationId);
|
||||
await this.persistencePlugin.afterCacheAccess(cacheContext);
|
||||
}
|
||||
}
|
||||
return ResponseHandler.generateAuthenticationResult(this.cryptoObj, authority, cacheRecord, false, request, this.performanceClient, 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);
|
||||
}
|
||||
const claimsTenantId = getTenantIdFromIdTokenClaims(idTokenClaims);
|
||||
// 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, claimsTenantId || "");
|
||||
cachedAccount = buildAccountToCache(this.cacheStorage, authority, this.homeAccountIdentifier, this.cryptoObj.base64Decode, request.correlationId, idTokenClaims, serverTokenResponse.client_info, env, claimsTenantId, authCodePayload, undefined, // nativeAccountId
|
||||
this.logger, this.performanceClient);
|
||||
}
|
||||
// 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, claimsTenantId || authority.tenant || "", responseScopes.printScopes(), tokenExpirationSeconds, extendedTokenExpirationSeconds, this.cryptoObj.base64Decode, refreshOnSeconds, serverTokenResponse.token_type, userAssertionHash, serverTokenResponse.key_id);
|
||||
// Set resource (to be used for MCP scenarios)
|
||||
const resource = request.resource || null;
|
||||
if (resource) {
|
||||
cachedAccessToken.resource = resource;
|
||||
}
|
||||
}
|
||||
// refreshToken
|
||||
let cachedRefreshToken = null;
|
||||
if (serverTokenResponse.refresh_token) {
|
||||
let rtExpiresOn;
|
||||
if (serverTokenResponse.refresh_token_expires_in) {
|
||||
const rtExpiresIn = typeof serverTokenResponse.refresh_token_expires_in ===
|
||||
"string"
|
||||
? parseInt(serverTokenResponse.refresh_token_expires_in, 10)
|
||||
: serverTokenResponse.refresh_token_expires_in;
|
||||
rtExpiresOn = reqTimestamp + rtExpiresIn;
|
||||
this.performanceClient?.addFields({ ntwkRtExpiresOnSeconds: rtExpiresOn }, request.correlationId);
|
||||
}
|
||||
cachedRefreshToken = createRefreshTokenEntity(this.homeAccountIdentifier, env, serverTokenResponse.refresh_token, this.clientId, serverTokenResponse.foci, userAssertionHash, rtExpiresOn);
|
||||
}
|
||||
// appMetadata
|
||||
let cachedAppMetadata = null;
|
||||
if (serverTokenResponse.foci) {
|
||||
cachedAppMetadata = {
|
||||
clientId: this.clientId,
|
||||
environment: env,
|
||||
familyId: serverTokenResponse.foci,
|
||||
};
|
||||
}
|
||||
return {
|
||||
account: cachedAccount,
|
||||
idToken: cachedIdToken,
|
||||
accessToken: cachedAccessToken,
|
||||
refreshToken: cachedRefreshToken,
|
||||
appMetadata: 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, performanceClient, idTokenClaims, requestState, serverTokenResponse, requestId) {
|
||||
let accessToken = "";
|
||||
let responseScopes = [];
|
||||
let expiresOn = null;
|
||||
let extExpiresOn;
|
||||
let refreshOn;
|
||||
let familyId = "";
|
||||
if (cacheRecord.accessToken) {
|
||||
/*
|
||||
* if the request object has `popKid` property, `signPopToken` will be set to false and
|
||||
* the token will be returned unsigned
|
||||
*/
|
||||
if (cacheRecord.accessToken.tokenType ===
|
||||
AuthenticationScheme.POP &&
|
||||
!request.popKid) {
|
||||
const popTokenGenerator = new PopTokenGenerator(cryptoObj, performanceClient);
|
||||
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();
|
||||
// Access token expiresOn cached in seconds, converting to Date for AuthenticationResult
|
||||
expiresOn = toDateFromSeconds(cacheRecord.accessToken.expiresOn);
|
||||
extExpiresOn = toDateFromSeconds(cacheRecord.accessToken.extendedExpiresOn);
|
||||
if (cacheRecord.accessToken.refreshOn) {
|
||||
refreshOn = toDateFromSeconds(cacheRecord.accessToken.refreshOn);
|
||||
}
|
||||
}
|
||||
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
|
||||
? updateAccountTenantProfileData(getAccountInfo(cacheRecord.account), undefined, // tenantProfile optional
|
||||
idTokenClaims, cacheRecord.idToken?.secret)
|
||||
: 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 || "",
|
||||
familyId: familyId,
|
||||
tokenType: cacheRecord.accessToken?.tokenType || "",
|
||||
state: requestState ? requestState.userRequestState : "",
|
||||
cloudGraphHostName: cacheRecord.account?.cloudGraphHostName || "",
|
||||
msGraphHost: cacheRecord.account?.msGraphHost || "",
|
||||
code: serverTokenResponse?.spa_code,
|
||||
fromPlatformBroker: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
function buildAccountToCache(cacheStorage, authority, homeAccountId, base64Decode, correlationId, idTokenClaims, clientInfo, environment, claimsTenantId, authCodePayload, nativeAccountId, logger, performanceClient) {
|
||||
logger?.verbose("setCachedAccount called", correlationId);
|
||||
/*
|
||||
* Check if base account is already cached. Filter by homeAccountId (identifies
|
||||
* the user's home identity) and environment (identifies the cloud) — the two
|
||||
* tenant-agnostic properties that uniquely locate a base AccountEntity.
|
||||
*/
|
||||
const accountEnvironment = environment || authority.getPreferredCache();
|
||||
const matchedAccounts = cacheStorage.getAccountsFilteredBy({ homeAccountId, environment: accountEnvironment }, correlationId);
|
||||
performanceClient?.addFields({ cacheMatchedAccounts: matchedAccounts.length }, correlationId);
|
||||
if (matchedAccounts.length > 1) {
|
||||
/*
|
||||
* Base accounts are expected to be unique for a given homeAccountId in normal cache usage.
|
||||
* If multiple matches exist, ignore the cache hit rather than arbitrarily choosing one.
|
||||
*/
|
||||
logger?.warning("Multiple base accounts matched homeAccountId. Ignoring cached account and creating a new base account.", correlationId);
|
||||
}
|
||||
const cachedAccount = matchedAccounts.length === 1 ? matchedAccounts[0] : null;
|
||||
const baseAccount = cachedAccount ||
|
||||
createAccountEntity({
|
||||
homeAccountId,
|
||||
idTokenClaims,
|
||||
clientInfo,
|
||||
environment,
|
||||
cloudGraphHostName: authCodePayload?.cloud_graph_host_name,
|
||||
msGraphHost: authCodePayload?.msgraph_host,
|
||||
nativeAccountId: nativeAccountId,
|
||||
}, authority, base64Decode);
|
||||
const tenantProfiles = baseAccount.tenantProfiles || [];
|
||||
const tenantId = claimsTenantId || baseAccount.realm;
|
||||
if (tenantId &&
|
||||
!tenantProfiles.find((tenantProfile) => {
|
||||
return tenantProfile.tenantId === tenantId;
|
||||
})) {
|
||||
const newTenantProfile = buildTenantProfile(homeAccountId, baseAccount.localAccountId, tenantId, idTokenClaims);
|
||||
tenantProfiles.push(newTenantProfile);
|
||||
}
|
||||
baseAccount.tenantProfiles = tenantProfiles;
|
||||
return baseAccount;
|
||||
}
|
||||
|
||||
export { ResponseHandler, buildAccountToCache };
|
||||
//# sourceMappingURL=ResponseHandler.mjs.map
|
||||
1
backend/node_modules/@azure/msal-common/dist/response/ResponseHandler.mjs.map
generated
vendored
Normal file
1
backend/node_modules/@azure/msal-common/dist/response/ResponseHandler.mjs.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user