Estructura inicial del proyecto
This commit is contained in:
606
backend/node_modules/@azure/msal-common/src/client/AuthorizationCodeClient.ts
generated
vendored
Normal file
606
backend/node_modules/@azure/msal-common/src/client/AuthorizationCodeClient.ts
generated
vendored
Normal file
@ -0,0 +1,606 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { CommonAuthorizationCodeRequest } from "../request/CommonAuthorizationCodeRequest.js";
|
||||
import { Authority } from "../authority/Authority.js";
|
||||
import * as RequestParameterBuilder from "../request/RequestParameterBuilder.js";
|
||||
import * as UrlUtils from "../utils/UrlUtils.js";
|
||||
import * as Constants from "../utils/Constants.js";
|
||||
import * as AADServerParamKeys from "../constants/AADServerParamKeys.js";
|
||||
import {
|
||||
buildClientConfiguration,
|
||||
ClientConfiguration,
|
||||
CommonClientConfiguration,
|
||||
isOidcProtocolMode,
|
||||
} from "../config/ClientConfiguration.js";
|
||||
import { ServerAuthorizationTokenResponse } from "../response/ServerAuthorizationTokenResponse.js";
|
||||
import { NetworkResponse } from "../network/NetworkResponse.js";
|
||||
import { ResponseHandler } from "../response/ResponseHandler.js";
|
||||
import { AuthenticationResult } from "../response/AuthenticationResult.js";
|
||||
import {
|
||||
ClientAuthErrorCodes,
|
||||
createClientAuthError,
|
||||
} from "../error/ClientAuthError.js";
|
||||
import { UrlString } from "../url/UrlString.js";
|
||||
import { CommonEndSessionRequest } from "../request/CommonEndSessionRequest.js";
|
||||
import { PopTokenGenerator } from "../crypto/PopTokenGenerator.js";
|
||||
import { AuthorizationCodePayload } from "../response/AuthorizationCodePayload.js";
|
||||
import * as TimeUtils from "../utils/TimeUtils.js";
|
||||
import {
|
||||
buildClientInfoFromHomeAccountId,
|
||||
buildClientInfo,
|
||||
} from "../account/ClientInfo.js";
|
||||
import { CcsCredentialType, CcsCredential } from "../account/CcsCredential.js";
|
||||
import {
|
||||
createClientConfigurationError,
|
||||
ClientConfigurationErrorCodes,
|
||||
} from "../error/ClientConfigurationError.js";
|
||||
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
|
||||
import * as PerformanceEvents from "../telemetry/performance/PerformanceEvents.js";
|
||||
import { invokeAsync } from "../utils/FunctionWrappers.js";
|
||||
import { ClientAssertion } from "../account/ClientCredentials.js";
|
||||
import { getClientAssertion } from "../utils/ClientAssertionUtils.js";
|
||||
import { getRequestThumbprint } from "../network/RequestThumbprint.js";
|
||||
import {
|
||||
createTokenQueryParameters,
|
||||
createTokenRequestHeaders,
|
||||
executePostToTokenEndpoint,
|
||||
} from "../protocol/Token.js";
|
||||
import { createDiscoveredInstance } from "../authority/AuthorityFactory.js";
|
||||
import { ServerTelemetryManager } from "../telemetry/server/ServerTelemetryManager.js";
|
||||
import { Logger } from "../logger/Logger.js";
|
||||
import { ICrypto } from "../crypto/ICrypto.js";
|
||||
import { CacheManager } from "../cache/CacheManager.js";
|
||||
import { INetworkModule } from "../network/INetworkModule.js";
|
||||
import { version, name } from "../packageMetadata.js";
|
||||
|
||||
/**
|
||||
* Oauth2.0 Authorization Code client
|
||||
* @internal
|
||||
*/
|
||||
export class AuthorizationCodeClient {
|
||||
// Flag to indicate if client is for hybrid spa auth code redemption
|
||||
protected includeRedirectUri: boolean = true;
|
||||
private oidcDefaultScopes;
|
||||
|
||||
// Logger object
|
||||
public logger: Logger;
|
||||
|
||||
// Application config
|
||||
protected config: CommonClientConfiguration;
|
||||
|
||||
// Crypto Interface
|
||||
protected cryptoUtils: ICrypto;
|
||||
|
||||
// Storage Interface
|
||||
protected cacheManager: CacheManager;
|
||||
|
||||
// Network Interface
|
||||
protected networkClient: INetworkModule;
|
||||
|
||||
// Server Telemetry Manager
|
||||
protected serverTelemetryManager: ServerTelemetryManager | null;
|
||||
|
||||
// Default authority object
|
||||
public authority: Authority;
|
||||
|
||||
// Performance telemetry client
|
||||
protected performanceClient: IPerformanceClient;
|
||||
|
||||
constructor(
|
||||
configuration: ClientConfiguration,
|
||||
performanceClient: IPerformanceClient
|
||||
) {
|
||||
// Set the configuration
|
||||
this.config = buildClientConfiguration(configuration);
|
||||
|
||||
// Initialize the logger
|
||||
this.logger = new Logger(this.config.loggerOptions, name, version);
|
||||
|
||||
// Initialize crypto
|
||||
this.cryptoUtils = this.config.cryptoInterface;
|
||||
|
||||
// Initialize storage interface
|
||||
this.cacheManager = this.config.storageInterface;
|
||||
|
||||
// Set the network interface
|
||||
this.networkClient = this.config.networkInterface;
|
||||
|
||||
// Set TelemetryManager
|
||||
this.serverTelemetryManager = this.config.serverTelemetryManager;
|
||||
|
||||
// set Authority
|
||||
this.authority = this.config.authOptions.authority;
|
||||
|
||||
// set performance telemetry client
|
||||
this.performanceClient = performanceClient;
|
||||
this.oidcDefaultScopes =
|
||||
this.config.authOptions.authority.options.OIDCOptions?.defaultScopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* API to acquire a token in exchange of 'authorization_code` acquired by the user in the first leg of the
|
||||
* authorization_code_grant
|
||||
* @param request
|
||||
*/
|
||||
async acquireToken(
|
||||
request: CommonAuthorizationCodeRequest,
|
||||
apiId: number,
|
||||
authCodePayload?: AuthorizationCodePayload
|
||||
): Promise<AuthenticationResult> {
|
||||
if (!request.code) {
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.requestCannotBeMade
|
||||
);
|
||||
}
|
||||
|
||||
// Check for new cloud instance
|
||||
if (authCodePayload && authCodePayload.cloud_instance_host_name) {
|
||||
await invokeAsync(
|
||||
this.updateTokenEndpointAuthority.bind(this),
|
||||
PerformanceEvents.UpdateTokenEndpointAuthority,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(authCodePayload.cloud_instance_host_name, request.correlationId);
|
||||
}
|
||||
|
||||
const reqTimestamp = TimeUtils.nowSeconds();
|
||||
const response = await invokeAsync(
|
||||
this.executeTokenRequest.bind(this),
|
||||
PerformanceEvents.AuthClientExecuteTokenRequest,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(this.authority, request, this.serverTelemetryManager);
|
||||
|
||||
// Retrieve requestId from response headers
|
||||
const requestId =
|
||||
response.headers?.[Constants.HeaderNames.X_MS_REQUEST_ID];
|
||||
|
||||
const responseHandler = new ResponseHandler(
|
||||
this.config.authOptions.clientId,
|
||||
this.cacheManager,
|
||||
this.cryptoUtils,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
this.config.serializableCache,
|
||||
this.config.persistencePlugin
|
||||
);
|
||||
|
||||
// Validate response. This function throws a server error if an error is returned by the server.
|
||||
responseHandler.validateTokenResponse(
|
||||
response.body,
|
||||
request.correlationId
|
||||
);
|
||||
|
||||
return invokeAsync(
|
||||
responseHandler.handleServerTokenResponse.bind(responseHandler),
|
||||
PerformanceEvents.HandleServerTokenResponse,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(
|
||||
response.body,
|
||||
this.authority,
|
||||
reqTimestamp,
|
||||
request,
|
||||
apiId,
|
||||
authCodePayload,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
requestId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to log out the current user, and redirect the user to the postLogoutRedirectUri.
|
||||
* Default behaviour is to redirect the user to `window.location.href`.
|
||||
* @param authorityUri
|
||||
*/
|
||||
getLogoutUri(logoutRequest: CommonEndSessionRequest): string {
|
||||
// Throw error if logoutRequest is null/undefined
|
||||
if (!logoutRequest) {
|
||||
throw createClientConfigurationError(
|
||||
ClientConfigurationErrorCodes.logoutRequestEmpty
|
||||
);
|
||||
}
|
||||
const queryString = this.createLogoutUrlQueryString(logoutRequest);
|
||||
|
||||
// Construct logout URI
|
||||
return UrlString.appendQueryString(
|
||||
this.authority.endSessionEndpoint,
|
||||
queryString
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes POST request to token endpoint
|
||||
* @param authority
|
||||
* @param request
|
||||
*/
|
||||
private async executeTokenRequest(
|
||||
authority: Authority,
|
||||
request: CommonAuthorizationCodeRequest,
|
||||
serverTelemetryManager: ServerTelemetryManager | null
|
||||
): Promise<NetworkResponse<ServerAuthorizationTokenResponse>> {
|
||||
const queryParametersString = createTokenQueryParameters(
|
||||
request,
|
||||
this.config.authOptions.clientId,
|
||||
this.config.authOptions.redirectUri,
|
||||
this.performanceClient
|
||||
);
|
||||
const endpoint = UrlString.appendQueryString(
|
||||
authority.tokenEndpoint,
|
||||
queryParametersString
|
||||
);
|
||||
|
||||
const requestBody = await invokeAsync(
|
||||
this.createTokenRequestBody.bind(this),
|
||||
PerformanceEvents.AuthClientCreateTokenRequestBody,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request);
|
||||
|
||||
let ccsCredential: CcsCredential | undefined = undefined;
|
||||
if (request.clientInfo) {
|
||||
try {
|
||||
const clientInfo = buildClientInfo(
|
||||
request.clientInfo,
|
||||
this.cryptoUtils.base64Decode
|
||||
);
|
||||
ccsCredential = {
|
||||
credential: `${clientInfo.uid}${Constants.CLIENT_INFO_SEPARATOR}${clientInfo.utid}`,
|
||||
type: CcsCredentialType.HOME_ACCOUNT_ID,
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.verbose(
|
||||
`Could not parse client info for CCS Header: '${e}'`,
|
||||
request.correlationId
|
||||
);
|
||||
}
|
||||
}
|
||||
const headers: Record<string, string> = createTokenRequestHeaders(
|
||||
this.logger,
|
||||
this.config.systemOptions.preventCorsPreflight,
|
||||
ccsCredential || request.ccsCredential
|
||||
);
|
||||
|
||||
const thumbprint = getRequestThumbprint(
|
||||
this.config.authOptions.clientId,
|
||||
request
|
||||
);
|
||||
|
||||
return invokeAsync(
|
||||
executePostToTokenEndpoint,
|
||||
PerformanceEvents.AuthorizationCodeClientExecutePostToTokenEndpoint,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(
|
||||
endpoint,
|
||||
requestBody,
|
||||
headers,
|
||||
thumbprint,
|
||||
request.correlationId,
|
||||
this.cacheManager,
|
||||
this.networkClient,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
serverTelemetryManager
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a map for all the params to be sent to the service
|
||||
* @param request
|
||||
*/
|
||||
private async createTokenRequestBody(
|
||||
request: CommonAuthorizationCodeRequest
|
||||
): Promise<string> {
|
||||
const parameters = new Map<string, string>();
|
||||
|
||||
RequestParameterBuilder.addClientId(
|
||||
parameters,
|
||||
request.embeddedClientId ||
|
||||
request.extraParameters?.[AADServerParamKeys.CLIENT_ID] ||
|
||||
this.config.authOptions.clientId
|
||||
);
|
||||
|
||||
/*
|
||||
* For hybrid spa flow, there will be a code but no verifier
|
||||
* In this scenario, don't include redirect uri as auth code will not be bound to redirect URI
|
||||
*/
|
||||
if (!this.includeRedirectUri) {
|
||||
// Just validate
|
||||
if (!request.redirectUri) {
|
||||
throw createClientConfigurationError(
|
||||
ClientConfigurationErrorCodes.redirectUriEmpty
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Validate and include redirect uri
|
||||
RequestParameterBuilder.addRedirectUri(
|
||||
parameters,
|
||||
request.redirectUri
|
||||
);
|
||||
}
|
||||
|
||||
// Add scope array, parameter builder will add default scopes and dedupe
|
||||
RequestParameterBuilder.addScopes(
|
||||
parameters,
|
||||
request.scopes,
|
||||
true,
|
||||
this.oidcDefaultScopes
|
||||
);
|
||||
|
||||
RequestParameterBuilder.addResource(parameters, request.resource);
|
||||
|
||||
// add code: user set, not validated
|
||||
RequestParameterBuilder.addAuthorizationCode(parameters, request.code);
|
||||
|
||||
// Add library metadata
|
||||
RequestParameterBuilder.addLibraryInfo(
|
||||
parameters,
|
||||
this.config.libraryInfo
|
||||
);
|
||||
RequestParameterBuilder.addApplicationTelemetry(
|
||||
parameters,
|
||||
this.config.telemetry.application
|
||||
);
|
||||
RequestParameterBuilder.addThrottling(parameters);
|
||||
|
||||
if (this.serverTelemetryManager && !isOidcProtocolMode(this.config)) {
|
||||
RequestParameterBuilder.addServerTelemetry(
|
||||
parameters,
|
||||
this.serverTelemetryManager
|
||||
);
|
||||
}
|
||||
|
||||
// add code_verifier if passed
|
||||
if (request.codeVerifier) {
|
||||
RequestParameterBuilder.addCodeVerifier(
|
||||
parameters,
|
||||
request.codeVerifier
|
||||
);
|
||||
}
|
||||
|
||||
if (this.config.clientCredentials.clientSecret) {
|
||||
RequestParameterBuilder.addClientSecret(
|
||||
parameters,
|
||||
this.config.clientCredentials.clientSecret
|
||||
);
|
||||
}
|
||||
|
||||
if (this.config.clientCredentials.clientAssertion) {
|
||||
const clientAssertion: ClientAssertion =
|
||||
this.config.clientCredentials.clientAssertion;
|
||||
|
||||
RequestParameterBuilder.addClientAssertion(
|
||||
parameters,
|
||||
await getClientAssertion(
|
||||
clientAssertion.assertion,
|
||||
this.config.authOptions.clientId,
|
||||
request.resourceRequestUri
|
||||
)
|
||||
);
|
||||
RequestParameterBuilder.addClientAssertionType(
|
||||
parameters,
|
||||
clientAssertion.assertionType
|
||||
);
|
||||
}
|
||||
|
||||
RequestParameterBuilder.addGrantType(
|
||||
parameters,
|
||||
Constants.GrantType.AUTHORIZATION_CODE_GRANT
|
||||
);
|
||||
RequestParameterBuilder.addClientInfo(parameters);
|
||||
|
||||
if (
|
||||
request.authenticationScheme === Constants.AuthenticationScheme.POP
|
||||
) {
|
||||
const popTokenGenerator = new PopTokenGenerator(
|
||||
this.cryptoUtils,
|
||||
this.performanceClient
|
||||
);
|
||||
|
||||
let reqCnfData;
|
||||
if (!request.popKid) {
|
||||
const generatedReqCnfData = await invokeAsync(
|
||||
popTokenGenerator.generateCnf.bind(popTokenGenerator),
|
||||
PerformanceEvents.PopTokenGenerateCnf,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request, this.logger);
|
||||
reqCnfData = generatedReqCnfData.reqCnfString;
|
||||
} else {
|
||||
reqCnfData = this.cryptoUtils.encodeKid(request.popKid);
|
||||
}
|
||||
|
||||
// SPA PoP requires full Base64Url encoded req_cnf string (unhashed)
|
||||
RequestParameterBuilder.addPopToken(parameters, reqCnfData);
|
||||
} else if (
|
||||
request.authenticationScheme === Constants.AuthenticationScheme.SSH
|
||||
) {
|
||||
if (request.sshJwk) {
|
||||
RequestParameterBuilder.addSshJwk(parameters, request.sshJwk);
|
||||
} else {
|
||||
throw createClientConfigurationError(
|
||||
ClientConfigurationErrorCodes.missingSshJwk
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let ccsCred: CcsCredential | undefined = undefined;
|
||||
if (request.clientInfo) {
|
||||
try {
|
||||
const clientInfo = buildClientInfo(
|
||||
request.clientInfo,
|
||||
this.cryptoUtils.base64Decode
|
||||
);
|
||||
ccsCred = {
|
||||
credential: `${clientInfo.uid}${Constants.CLIENT_INFO_SEPARATOR}${clientInfo.utid}`,
|
||||
type: CcsCredentialType.HOME_ACCOUNT_ID,
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.verbose(
|
||||
`Could not parse client info for CCS Header: '${e}'`,
|
||||
request.correlationId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ccsCred = request.ccsCredential;
|
||||
}
|
||||
|
||||
// Adds these as parameters in the request instead of headers to prevent CORS preflight request
|
||||
if (this.config.systemOptions.preventCorsPreflight && ccsCred) {
|
||||
switch (ccsCred.type) {
|
||||
case CcsCredentialType.HOME_ACCOUNT_ID:
|
||||
try {
|
||||
const clientInfo = buildClientInfoFromHomeAccountId(
|
||||
ccsCred.credential
|
||||
);
|
||||
RequestParameterBuilder.addCcsOid(
|
||||
parameters,
|
||||
clientInfo
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.verbose(
|
||||
`Could not parse home account ID for CCS Header: '${e}'`,
|
||||
request.correlationId
|
||||
);
|
||||
}
|
||||
break;
|
||||
case CcsCredentialType.UPN:
|
||||
RequestParameterBuilder.addCcsUpn(
|
||||
parameters,
|
||||
ccsCred.credential
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.embeddedClientId) {
|
||||
RequestParameterBuilder.addBrokerParameters(
|
||||
parameters,
|
||||
this.config.authOptions.clientId,
|
||||
this.config.authOptions.redirectUri
|
||||
);
|
||||
}
|
||||
|
||||
if (request.extraParameters) {
|
||||
RequestParameterBuilder.addExtraParameters(
|
||||
parameters,
|
||||
request.extraParameters
|
||||
);
|
||||
}
|
||||
|
||||
// Add hybrid spa parameters if not already provided
|
||||
if (
|
||||
request.enableSpaAuthorizationCode &&
|
||||
(!request.extraParameters ||
|
||||
!request.extraParameters[AADServerParamKeys.RETURN_SPA_CODE])
|
||||
) {
|
||||
RequestParameterBuilder.addExtraParameters(parameters, {
|
||||
[AADServerParamKeys.RETURN_SPA_CODE]: "1",
|
||||
});
|
||||
}
|
||||
|
||||
RequestParameterBuilder.instrumentBrokerParams(
|
||||
parameters,
|
||||
request.correlationId,
|
||||
this.performanceClient
|
||||
);
|
||||
|
||||
RequestParameterBuilder.addClaims(
|
||||
parameters,
|
||||
request.claims,
|
||||
this.config.authOptions.clientCapabilities,
|
||||
request.skipBrokerClaims
|
||||
);
|
||||
|
||||
return UrlUtils.mapToQueryString(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* This API validates the `EndSessionRequest` and creates a URL
|
||||
* @param request
|
||||
*/
|
||||
private createLogoutUrlQueryString(
|
||||
request: CommonEndSessionRequest
|
||||
): string {
|
||||
const parameters = new Map<string, string>();
|
||||
|
||||
if (request.postLogoutRedirectUri) {
|
||||
RequestParameterBuilder.addPostLogoutRedirectUri(
|
||||
parameters,
|
||||
request.postLogoutRedirectUri
|
||||
);
|
||||
}
|
||||
|
||||
if (request.correlationId) {
|
||||
RequestParameterBuilder.addCorrelationId(
|
||||
parameters,
|
||||
request.correlationId
|
||||
);
|
||||
}
|
||||
|
||||
if (request.idTokenHint) {
|
||||
RequestParameterBuilder.addIdTokenHint(
|
||||
parameters,
|
||||
request.idTokenHint
|
||||
);
|
||||
}
|
||||
|
||||
if (request.state) {
|
||||
RequestParameterBuilder.addState(parameters, request.state);
|
||||
}
|
||||
|
||||
if (request.logoutHint) {
|
||||
RequestParameterBuilder.addLogoutHint(
|
||||
parameters,
|
||||
request.logoutHint
|
||||
);
|
||||
}
|
||||
|
||||
if (request.extraQueryParameters) {
|
||||
RequestParameterBuilder.addExtraParameters(
|
||||
parameters,
|
||||
request.extraQueryParameters
|
||||
);
|
||||
}
|
||||
|
||||
if (this.config.authOptions.instanceAware) {
|
||||
RequestParameterBuilder.addInstanceAware(parameters);
|
||||
}
|
||||
|
||||
return UrlUtils.mapToQueryString(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the authority to the cloud instance provided in the authorization response
|
||||
* @param cloudInstanceHostName - cloud instance host name from authorization code payload
|
||||
* @param correlationId - request correlation id
|
||||
*/
|
||||
private async updateTokenEndpointAuthority(
|
||||
cloudInstanceHostName: string,
|
||||
correlationId: string
|
||||
): Promise<void> {
|
||||
const cloudInstanceAuthorityUri = `https://${cloudInstanceHostName}/${this.authority.tenant}/`;
|
||||
const cloudInstanceAuthority = await createDiscoveredInstance(
|
||||
cloudInstanceAuthorityUri,
|
||||
this.networkClient,
|
||||
this.cacheManager,
|
||||
this.authority.options,
|
||||
this.logger,
|
||||
correlationId,
|
||||
this.performanceClient
|
||||
);
|
||||
this.authority = cloudInstanceAuthority;
|
||||
}
|
||||
}
|
||||
564
backend/node_modules/@azure/msal-common/src/client/RefreshTokenClient.ts
generated
vendored
Normal file
564
backend/node_modules/@azure/msal-common/src/client/RefreshTokenClient.ts
generated
vendored
Normal file
@ -0,0 +1,564 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import {
|
||||
buildClientConfiguration,
|
||||
ClientConfiguration,
|
||||
CommonClientConfiguration,
|
||||
isOidcProtocolMode,
|
||||
} from "../config/ClientConfiguration.js";
|
||||
import { CommonRefreshTokenRequest } from "../request/CommonRefreshTokenRequest.js";
|
||||
import { Authority } from "../authority/Authority.js";
|
||||
import { ServerAuthorizationTokenResponse } from "../response/ServerAuthorizationTokenResponse.js";
|
||||
import * as RequestParameterBuilder from "../request/RequestParameterBuilder.js";
|
||||
import * as UrlUtils from "../utils/UrlUtils.js";
|
||||
import * as Constants from "../utils/Constants.js";
|
||||
import * as AADServerParamKeys from "../constants/AADServerParamKeys.js";
|
||||
import { ResponseHandler } from "../response/ResponseHandler.js";
|
||||
import { AuthenticationResult } from "../response/AuthenticationResult.js";
|
||||
import { PopTokenGenerator } from "../crypto/PopTokenGenerator.js";
|
||||
import { NetworkResponse } from "../network/NetworkResponse.js";
|
||||
import { CommonSilentFlowRequest } from "../request/CommonSilentFlowRequest.js";
|
||||
import {
|
||||
createClientConfigurationError,
|
||||
ClientConfigurationErrorCodes,
|
||||
} from "../error/ClientConfigurationError.js";
|
||||
import {
|
||||
createClientAuthError,
|
||||
ClientAuthErrorCodes,
|
||||
} from "../error/ClientAuthError.js";
|
||||
import { ServerError } from "../error/ServerError.js";
|
||||
import * as TimeUtils from "../utils/TimeUtils.js";
|
||||
import { UrlString } from "../url/UrlString.js";
|
||||
import { CcsCredentialType } from "../account/CcsCredential.js";
|
||||
import { buildClientInfoFromHomeAccountId } from "../account/ClientInfo.js";
|
||||
import {
|
||||
InteractionRequiredAuthError,
|
||||
InteractionRequiredAuthErrorCodes,
|
||||
createInteractionRequiredAuthError,
|
||||
} from "../error/InteractionRequiredAuthError.js";
|
||||
import * as PerformanceEvents from "../telemetry/performance/PerformanceEvents.js";
|
||||
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
|
||||
import { invoke, invokeAsync } from "../utils/FunctionWrappers.js";
|
||||
import { ClientAssertion } from "../account/ClientCredentials.js";
|
||||
import { getClientAssertion } from "../utils/ClientAssertionUtils.js";
|
||||
import { getRequestThumbprint } from "../network/RequestThumbprint.js";
|
||||
import {
|
||||
createTokenQueryParameters,
|
||||
createTokenRequestHeaders,
|
||||
executePostToTokenEndpoint,
|
||||
} from "../protocol/Token.js";
|
||||
import { ServerTelemetryManager } from "../telemetry/server/ServerTelemetryManager.js";
|
||||
import { INetworkModule } from "../network/INetworkModule.js";
|
||||
import { CacheManager } from "../cache/CacheManager.js";
|
||||
import { ICrypto } from "../crypto/ICrypto.js";
|
||||
import { Logger } from "../logger/Logger.js";
|
||||
import { version, name } from "../packageMetadata.js";
|
||||
|
||||
const DEFAULT_REFRESH_TOKEN_EXPIRATION_OFFSET_SECONDS = 300; // 5 Minutes
|
||||
|
||||
/**
|
||||
* OAuth2.0 refresh token client
|
||||
* @internal
|
||||
*/
|
||||
export class RefreshTokenClient {
|
||||
// Logger object
|
||||
public logger: Logger;
|
||||
|
||||
// Application config
|
||||
protected config: CommonClientConfiguration;
|
||||
|
||||
// Crypto Interface
|
||||
protected cryptoUtils: ICrypto;
|
||||
|
||||
// Storage Interface
|
||||
protected cacheManager: CacheManager;
|
||||
|
||||
// Network Interface
|
||||
protected networkClient: INetworkModule;
|
||||
|
||||
// Server Telemetry Manager
|
||||
protected serverTelemetryManager: ServerTelemetryManager | null;
|
||||
|
||||
// Default authority object
|
||||
public authority: Authority;
|
||||
|
||||
// Performance telemetry client
|
||||
protected performanceClient: IPerformanceClient;
|
||||
|
||||
constructor(
|
||||
configuration: ClientConfiguration,
|
||||
performanceClient: IPerformanceClient
|
||||
) {
|
||||
// Set the configuration
|
||||
this.config = buildClientConfiguration(configuration);
|
||||
|
||||
// Initialize the logger
|
||||
this.logger = new Logger(this.config.loggerOptions, name, version);
|
||||
|
||||
// Initialize crypto
|
||||
this.cryptoUtils = this.config.cryptoInterface;
|
||||
|
||||
// Initialize storage interface
|
||||
this.cacheManager = this.config.storageInterface;
|
||||
|
||||
// Set the network interface
|
||||
this.networkClient = this.config.networkInterface;
|
||||
|
||||
// Set TelemetryManager
|
||||
this.serverTelemetryManager = this.config.serverTelemetryManager;
|
||||
|
||||
// set Authority
|
||||
this.authority = this.config.authOptions.authority;
|
||||
|
||||
// set performance telemetry client
|
||||
this.performanceClient = performanceClient;
|
||||
}
|
||||
public async acquireToken(
|
||||
request: CommonRefreshTokenRequest,
|
||||
apiId: number
|
||||
): Promise<AuthenticationResult> {
|
||||
const reqTimestamp = TimeUtils.nowSeconds();
|
||||
const response = await invokeAsync(
|
||||
this.executeTokenRequest.bind(this),
|
||||
PerformanceEvents.RefreshTokenClientExecuteTokenRequest,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request, this.authority);
|
||||
|
||||
// Retrieve requestId from response headers
|
||||
const requestId =
|
||||
response.headers?.[Constants.HeaderNames.X_MS_REQUEST_ID];
|
||||
const responseHandler = new ResponseHandler(
|
||||
this.config.authOptions.clientId,
|
||||
this.cacheManager,
|
||||
this.cryptoUtils,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
this.config.serializableCache,
|
||||
this.config.persistencePlugin
|
||||
);
|
||||
responseHandler.validateTokenResponse(
|
||||
response.body,
|
||||
request.correlationId
|
||||
);
|
||||
|
||||
return invokeAsync(
|
||||
responseHandler.handleServerTokenResponse.bind(responseHandler),
|
||||
PerformanceEvents.HandleServerTokenResponse,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(
|
||||
response.body,
|
||||
this.authority,
|
||||
reqTimestamp,
|
||||
request,
|
||||
apiId,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
request.forceCache,
|
||||
requestId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets cached refresh token and attaches to request, then calls acquireToken API
|
||||
* @param request
|
||||
*/
|
||||
public async acquireTokenByRefreshToken(
|
||||
request: CommonSilentFlowRequest,
|
||||
apiId: number
|
||||
): Promise<AuthenticationResult> {
|
||||
// Cannot renew token if no request object is given.
|
||||
if (!request) {
|
||||
throw createClientConfigurationError(
|
||||
ClientConfigurationErrorCodes.tokenRequestEmpty
|
||||
);
|
||||
}
|
||||
// We currently do not support silent flow for account === null use cases; This will be revisited for confidential flow usecases
|
||||
if (!request.account) {
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.noAccountInSilentRequest
|
||||
);
|
||||
}
|
||||
|
||||
// try checking if FOCI is enabled for the given application
|
||||
const isFOCI = this.cacheManager.isAppMetadataFOCI(
|
||||
request.account.environment,
|
||||
request.correlationId
|
||||
);
|
||||
|
||||
// if the app is part of the family, retrive a Family refresh token if present and make a refreshTokenRequest
|
||||
if (isFOCI) {
|
||||
try {
|
||||
return await invokeAsync(
|
||||
this.acquireTokenWithCachedRefreshToken.bind(this),
|
||||
PerformanceEvents.RefreshTokenClientAcquireTokenWithCachedRefreshToken,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request, true, apiId);
|
||||
} catch (e) {
|
||||
const noFamilyRTInCache =
|
||||
e instanceof InteractionRequiredAuthError &&
|
||||
e.errorCode ===
|
||||
InteractionRequiredAuthErrorCodes.noTokensFound;
|
||||
const clientMismatchErrorWithFamilyRT =
|
||||
e instanceof ServerError &&
|
||||
e.errorCode === Constants.INVALID_GRANT_ERROR &&
|
||||
e.subError === Constants.CLIENT_MISMATCH_ERROR;
|
||||
|
||||
// if family Refresh Token (FRT) cache acquisition fails or if client_mismatch error is seen with FRT, reattempt with application Refresh Token (ART)
|
||||
if (noFamilyRTInCache || clientMismatchErrorWithFamilyRT) {
|
||||
return invokeAsync(
|
||||
this.acquireTokenWithCachedRefreshToken.bind(this),
|
||||
PerformanceEvents.RefreshTokenClientAcquireTokenWithCachedRefreshToken,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request, false, apiId);
|
||||
// throw in all other cases
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
// fall back to application refresh token acquisition
|
||||
return invokeAsync(
|
||||
this.acquireTokenWithCachedRefreshToken.bind(this),
|
||||
PerformanceEvents.RefreshTokenClientAcquireTokenWithCachedRefreshToken,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request, false, apiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* makes a network call to acquire tokens by exchanging RefreshToken available in userCache; throws if refresh token is not cached
|
||||
* @param request
|
||||
*/
|
||||
private async acquireTokenWithCachedRefreshToken(
|
||||
request: CommonSilentFlowRequest,
|
||||
foci: boolean,
|
||||
apiId: number
|
||||
) {
|
||||
// fetches family RT or application RT based on FOCI value
|
||||
const refreshToken = invoke(
|
||||
this.cacheManager.getRefreshToken.bind(this.cacheManager),
|
||||
PerformanceEvents.CacheManagerGetRefreshToken,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request.account, foci, request.correlationId, undefined);
|
||||
|
||||
if (!refreshToken) {
|
||||
throw createInteractionRequiredAuthError(
|
||||
InteractionRequiredAuthErrorCodes.noTokensFound
|
||||
);
|
||||
}
|
||||
|
||||
if (refreshToken.expiresOn) {
|
||||
const offset =
|
||||
request.refreshTokenExpirationOffsetSeconds ||
|
||||
DEFAULT_REFRESH_TOKEN_EXPIRATION_OFFSET_SECONDS;
|
||||
this.performanceClient?.addFields(
|
||||
{
|
||||
cacheRtExpiresOnSeconds: Number(refreshToken.expiresOn),
|
||||
rtOffsetSeconds: offset,
|
||||
},
|
||||
request.correlationId
|
||||
);
|
||||
|
||||
if (TimeUtils.isTokenExpired(refreshToken.expiresOn, offset)) {
|
||||
throw createInteractionRequiredAuthError(
|
||||
InteractionRequiredAuthErrorCodes.refreshTokenExpired
|
||||
);
|
||||
}
|
||||
}
|
||||
// attach cached RT size to the current measurement
|
||||
|
||||
const refreshTokenRequest: CommonRefreshTokenRequest = {
|
||||
...request,
|
||||
refreshToken: refreshToken.secret,
|
||||
authenticationScheme:
|
||||
request.authenticationScheme ||
|
||||
Constants.AuthenticationScheme.BEARER,
|
||||
ccsCredential: {
|
||||
credential: request.account.homeAccountId,
|
||||
type: CcsCredentialType.HOME_ACCOUNT_ID,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
return await invokeAsync(
|
||||
this.acquireToken.bind(this),
|
||||
PerformanceEvents.RefreshTokenClientAcquireToken,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(refreshTokenRequest, apiId);
|
||||
} catch (e) {
|
||||
if (e instanceof InteractionRequiredAuthError) {
|
||||
if (e.subError === InteractionRequiredAuthErrorCodes.badToken) {
|
||||
// Remove bad refresh token from cache
|
||||
this.logger.verbose(
|
||||
"acquireTokenWithRefreshToken: bad refresh token, removing from cache",
|
||||
request.correlationId
|
||||
);
|
||||
const badRefreshTokenKey =
|
||||
this.cacheManager.generateCredentialKey(refreshToken);
|
||||
this.cacheManager.removeRefreshToken(
|
||||
badRefreshTokenKey,
|
||||
request.correlationId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the network message and makes a NW call to the underlying secure token service
|
||||
* @param request
|
||||
* @param authority
|
||||
*/
|
||||
private async executeTokenRequest(
|
||||
request: CommonRefreshTokenRequest,
|
||||
authority: Authority
|
||||
): Promise<NetworkResponse<ServerAuthorizationTokenResponse>> {
|
||||
const queryParametersString = createTokenQueryParameters(
|
||||
request,
|
||||
this.config.authOptions.clientId,
|
||||
this.config.authOptions.redirectUri,
|
||||
this.performanceClient
|
||||
);
|
||||
const endpoint = UrlString.appendQueryString(
|
||||
authority.tokenEndpoint,
|
||||
queryParametersString
|
||||
);
|
||||
|
||||
const requestBody = await invokeAsync(
|
||||
this.createTokenRequestBody.bind(this),
|
||||
PerformanceEvents.RefreshTokenClientCreateTokenRequestBody,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request);
|
||||
const headers: Record<string, string> = createTokenRequestHeaders(
|
||||
this.logger,
|
||||
this.config.systemOptions.preventCorsPreflight,
|
||||
request.ccsCredential
|
||||
);
|
||||
|
||||
const thumbprint = getRequestThumbprint(
|
||||
this.config.authOptions.clientId,
|
||||
request
|
||||
);
|
||||
|
||||
return invokeAsync(
|
||||
executePostToTokenEndpoint,
|
||||
PerformanceEvents.RefreshTokenClientExecutePostToTokenEndpoint,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(
|
||||
endpoint,
|
||||
requestBody,
|
||||
headers,
|
||||
thumbprint,
|
||||
request.correlationId,
|
||||
this.cacheManager,
|
||||
this.networkClient,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
this.serverTelemetryManager
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create the token request body
|
||||
* @param request
|
||||
*/
|
||||
private async createTokenRequestBody(
|
||||
request: CommonRefreshTokenRequest
|
||||
): Promise<string> {
|
||||
const parameters = new Map<string, string>();
|
||||
|
||||
RequestParameterBuilder.addClientId(
|
||||
parameters,
|
||||
request.embeddedClientId ||
|
||||
request.extraParameters?.[AADServerParamKeys.CLIENT_ID] ||
|
||||
this.config.authOptions.clientId
|
||||
);
|
||||
|
||||
if (request.redirectUri) {
|
||||
RequestParameterBuilder.addRedirectUri(
|
||||
parameters,
|
||||
request.redirectUri
|
||||
);
|
||||
}
|
||||
|
||||
RequestParameterBuilder.addScopes(
|
||||
parameters,
|
||||
request.scopes,
|
||||
true,
|
||||
this.config.authOptions.authority.options.OIDCOptions?.defaultScopes
|
||||
);
|
||||
|
||||
RequestParameterBuilder.addGrantType(
|
||||
parameters,
|
||||
Constants.GrantType.REFRESH_TOKEN_GRANT
|
||||
);
|
||||
|
||||
RequestParameterBuilder.addClientInfo(parameters);
|
||||
|
||||
RequestParameterBuilder.addLibraryInfo(
|
||||
parameters,
|
||||
this.config.libraryInfo
|
||||
);
|
||||
RequestParameterBuilder.addApplicationTelemetry(
|
||||
parameters,
|
||||
this.config.telemetry.application
|
||||
);
|
||||
RequestParameterBuilder.addThrottling(parameters);
|
||||
|
||||
if (this.serverTelemetryManager && !isOidcProtocolMode(this.config)) {
|
||||
RequestParameterBuilder.addServerTelemetry(
|
||||
parameters,
|
||||
this.serverTelemetryManager
|
||||
);
|
||||
}
|
||||
|
||||
RequestParameterBuilder.addRefreshToken(
|
||||
parameters,
|
||||
request.refreshToken
|
||||
);
|
||||
|
||||
if (this.config.clientCredentials.clientSecret) {
|
||||
RequestParameterBuilder.addClientSecret(
|
||||
parameters,
|
||||
this.config.clientCredentials.clientSecret
|
||||
);
|
||||
}
|
||||
|
||||
if (this.config.clientCredentials.clientAssertion) {
|
||||
const clientAssertion: ClientAssertion =
|
||||
this.config.clientCredentials.clientAssertion;
|
||||
|
||||
RequestParameterBuilder.addClientAssertion(
|
||||
parameters,
|
||||
await getClientAssertion(
|
||||
clientAssertion.assertion,
|
||||
this.config.authOptions.clientId,
|
||||
request.resourceRequestUri
|
||||
)
|
||||
);
|
||||
RequestParameterBuilder.addClientAssertionType(
|
||||
parameters,
|
||||
clientAssertion.assertionType
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
request.authenticationScheme === Constants.AuthenticationScheme.POP
|
||||
) {
|
||||
const popTokenGenerator = new PopTokenGenerator(
|
||||
this.cryptoUtils,
|
||||
this.performanceClient
|
||||
);
|
||||
|
||||
let reqCnfData;
|
||||
if (!request.popKid) {
|
||||
const generatedReqCnfData = await invokeAsync(
|
||||
popTokenGenerator.generateCnf.bind(popTokenGenerator),
|
||||
PerformanceEvents.PopTokenGenerateCnf,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(request, this.logger);
|
||||
|
||||
reqCnfData = generatedReqCnfData.reqCnfString;
|
||||
} else {
|
||||
reqCnfData = this.cryptoUtils.encodeKid(request.popKid);
|
||||
}
|
||||
|
||||
// SPA PoP requires full Base64Url encoded req_cnf string (unhashed)
|
||||
RequestParameterBuilder.addPopToken(parameters, reqCnfData);
|
||||
} else if (
|
||||
request.authenticationScheme === Constants.AuthenticationScheme.SSH
|
||||
) {
|
||||
if (request.sshJwk) {
|
||||
RequestParameterBuilder.addSshJwk(parameters, request.sshJwk);
|
||||
} else {
|
||||
throw createClientConfigurationError(
|
||||
ClientConfigurationErrorCodes.missingSshJwk
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.config.systemOptions.preventCorsPreflight &&
|
||||
request.ccsCredential
|
||||
) {
|
||||
switch (request.ccsCredential.type) {
|
||||
case CcsCredentialType.HOME_ACCOUNT_ID:
|
||||
try {
|
||||
const clientInfo = buildClientInfoFromHomeAccountId(
|
||||
request.ccsCredential.credential
|
||||
);
|
||||
RequestParameterBuilder.addCcsOid(
|
||||
parameters,
|
||||
clientInfo
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.verbose(
|
||||
`Could not parse home account ID for CCS Header: '${e}'`,
|
||||
request.correlationId
|
||||
);
|
||||
}
|
||||
break;
|
||||
case CcsCredentialType.UPN:
|
||||
RequestParameterBuilder.addCcsUpn(
|
||||
parameters,
|
||||
request.ccsCredential.credential
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.embeddedClientId) {
|
||||
RequestParameterBuilder.addBrokerParameters(
|
||||
parameters,
|
||||
this.config.authOptions.clientId,
|
||||
this.config.authOptions.redirectUri
|
||||
);
|
||||
}
|
||||
|
||||
if (request.extraParameters) {
|
||||
RequestParameterBuilder.addExtraParameters(parameters, {
|
||||
...request.extraParameters,
|
||||
});
|
||||
}
|
||||
|
||||
RequestParameterBuilder.instrumentBrokerParams(
|
||||
parameters,
|
||||
request.correlationId,
|
||||
this.performanceClient
|
||||
);
|
||||
|
||||
RequestParameterBuilder.addClaims(
|
||||
parameters,
|
||||
request.claims,
|
||||
this.config.authOptions.clientCapabilities,
|
||||
request.skipBrokerClaims
|
||||
);
|
||||
|
||||
return UrlUtils.mapToQueryString(parameters);
|
||||
}
|
||||
}
|
||||
272
backend/node_modules/@azure/msal-common/src/client/SilentFlowClient.ts
generated
vendored
Normal file
272
backend/node_modules/@azure/msal-common/src/client/SilentFlowClient.ts
generated
vendored
Normal file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import {
|
||||
buildClientConfiguration,
|
||||
ClientConfiguration,
|
||||
CommonClientConfiguration,
|
||||
} from "../config/ClientConfiguration.js";
|
||||
import { CommonSilentFlowRequest } from "../request/CommonSilentFlowRequest.js";
|
||||
import { AuthenticationResult } from "../response/AuthenticationResult.js";
|
||||
import * as TimeUtils from "../utils/TimeUtils.js";
|
||||
import {
|
||||
ClientAuthErrorCodes,
|
||||
createClientAuthError,
|
||||
} from "../error/ClientAuthError.js";
|
||||
import { ResponseHandler } from "../response/ResponseHandler.js";
|
||||
import { CacheRecord } from "../cache/entities/CacheRecord.js";
|
||||
import { CacheOutcome } from "../utils/Constants.js";
|
||||
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
|
||||
import { StringUtils } from "../utils/StringUtils.js";
|
||||
import { checkMaxAge, extractTokenClaims } from "../account/AuthToken.js";
|
||||
import { TokenClaims } from "../account/TokenClaims.js";
|
||||
import * as PerformanceEvents from "../telemetry/performance/PerformanceEvents.js";
|
||||
import { invokeAsync } from "../utils/FunctionWrappers.js";
|
||||
import {
|
||||
Authority,
|
||||
getTenantFromAuthorityString,
|
||||
} from "../authority/Authority.js";
|
||||
import { Logger } from "../logger/Logger.js";
|
||||
import { ICrypto } from "../crypto/ICrypto.js";
|
||||
import { CacheManager } from "../cache/CacheManager.js";
|
||||
import { INetworkModule } from "../network/INetworkModule.js";
|
||||
import { ServerTelemetryManager } from "../telemetry/server/ServerTelemetryManager.js";
|
||||
import { version, name } from "../packageMetadata.js";
|
||||
|
||||
/** @internal */
|
||||
export class SilentFlowClient {
|
||||
// Logger object
|
||||
public logger: Logger;
|
||||
|
||||
// Application config
|
||||
protected config: CommonClientConfiguration;
|
||||
|
||||
// Crypto Interface
|
||||
protected cryptoUtils: ICrypto;
|
||||
|
||||
// Storage Interface
|
||||
protected cacheManager: CacheManager;
|
||||
|
||||
// Network Interface
|
||||
protected networkClient: INetworkModule;
|
||||
|
||||
// Server Telemetry Manager
|
||||
protected serverTelemetryManager: ServerTelemetryManager | null;
|
||||
|
||||
// Default authority object
|
||||
public authority: Authority;
|
||||
|
||||
// Performance telemetry client
|
||||
protected performanceClient: IPerformanceClient;
|
||||
|
||||
constructor(
|
||||
configuration: ClientConfiguration,
|
||||
performanceClient: IPerformanceClient
|
||||
) {
|
||||
// Set the configuration
|
||||
this.config = buildClientConfiguration(configuration);
|
||||
|
||||
// Initialize the logger
|
||||
this.logger = new Logger(this.config.loggerOptions, name, version);
|
||||
|
||||
// Initialize crypto
|
||||
this.cryptoUtils = this.config.cryptoInterface;
|
||||
|
||||
// Initialize storage interface
|
||||
this.cacheManager = this.config.storageInterface;
|
||||
|
||||
// Set the network interface
|
||||
this.networkClient = this.config.networkInterface;
|
||||
|
||||
// Set TelemetryManager
|
||||
this.serverTelemetryManager = this.config.serverTelemetryManager;
|
||||
|
||||
// set Authority
|
||||
this.authority = this.config.authOptions.authority;
|
||||
|
||||
// set performance telemetry client
|
||||
this.performanceClient = performanceClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves token from cache or throws an error if it must be refreshed.
|
||||
* @param request
|
||||
*/
|
||||
async acquireCachedToken(
|
||||
request: CommonSilentFlowRequest
|
||||
): Promise<[AuthenticationResult, CacheOutcome]> {
|
||||
let lastCacheOutcome: CacheOutcome = CacheOutcome.NOT_APPLICABLE;
|
||||
|
||||
if (request.forceRefresh || !StringUtils.isEmptyObj(request.claims)) {
|
||||
// Must refresh due to present force_refresh flag.
|
||||
this.setCacheOutcome(
|
||||
CacheOutcome.FORCE_REFRESH_OR_CLAIMS,
|
||||
request.correlationId
|
||||
);
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.tokenRefreshRequired
|
||||
);
|
||||
}
|
||||
|
||||
// We currently do not support silent flow for account === null use cases; This will be revisited for confidential flow usecases
|
||||
if (!request.account) {
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.noAccountInSilentRequest
|
||||
);
|
||||
}
|
||||
|
||||
const requestTenantId =
|
||||
request.account.tenantId ||
|
||||
getTenantFromAuthorityString(request.authority);
|
||||
const tokenKeys = this.cacheManager.getTokenKeys();
|
||||
const cachedAccessToken = this.cacheManager.getAccessToken(
|
||||
request.account,
|
||||
request,
|
||||
tokenKeys,
|
||||
requestTenantId
|
||||
);
|
||||
|
||||
if (!cachedAccessToken) {
|
||||
// must refresh due to non-existent access_token
|
||||
this.setCacheOutcome(
|
||||
CacheOutcome.NO_CACHED_ACCESS_TOKEN,
|
||||
request.correlationId
|
||||
);
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.tokenRefreshRequired
|
||||
);
|
||||
} else if (
|
||||
TimeUtils.wasClockTurnedBack(cachedAccessToken.cachedAt) ||
|
||||
TimeUtils.isTokenExpired(
|
||||
cachedAccessToken.expiresOn,
|
||||
this.config.systemOptions.tokenRenewalOffsetSeconds
|
||||
)
|
||||
) {
|
||||
// must refresh due to the expires_in value
|
||||
this.setCacheOutcome(
|
||||
CacheOutcome.CACHED_ACCESS_TOKEN_EXPIRED,
|
||||
request.correlationId
|
||||
);
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.tokenRefreshRequired
|
||||
);
|
||||
} else if (request.resource) {
|
||||
// cached access token must have a resource that matches the request resource for MCP scenarios
|
||||
if (cachedAccessToken.resource !== request.resource) {
|
||||
this.setCacheOutcome(
|
||||
CacheOutcome.NO_CACHED_ACCESS_TOKEN,
|
||||
request.correlationId
|
||||
);
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.tokenRefreshRequired
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
cachedAccessToken.refreshOn &&
|
||||
TimeUtils.isTokenExpired(cachedAccessToken.refreshOn, 0)
|
||||
) {
|
||||
// must refresh (in the background) due to the refresh_in value
|
||||
lastCacheOutcome = CacheOutcome.PROACTIVELY_REFRESHED;
|
||||
|
||||
// don't throw ClientAuthError.createRefreshRequiredError(), return cached token instead
|
||||
}
|
||||
|
||||
const environment =
|
||||
request.authority || this.authority.getPreferredCache();
|
||||
const cacheRecord: CacheRecord = {
|
||||
account: this.cacheManager.getAccount(
|
||||
this.cacheManager.generateAccountKey(request.account),
|
||||
request.correlationId
|
||||
),
|
||||
accessToken: cachedAccessToken,
|
||||
idToken: this.cacheManager.getIdToken(
|
||||
request.account,
|
||||
request.correlationId,
|
||||
tokenKeys,
|
||||
requestTenantId
|
||||
),
|
||||
refreshToken: null,
|
||||
appMetadata: this.cacheManager.readAppMetadataFromCache(
|
||||
environment,
|
||||
request.correlationId
|
||||
),
|
||||
};
|
||||
|
||||
this.setCacheOutcome(lastCacheOutcome, request.correlationId);
|
||||
|
||||
if (this.config.serverTelemetryManager) {
|
||||
this.config.serverTelemetryManager.incrementCacheHits();
|
||||
}
|
||||
|
||||
return [
|
||||
await invokeAsync(
|
||||
this.generateResultFromCacheRecord.bind(this),
|
||||
PerformanceEvents.SilentFlowClientGenerateResultFromCacheRecord,
|
||||
this.logger,
|
||||
this.performanceClient,
|
||||
request.correlationId
|
||||
)(cacheRecord, request),
|
||||
lastCacheOutcome,
|
||||
];
|
||||
}
|
||||
|
||||
private setCacheOutcome(
|
||||
cacheOutcome: CacheOutcome,
|
||||
correlationId: string
|
||||
): void {
|
||||
this.serverTelemetryManager?.setCacheOutcome(cacheOutcome);
|
||||
this.performanceClient?.addFields(
|
||||
{
|
||||
cacheOutcome: cacheOutcome,
|
||||
},
|
||||
correlationId
|
||||
);
|
||||
if (cacheOutcome !== CacheOutcome.NOT_APPLICABLE) {
|
||||
this.logger.info(
|
||||
`Token refresh is required due to cache outcome: '${cacheOutcome}'`,
|
||||
correlationId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to build response object from the CacheRecord
|
||||
* @param cacheRecord
|
||||
*/
|
||||
private async generateResultFromCacheRecord(
|
||||
cacheRecord: CacheRecord,
|
||||
request: CommonSilentFlowRequest
|
||||
): Promise<AuthenticationResult> {
|
||||
let idTokenClaims: TokenClaims | undefined;
|
||||
if (cacheRecord.idToken) {
|
||||
idTokenClaims = extractTokenClaims(
|
||||
cacheRecord.idToken.secret,
|
||||
this.config.cryptoInterface.base64Decode
|
||||
);
|
||||
}
|
||||
|
||||
// token max_age check
|
||||
if (request.maxAge || request.maxAge === 0) {
|
||||
const authTime = idTokenClaims?.auth_time;
|
||||
if (!authTime) {
|
||||
throw createClientAuthError(
|
||||
ClientAuthErrorCodes.authTimeNotFound
|
||||
);
|
||||
}
|
||||
|
||||
checkMaxAge(authTime, request.maxAge);
|
||||
}
|
||||
|
||||
return ResponseHandler.generateAuthenticationResult(
|
||||
this.cryptoUtils,
|
||||
this.authority,
|
||||
cacheRecord,
|
||||
true,
|
||||
request,
|
||||
this.performanceClient,
|
||||
idTokenClaims
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user