Estructura inicial del proyecto

This commit is contained in:
2026-06-02 16:57:08 +00:00
commit 8b306b9afc
9864 changed files with 1435687 additions and 0 deletions

View File

@ -0,0 +1,183 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { TokenClaims } from "./TokenClaims.js";
export type DataBoundary = "EU" | "None";
/**
* Account object with the following signature:
* - homeAccountId - Home account identifier for this account object
* - environment - Entity which issued the token represented by the domain of the issuer (e.g. login.microsoftonline.com)
* - tenantId - Full tenant or organizational id that this account belongs to
* - username - preferred_username claim of the id_token that represents this account
* - upn - The user's UPN used to populate the account username in cases where preferred_username is not present in the ID token claims.
* - localAccountId - Local, tenant-specific account identifer for this account object, usually used in legacy cases
* - name - Full name for the account, including given name and family name
* - idToken - raw ID token
* - idTokenClaims - Object contains claims from ID token
* - nativeAccountId - The user's native account ID
* - tenantProfiles - Map of tenant profile objects for each tenant that the account has authenticated with in the browser
* - dataBoundary - Data boundary extracted from clientInfo
*/
export type AccountInfo = {
homeAccountId: string;
environment: string;
tenantId: string;
username: string;
localAccountId: string;
loginHint?: string;
name?: string;
upn?: string;
idToken?: string;
idTokenClaims?: TokenClaims & {
[key: string]:
| string
| number
| string[]
| object
| undefined
| unknown;
};
nativeAccountId?: string;
authorityType?: string;
tenantProfiles?: Map<string, TenantProfile>;
dataBoundary?: DataBoundary;
};
/**
* Account details that vary across tenants for the same user
*/
export type TenantProfile = Pick<
AccountInfo,
"tenantId" | "localAccountId" | "name" | "username" | "loginHint" | "upn"
> & {
/**
* - isHomeTenant - True if this is the home tenant profile of the account, false if it's a guest tenant profile
*/
isHomeTenant?: boolean;
};
export type ActiveAccountFilters = {
homeAccountId: string;
localAccountId: string;
tenantId?: string;
};
/**
* Returns true if tenantId matches the utid portion of homeAccountId
* @param tenantId
* @param homeAccountId
* @returns
*/
export function tenantIdMatchesHomeTenant(
tenantId?: string,
homeAccountId?: string
): boolean {
return (
!!tenantId &&
!!homeAccountId &&
tenantId === homeAccountId.split(".")[1]
);
}
/**
* Build tenant profile
* @param homeAccountId - Home account identifier for this account object
* @param localAccountId - Local account identifer for this account object
* @param tenantId - Full tenant or organizational id that this account belongs to
* @param idTokenClaims - Claims from the ID token
* @returns
*/
export function buildTenantProfile(
homeAccountId: string,
localAccountId: string,
tenantId: string,
idTokenClaims?: TokenClaims
): TenantProfile {
if (idTokenClaims) {
const {
oid,
sub,
tid,
name,
tfp,
acr,
preferred_username,
upn,
login_hint,
} = idTokenClaims;
/**
* Since there is no way to determine if the authority is AAD or B2C, we exhaust all the possible claims that can serve as tenant ID with the following precedence:
* tid - TenantID claim that identifies the tenant that issued the token in AAD. Expected in all AAD ID tokens, not present in B2C ID Tokens.
* tfp - Trust Framework Policy claim that identifies the policy that was used to authenticate the user. Functions as tenant for B2C scenarios.
* acr - Authentication Context Class Reference claim used only with older B2C policies. Fallback in case tfp is not present, but likely won't be present anyway.
*/
const tenantId = tid || tfp || acr || "";
return {
tenantId: tenantId,
localAccountId: oid || sub || "",
name: name,
username: preferred_username || upn || "",
loginHint: login_hint,
isHomeTenant: tenantIdMatchesHomeTenant(tenantId, homeAccountId),
upn: upn,
};
} else {
return {
tenantId,
localAccountId,
username: "",
isHomeTenant: tenantIdMatchesHomeTenant(tenantId, homeAccountId),
};
}
}
/**
* Replaces account info that varies by tenant profile sourced from the ID token claims passed in with the tenant-specific account info
* @param baseAccountInfo
* @param idTokenClaims
* @returns
*/
export function updateAccountTenantProfileData(
baseAccountInfo: AccountInfo,
tenantProfile?: TenantProfile,
idTokenClaims?: TokenClaims,
idTokenSecret?: string
): AccountInfo {
let updatedAccountInfo = baseAccountInfo;
// Tenant Profile overrides passed in account info
if (tenantProfile) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { isHomeTenant, ...tenantProfileOverride } = tenantProfile;
updatedAccountInfo = { ...baseAccountInfo, ...tenantProfileOverride };
}
// ID token claims override passed in account info and tenant profile
if (idTokenClaims) {
// Ignore isHomeTenant which is a utility property of tenant profile but not required in base account info
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { isHomeTenant, ...claimsSourcedTenantProfile } =
buildTenantProfile(
baseAccountInfo.homeAccountId,
baseAccountInfo.localAccountId,
baseAccountInfo.tenantId,
idTokenClaims
);
updatedAccountInfo = {
...updatedAccountInfo,
...claimsSourcedTenantProfile,
idTokenClaims: idTokenClaims,
idToken: idTokenSecret,
};
return updatedAccountInfo;
}
return updatedAccountInfo;
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { TokenClaims } from "./TokenClaims.js";
import {
createClientAuthError,
ClientAuthErrorCodes,
} from "../error/ClientAuthError.js";
/**
* Extract token by decoding the rawToken
*
* @param encodedToken
*/
export function extractTokenClaims(
encodedToken: string,
base64Decode: (input: string) => string
): TokenClaims {
const jswPayload = getJWSPayload(encodedToken);
// token will be decoded to get the username
try {
// base64Decode() should throw an error if there is an issue
const base64Decoded = base64Decode(jswPayload);
return JSON.parse(base64Decoded) as TokenClaims;
} catch (err) {
throw createClientAuthError(ClientAuthErrorCodes.tokenParsingError);
}
}
/**
* Check if the signin_state claim contains "kmsi"
* @param idTokenClaims
* @returns
*/
export function isKmsi(idTokenClaims: TokenClaims): boolean {
if (!idTokenClaims.signin_state) {
return false;
}
/**
* Signin_state claim known values:
* dvc_mngd - device is managed
* dvc_dmjd - device is domain joined
* kmsi - user opted to "keep me signed in"
* inknownntwk - Request made inside a known network. Don't use this, use CAE instead.
*/
const kmsiClaims = ["kmsi", "dvc_dmjd"]; // There are some cases where kmsi may not be returned but persistent storage is still OK - allow dvc_dmjd as well
return idTokenClaims.signin_state.some((value) =>
kmsiClaims.includes(value.trim().toLowerCase())
);
}
/**
* decode a JWT
*
* @param authToken
*/
export function getJWSPayload(authToken: string): string {
if (!authToken) {
throw createClientAuthError(ClientAuthErrorCodes.nullOrEmptyToken);
}
const tokenPartsRegex = /^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/;
const matches = tokenPartsRegex.exec(authToken);
if (!matches || matches.length < 4) {
throw createClientAuthError(ClientAuthErrorCodes.tokenParsingError);
}
/**
* const crackedToken = {
* header: matches[1],
* JWSPayload: matches[2],
* JWSSig: matches[3],
* };
*/
return matches[2];
}
/**
* Determine if the token's max_age has transpired
*/
export function checkMaxAge(authTime: number, maxAge: number): void {
/*
* per https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
* To force an immediate re-authentication: If an app requires that a user re-authenticate prior to access,
* provide a value of 0 for the max_age parameter and the AS will force a fresh login.
*/
const fiveMinuteSkew = 300000; // five minutes in milliseconds
if (maxAge === 0 || Date.now() - fiveMinuteSkew > authTime + maxAge) {
throw createClientAuthError(ClientAuthErrorCodes.maxAgeTranspired);
}
}

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
export type CcsCredential = {
credential: string;
type: CcsCredentialType;
};
export const CcsCredentialType = {
HOME_ACCOUNT_ID: "home_account_id",
UPN: "UPN",
} as const;
export type CcsCredentialType =
(typeof CcsCredentialType)[keyof typeof CcsCredentialType];

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
export type ClientAssertionConfig = {
clientId: string;
tokenEndpoint?: string;
};
export type ClientAssertionCallback = (
config: ClientAssertionConfig
) => Promise<string>;
/**
* Client Assertion credential for Confidential Clients
*/
export type ClientAssertion = {
assertion: string | ClientAssertionCallback;
assertionType: string;
};
/**
* Client Credentials set for Confidential Clients
*/
export type ClientCredentials = {
clientSecret?: string;
clientAssertion?: ClientAssertion;
};

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
createClientAuthError,
ClientAuthErrorCodes,
} from "../error/ClientAuthError.js";
import * as Constants from "../utils/Constants.js";
/**
* Client info object which consists of:
* uid: user id
* utid: tenant id
* xms_tdbr: optional, only for non-US tenants
*/
export type ClientInfo = {
uid: string;
utid: string;
xms_tdbr?: string;
};
/**
* Function to build a client info object from server clientInfo string
* @param rawClientInfo
* @param crypto
*/
export function buildClientInfo(
rawClientInfo: string,
base64Decode: (input: string) => string
): ClientInfo {
if (!rawClientInfo) {
throw createClientAuthError(ClientAuthErrorCodes.clientInfoEmptyError);
}
try {
const decodedClientInfo: string = base64Decode(rawClientInfo);
return JSON.parse(decodedClientInfo) as ClientInfo;
} catch (e) {
throw createClientAuthError(
ClientAuthErrorCodes.clientInfoDecodingError
);
}
}
/**
* Function to build a client info object from cached homeAccountId string
* @param homeAccountId
*/
export function buildClientInfoFromHomeAccountId(
homeAccountId: string
): ClientInfo {
if (!homeAccountId) {
throw createClientAuthError(
ClientAuthErrorCodes.clientInfoDecodingError
);
}
const clientInfoParts: string[] = homeAccountId.split(
Constants.CLIENT_INFO_SEPARATOR,
2
);
return {
uid: clientInfoParts[0],
utid: clientInfoParts.length < 2 ? "" : clientInfoParts[1],
};
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* Type which describes Id Token claims known by MSAL.
*/
export type TokenClaims = {
/**
* Audience
*/
aud?: string;
/**
* Issuer
*/
iss?: string;
/**
* Issued at
*/
iat?: number;
/**
* Not valid before
*/
nbf?: number;
/**
* Immutable object identifier, this ID uniquely identifies the user across applications
*/
oid?: string;
/**
* Immutable subject identifier, this is a pairwise identifier - it is unique to a particular application ID
*/
sub?: string;
/**
* Users' tenant or '9188040d-6c67-4c5b-b112-36a304b66dad' for personal accounts.
*/
tid?: string;
/**
* Trusted Framework Policy (B2C) The name of the policy that was used to acquire the ID token.
*/
tfp?: string;
/**
* Authentication Context Class Reference (B2C) Used only with older policies.
*/
acr?: string;
ver?: string;
upn?: string;
preferred_username?: string;
login_hint?: string;
/**
* Contains KMSI (Keep Me Signed In) status among other things
*/
signin_state?: Array<string>;
emails?: string[];
name?: string;
nonce?: string;
/**
* Expiration
*/
exp?: number;
home_oid?: string;
sid?: string;
cloud_instance_host_name?: string;
cnf?: {
kid: string;
};
x5c_ca?: string[];
ts?: number;
at?: string;
u?: string;
p?: string;
m?: string;
roles?: string[];
amr?: string[];
idp?: string;
auth_time?: number;
/**
* Region of the resource tenant
*/
tenant_region_scope?: string;
tenant_region_sub_scope?: string;
};
/**
* Gets tenantId from available ID token claims to set as credential realm with the following precedence:
* 1. tid - if the token is acquired from an Azure AD tenant tid will be present
* 2. tfp - if the token is acquired from a modern B2C tenant tfp should be present
* 3. acr - if the token is acquired from a legacy B2C tenant acr should be present
* Downcased to match the realm case-insensitive comparison requirements
* @param idTokenClaims
* @returns
*/
export function getTenantIdFromIdTokenClaims(
idTokenClaims?: TokenClaims
): string | null {
if (idTokenClaims) {
const tenantId =
idTokenClaims.tid || idTokenClaims.tfp || idTokenClaims.acr;
return tenantId || null;
}
return null;
}