Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AccountInfo } from "../account/AccountInfo.js";
/**
* Result returned from the authority's token endpoint.
* - uniqueId - `oid` or `sub` claim from ID token
* - tenantId - `tid` claim from ID token
* - scopes - Scopes that are validated for the respective token
* - account - An account object representation of the currently signed-in user
* - idToken - Id token received as part of the response
* - idTokenClaims - MSAL-relevant ID token claims
* - accessToken - Access token or SSH certificate received as part of the response
* - fromCache - Boolean denoting whether token came from cache
* - expiresOn - Javascript Date object representing relative expiration of access token
* - extExpiresOn - Javascript Date object representing extended relative expiration of access token in case of server outage
* - refreshOn - Javascript Date object representing relative time until an access token must be refreshed
* - state - Value passed in by user in request
* - familyId - Family ID identifier, usually only used for refresh tokens
* - requestId - Request ID returned as part of the response
*/
export type AuthenticationResult = {
authority: string;
uniqueId: string;
tenantId: string;
scopes: Array<string>;
account: AccountInfo | null;
idToken: string;
idTokenClaims: object;
accessToken: string;
fromCache: boolean;
expiresOn: Date | null;
extExpiresOn?: Date;
refreshOn?: Date;
tokenType: string;
correlationId: string;
requestId?: string;
state?: string;
familyId?: string;
cloudGraphHostName?: string;
msGraphHost?: string;
code?: string;
fromNativeBroker?: boolean;
};
@@ -0,0 +1,18 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Response returned after processing the code response query string or fragment.
*/
export type AuthorizationCodePayload = {
code: string;
cloud_instance_name?: string;
cloud_instance_host_name?: string;
cloud_graph_host_name?: string;
msgraph_host?: string;
state?: string;
nonce?: string;
client_info?: string;
};
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* DeviceCode returned by the security token service device code endpoint containing information necessary for device code flow.
* - userCode: code which user needs to provide when authenticating at the verification URI
* - deviceCode: code which should be included in the request for the access token
* - verificationUri: URI where user can authenticate
* - expiresIn: expiration time of the device code in seconds
* - interval: interval at which the STS should be polled at
* - message: message which should be displayed to the user
*/
export type DeviceCodeResponse = {
userCode: string;
deviceCode: string;
verificationUri: string;
expiresIn: number;
interval: number;
message: string;
};
export type ServerDeviceCodeResponse = {
user_code: string;
device_code: string;
verification_uri: string;
expires_in: number;
interval: number;
message: string;
};
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { ServerAuthorizationTokenResponse } from "./ServerAuthorizationTokenResponse.js";
/**
* Response object used for loading external tokens to cache.
* - token_type: Indicates the token type value. The only type that Azure AD supports is Bearer.
* - scope: The scopes that the access_token is valid for.
* - expires_in: How long the access token is valid (in seconds).
* - id_token: A JSON Web Token (JWT). The app can decode the segments of this token to request information about the user who signed in.
* - refresh_token: An OAuth 2.0 refresh token. The app can use this token acquire additional access tokens after the current access token expires.
* - access_token: The requested access token. The app can use this token to authenticate to the secured resource, such as a web API.
* - client_info: Client info object
*/
export type ExternalTokenResponse = Pick<
ServerAuthorizationTokenResponse,
| "token_type"
| "scope"
| "expires_in"
| "ext_expires_in"
| "id_token"
| "refresh_token"
| "refresh_token_expires_in"
| "foci"
> & {
access_token?: string;
client_info?: string;
};
+9
View File
@@ -0,0 +1,9 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
export type IMDSBadResponse = {
error: string;
"newest-versions": Array<string>;
};
+765
View File
@@ -0,0 +1,765 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { ServerAuthorizationTokenResponse } from "./ServerAuthorizationTokenResponse.js";
import { ICrypto } from "../crypto/ICrypto.js";
import {
ClientAuthErrorCodes,
createClientAuthError,
} from "../error/ClientAuthError.js";
import { ServerAuthorizationCodeResponse } from "./ServerAuthorizationCodeResponse.js";
import { Logger } from "../logger/Logger.js";
import { ServerError } from "../error/ServerError.js";
import { ScopeSet } from "../request/ScopeSet.js";
import { AuthenticationResult } from "./AuthenticationResult.js";
import { AccountEntity } from "../cache/entities/AccountEntity.js";
import { Authority } from "../authority/Authority.js";
import { IdTokenEntity } from "../cache/entities/IdTokenEntity.js";
import { AccessTokenEntity } from "../cache/entities/AccessTokenEntity.js";
import { RefreshTokenEntity } from "../cache/entities/RefreshTokenEntity.js";
import {
InteractionRequiredAuthError,
isInteractionRequiredError,
} from "../error/InteractionRequiredAuthError.js";
import { CacheRecord } from "../cache/entities/CacheRecord.js";
import { CacheManager } from "../cache/CacheManager.js";
import { ProtocolUtils, RequestStateObject } from "../utils/ProtocolUtils.js";
import {
AuthenticationScheme,
Constants,
THE_FAMILY_ID,
HttpStatus,
} from "../utils/Constants.js";
import { PopTokenGenerator } from "../crypto/PopTokenGenerator.js";
import { AppMetadataEntity } from "../cache/entities/AppMetadataEntity.js";
import { ICachePlugin } from "../cache/interface/ICachePlugin.js";
import { TokenCacheContext } from "../cache/persistence/TokenCacheContext.js";
import { ISerializableTokenCache } from "../cache/interface/ISerializableTokenCache.js";
import { AuthorizationCodePayload } from "./AuthorizationCodePayload.js";
import { BaseAuthRequest } from "../request/BaseAuthRequest.js";
import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient.js";
import { PerformanceEvents } from "../telemetry/performance/PerformanceEvent.js";
import { checkMaxAge, extractTokenClaims } from "../account/AuthToken.js";
import {
TokenClaims,
getTenantIdFromIdTokenClaims,
} from "../account/TokenClaims.js";
import {
AccountInfo,
buildTenantProfile,
updateAccountTenantProfileData,
} from "../account/AccountInfo.js";
import * as CacheHelpers from "../cache/utils/CacheHelpers.js";
function parseServerErrorNo(
serverResponse: ServerAuthorizationCodeResponse
): string | undefined {
const errorCodePrefix = "code=";
const errorCodePrefixIndex =
serverResponse.error_uri?.lastIndexOf(errorCodePrefix);
return errorCodePrefixIndex && errorCodePrefixIndex >= 0
? serverResponse.error_uri?.substring(
errorCodePrefixIndex + errorCodePrefix.length
)
: undefined;
}
/**
* Class that handles response parsing.
* @internal
*/
export class ResponseHandler {
private clientId: string;
private cacheStorage: CacheManager;
private cryptoObj: ICrypto;
private logger: Logger;
private homeAccountIdentifier: string;
private serializableCache: ISerializableTokenCache | null;
private persistencePlugin: ICachePlugin | null;
private performanceClient?: IPerformanceClient;
constructor(
clientId: string,
cacheStorage: CacheManager,
cryptoObj: ICrypto,
logger: Logger,
serializableCache: ISerializableTokenCache | null,
persistencePlugin: ICachePlugin | null,
performanceClient?: IPerformanceClient
) {
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: ServerAuthorizationCodeResponse,
requestState: string
): void {
if (!serverResponse.state || !requestState) {
throw serverResponse.state
? createClientAuthError(
ClientAuthErrorCodes.stateNotFound,
"Cached State"
)
: createClientAuthError(
ClientAuthErrorCodes.stateNotFound,
"Server State"
);
}
let decodedServerResponseState: string;
let decodedRequestState: string;
try {
decodedServerResponseState = decodeURIComponent(
serverResponse.state
);
} catch (e) {
throw createClientAuthError(
ClientAuthErrorCodes.invalidState,
serverResponse.state
);
}
try {
decodedRequestState = decodeURIComponent(requestState);
} catch (e) {
throw createClientAuthError(
ClientAuthErrorCodes.invalidState,
serverResponse.state
);
}
if (decodedServerResponseState !== decodedRequestState) {
throw createClientAuthError(ClientAuthErrorCodes.stateMismatch);
}
// Check for error
if (
serverResponse.error ||
serverResponse.error_description ||
serverResponse.suberror
) {
const serverErrorNo = parseServerErrorNo(serverResponse);
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 new ServerError(
serverResponse.error || "",
serverResponse.error_description,
serverResponse.suberror,
serverErrorNo
);
}
}
/**
* Function which validates server authorization token response.
* @param serverResponse
* @param refreshAccessToken
*/
validateTokenResponse(
serverResponse: ServerAuthorizationTokenResponse,
refreshAccessToken?: boolean
): void {
// Check for error
if (
serverResponse.error ||
serverResponse.error_description ||
serverResponse.suberror
) {
const errString = `Error(s): ${
serverResponse.error_codes || Constants.NOT_AVAILABLE
} - Timestamp: ${
serverResponse.timestamp || Constants.NOT_AVAILABLE
} - Description: ${
serverResponse.error_description || Constants.NOT_AVAILABLE
} - Correlation ID: ${
serverResponse.correlation_id || Constants.NOT_AVAILABLE
} - Trace ID: ${
serverResponse.trace_id || Constants.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 >= 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,
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: ServerAuthorizationTokenResponse,
authority: Authority,
reqTimestamp: number,
request: BaseAuthRequest,
authCodePayload?: AuthorizationCodePayload,
userAssertionHash?: string,
handlingRefreshTokenResponse?: boolean,
forceCacheRefreshTokenResponse?: boolean,
serverRequestId?: string
): Promise<AuthenticationResult> {
this.performanceClient?.addQueueMeasurement(
PerformanceEvents.HandleServerTokenResponse,
serverTokenResponse.correlation_id
);
// create an idToken object (not entity)
let idTokenClaims: TokenClaims | undefined;
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(
ClientAuthErrorCodes.nonceMismatch
);
}
}
// 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);
}
}
// 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: RequestStateObject | undefined;
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 await ResponseHandler.generateAuthenticationResult(
this.cryptoObj,
authority,
cacheRecord,
false,
request,
idTokenClaims,
requestStateObj,
undefined,
serverRequestId
);
}
}
await this.cacheStorage.saveCacheRecord(
cacheRecord,
request.correlationId,
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
*/
private generateCacheRecord(
serverTokenResponse: ServerAuthorizationTokenResponse,
authority: Authority,
reqTimestamp: number,
request: BaseAuthRequest,
idTokenClaims?: TokenClaims,
userAssertionHash?: string,
authCodePayload?: AuthorizationCodePayload
): CacheRecord {
const env = authority.getPreferredCache();
if (!env) {
throw createClientAuthError(
ClientAuthErrorCodes.invalidCacheEnvironment
);
}
const claimsTenantId = getTenantIdFromIdTokenClaims(idTokenClaims);
// IdToken: non AAD scenarios can have empty realm
let cachedIdToken: IdTokenEntity | undefined;
let cachedAccount: AccountEntity | undefined;
if (serverTokenResponse.id_token && !!idTokenClaims) {
cachedIdToken = CacheHelpers.createIdTokenEntity(
this.homeAccountIdentifier,
env,
serverTokenResponse.id_token,
this.clientId,
claimsTenantId || ""
);
cachedAccount = buildAccountToCache(
this.cacheStorage,
authority,
this.homeAccountIdentifier,
this.cryptoObj.base64Decode,
idTokenClaims,
serverTokenResponse.client_info,
env,
claimsTenantId,
authCodePayload,
undefined, // nativeAccountId
this.logger
);
}
// AccessToken
let cachedAccessToken: AccessTokenEntity | null = 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: number =
(typeof serverTokenResponse.expires_in === "string"
? parseInt(serverTokenResponse.expires_in, 10)
: serverTokenResponse.expires_in) || 0;
const extExpiresIn: number =
(typeof serverTokenResponse.ext_expires_in === "string"
? parseInt(serverTokenResponse.ext_expires_in, 10)
: serverTokenResponse.ext_expires_in) || 0;
const refreshIn: number | undefined =
(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 = CacheHelpers.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,
request.claims,
request.requestedClaimsHash
);
}
// refreshToken
let cachedRefreshToken: RefreshTokenEntity | null = null;
if (serverTokenResponse.refresh_token) {
let rtExpiresOn: number | undefined;
if (serverTokenResponse.refresh_token_expires_in) {
const rtExpiresIn: number =
typeof serverTokenResponse.refresh_token_expires_in ===
"string"
? parseInt(
serverTokenResponse.refresh_token_expires_in,
10
)
: serverTokenResponse.refresh_token_expires_in;
rtExpiresOn = reqTimestamp + rtExpiresIn;
}
cachedRefreshToken = CacheHelpers.createRefreshTokenEntity(
this.homeAccountIdentifier,
env,
serverTokenResponse.refresh_token,
this.clientId,
serverTokenResponse.foci,
userAssertionHash,
rtExpiresOn
);
}
// appMetadata
let cachedAppMetadata: AppMetadataEntity | null = 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: ICrypto,
authority: Authority,
cacheRecord: CacheRecord,
fromTokenCache: boolean,
request: BaseAuthRequest,
idTokenClaims?: TokenClaims,
requestState?: RequestStateObject,
serverTokenResponse?: ServerAuthorizationTokenResponse,
requestId?: string
): Promise<AuthenticationResult> {
let accessToken: string = Constants.EMPTY_STRING;
let responseScopes: Array<string> = [];
let expiresOn: Date | null = null;
let extExpiresOn: Date | undefined;
let refreshOn: Date | undefined;
let familyId: string = Constants.EMPTY_STRING;
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: PopTokenGenerator =
new PopTokenGenerator(cryptoObj);
const { secret, keyId } = cacheRecord.accessToken;
if (!keyId) {
throw createClientAuthError(
ClientAuthErrorCodes.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: AccountInfo | null = cacheRecord.account
? updateAccountTenantProfileData(
cacheRecord.account.getAccountInfo(),
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 || 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 function buildAccountToCache(
cacheStorage: CacheManager,
authority: Authority,
homeAccountId: string,
base64Decode: (input: string) => string,
idTokenClaims?: TokenClaims,
clientInfo?: string,
environment?: string,
claimsTenantId?: string | null,
authCodePayload?: AuthorizationCodePayload,
nativeAccountId?: string,
logger?: Logger
): AccountEntity {
logger?.verbose("setCachedAccount called");
// Check if base account is already cached
const accountKeys = cacheStorage.getAccountKeys();
const baseAccountKey = accountKeys.find((accountKey: string) => {
return accountKey.startsWith(homeAccountId);
});
let cachedAccount: AccountEntity | null = null;
if (baseAccountKey) {
cachedAccount = cacheStorage.getAccount(baseAccountKey);
}
const baseAccount =
cachedAccount ||
AccountEntity.createAccount(
{
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;
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Deserialized response object from server authorization code request.
* - code: authorization code from server
* - client_info: client info object
* - state: OAuth2 request state
* - error: error sent back in hash
* - error: description
*/
export type ServerAuthorizationCodeResponse = {
// Success case
code?: string;
client_info?: string;
state?: string;
cloud_instance_name?: string;
cloud_instance_host_name?: string;
cloud_graph_host_name?: string;
msgraph_host?: string;
// Error case
error?: string;
error_uri?: string;
error_description?: string;
suberror?: string;
timestamp?: string;
trace_id?: string;
correlation_id?: string;
claims?: string;
// Native Account ID
accountId?: string;
};
@@ -0,0 +1,55 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { AuthenticationScheme } from "../utils/Constants.js";
/**
* Deserialized response object from server authorization code request.
* - token_type: Indicates the token type value. Can be either Bearer or pop.
* - scope: The scopes that the access_token is valid for.
* - expires_in: How long the access token is valid (in seconds).
* - refresh_in: Duration afer which a token should be renewed, regardless of expiration.
* - ext_expires_in: How long the access token is valid (in seconds) if the server isn't responding.
* - access_token: The requested access token. The app can use this token to authenticate to the secured resource, such as a web API.
* - refresh_token: An OAuth 2.0 refresh token. The app can use this token acquire additional access tokens after the current access token expires.
* - id_token: A JSON Web Token (JWT). The app can decode the segments of this token to request information about the user who signed in.
* - key_id: A string that uniquely identifies a public key that the request is bound to.
*
* In case of error:
* - error: An error code string that can be used to classify types of errors that occur, and can be used to react to errors.
* - error_description: A specific error message that can help a developer identify the root cause of an authentication error.
* - error_codes: A list of STS-specific error codes that can help in diagnostics.
* - timestamp: The time at which the error occurred.
* - trace_id: A unique identifier for the request that can help in diagnostics.
* - correlation_id: A unique identifier for the request that can help in diagnostics across components.
* - status: the network request's response status
*/
export type ServerAuthorizationTokenResponse = {
status?: number;
// Success
token_type?: AuthenticationScheme;
scope?: string;
expires_in?: number;
refresh_in?: number;
ext_expires_in?: number;
access_token?: string;
refresh_token?: string;
refresh_token_expires_in?: number;
id_token?: string;
client_info?: string;
foci?: string;
spa_code?: string;
spa_accountid?: string;
key_id?: string;
// Error
error?: string;
error_description?: string;
error_codes?: Array<string>;
suberror?: string;
timestamp?: string;
trace_id?: string;
correlation_id?: string;
claims?: string;
};