This commit is contained in:
RochesterX
2025-11-12 10:13:24 -05:00
parent d5b0f97adb
commit 6e820464d5
9761 changed files with 706938 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
ClientAuthErrorCodes,
createClientAuthError,
} from "../error/ClientAuthError.js";
import { NetworkResponse } from "./NetworkResponse.js";
/**
* Options allowed by network request APIs.
*/
export type NetworkRequestOptions = {
headers?: Record<string, string>;
body?: string;
};
/**
* Client network interface to send backend requests.
* @interface
*/
export interface INetworkModule {
/**
* Interface function for async network "GET" requests. Based on the Fetch standard: https://fetch.spec.whatwg.org/
* @param url
* @param requestParams
* @param enableCaching
*/
sendGetRequestAsync<T>(
url: string,
options?: NetworkRequestOptions,
timeout?: number
): Promise<NetworkResponse<T>>;
/**
* Interface function for async network "POST" requests. Based on the Fetch standard: https://fetch.spec.whatwg.org/
* @param url
* @param requestParams
* @param enableCaching
*/
sendPostRequestAsync<T>(
url: string,
options?: NetworkRequestOptions
): Promise<NetworkResponse<T>>;
}
export const StubbedNetworkModule: INetworkModule = {
sendGetRequestAsync: () => {
return Promise.reject(
createClientAuthError(ClientAuthErrorCodes.methodNotImplemented)
);
},
sendPostRequestAsync: () => {
return Promise.reject(
createClientAuthError(ClientAuthErrorCodes.methodNotImplemented)
);
},
};

View File

@@ -0,0 +1,10 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
export type NetworkResponse<T> = {
headers: Record<string, string>;
body: T;
status: number;
};

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { ShrOptions } from "../crypto/SignedHttpRequest.js";
import { BaseAuthRequest } from "../request/BaseAuthRequest.js";
import { AuthenticationScheme } from "../utils/Constants.js";
/**
* Type representing a unique request thumbprint.
*/
export type RequestThumbprint = {
clientId: string;
authority: string;
scopes: Array<string>;
homeAccountIdentifier?: string;
claims?: string;
authenticationScheme?: AuthenticationScheme;
resourceRequestMethod?: string;
resourceRequestUri?: string;
shrClaims?: string;
sshKid?: string;
shrOptions?: ShrOptions;
embeddedClientId?: string;
};
export function getRequestThumbprint(
clientId: string,
request: BaseAuthRequest,
homeAccountId?: string
): RequestThumbprint {
return {
clientId: clientId,
authority: request.authority,
scopes: request.scopes,
homeAccountIdentifier: homeAccountId,
claims: request.claims,
authenticationScheme: request.authenticationScheme,
resourceRequestMethod: request.resourceRequestMethod,
resourceRequestUri: request.resourceRequestUri,
shrClaims: request.shrClaims,
sshKid: request.sshKid,
embeddedClientId:
request.embeddedClientId || request.tokenBodyParameters?.clientId,
};
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { NetworkResponse } from "./NetworkResponse.js";
import { ServerAuthorizationTokenResponse } from "../response/ServerAuthorizationTokenResponse.js";
import {
HeaderNames,
ThrottlingConstants,
Constants,
} from "../utils/Constants.js";
import { CacheManager } from "../cache/CacheManager.js";
import { ServerError } from "../error/ServerError.js";
import {
getRequestThumbprint,
RequestThumbprint,
} from "./RequestThumbprint.js";
import { ThrottlingEntity } from "../cache/entities/ThrottlingEntity.js";
import { BaseAuthRequest } from "../request/BaseAuthRequest.js";
/** @internal */
export class ThrottlingUtils {
/**
* Prepares a RequestThumbprint to be stored as a key.
* @param thumbprint
*/
static generateThrottlingStorageKey(thumbprint: RequestThumbprint): string {
return `${ThrottlingConstants.THROTTLING_PREFIX}.${JSON.stringify(
thumbprint
)}`;
}
/**
* Performs necessary throttling checks before a network request.
* @param cacheManager
* @param thumbprint
*/
static preProcess(
cacheManager: CacheManager,
thumbprint: RequestThumbprint,
correlationId: string
): void {
const key = ThrottlingUtils.generateThrottlingStorageKey(thumbprint);
const value = cacheManager.getThrottlingCache(key);
if (value) {
if (value.throttleTime < Date.now()) {
cacheManager.removeItem(key, correlationId);
return;
}
throw new ServerError(
value.errorCodes?.join(" ") || Constants.EMPTY_STRING,
value.errorMessage,
value.subError
);
}
}
/**
* Performs necessary throttling checks after a network request.
* @param cacheManager
* @param thumbprint
* @param response
*/
static postProcess(
cacheManager: CacheManager,
thumbprint: RequestThumbprint,
response: NetworkResponse<ServerAuthorizationTokenResponse>,
correlationId: string
): void {
if (
ThrottlingUtils.checkResponseStatus(response) ||
ThrottlingUtils.checkResponseForRetryAfter(response)
) {
const thumbprintValue: ThrottlingEntity = {
throttleTime: ThrottlingUtils.calculateThrottleTime(
parseInt(response.headers[HeaderNames.RETRY_AFTER])
),
error: response.body.error,
errorCodes: response.body.error_codes,
errorMessage: response.body.error_description,
subError: response.body.suberror,
};
cacheManager.setThrottlingCache(
ThrottlingUtils.generateThrottlingStorageKey(thumbprint),
thumbprintValue,
correlationId
);
}
}
/**
* Checks a NetworkResponse object's status codes against 429 or 5xx
* @param response
*/
static checkResponseStatus(
response: NetworkResponse<ServerAuthorizationTokenResponse>
): boolean {
return (
response.status === 429 ||
(response.status >= 500 && response.status < 600)
);
}
/**
* Checks a NetworkResponse object's RetryAfter header
* @param response
*/
static checkResponseForRetryAfter(
response: NetworkResponse<ServerAuthorizationTokenResponse>
): boolean {
if (response.headers) {
return (
response.headers.hasOwnProperty(HeaderNames.RETRY_AFTER) &&
(response.status < 200 || response.status >= 300)
);
}
return false;
}
/**
* Calculates the Unix-time value for a throttle to expire given throttleTime in seconds.
* @param throttleTime
*/
static calculateThrottleTime(throttleTime: number): number {
const time = throttleTime <= 0 ? 0 : throttleTime;
const currentSeconds = Date.now() / 1000;
return Math.floor(
Math.min(
currentSeconds +
(time || ThrottlingConstants.DEFAULT_THROTTLE_TIME_SECONDS),
currentSeconds +
ThrottlingConstants.DEFAULT_MAX_THROTTLE_TIME_SECONDS
) * 1000
);
}
static removeThrottle(
cacheManager: CacheManager,
clientId: string,
request: BaseAuthRequest,
homeAccountIdentifier?: string
): void {
const thumbprint = getRequestThumbprint(
clientId,
request,
homeAccountIdentifier
);
const key = this.generateThrottlingStorageKey(thumbprint);
cacheManager.removeItem(key, request.correlationId);
}
}