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,59 @@
import type { HttpClient, PipelineOptions } from "@azure/core-rest-pipeline";
import type { AdditionalPolicyConfig } from "@azure-rest/core-client";
/**
* The common set of options that high level clients are expected to expose.
*/
export interface CommonClientOptions extends PipelineOptions {
/**
* The HttpClient that will be used to send HTTP requests.
*/
httpClient?: HttpClient;
/**
* Set to true if the request is sent over HTTP instead of HTTPS
*/
allowInsecureConnection?: boolean;
/**
* Additional policies to include in the HTTP pipeline.
*/
additionalPolicies?: AdditionalPolicyConfig[];
}
/**
* Options for keep alive behavior of HTTP connections.
*/
export interface KeepAliveOptions {
/**
* When true, connections will be kept alive for multiple requests.
* Defaults to true.
*/
enable?: boolean;
}
/**
* Options for how redirect responses are handled.
*/
export interface RedirectOptions {
/**
* When true, redirect responses are followed. Defaults to true.
*/
handleRedirects?: boolean;
/**
* The maximum number of times the redirect URL will be tried before
* failing. Defaults to 20.
*/
maxRetries?: number;
}
/**
* Extended common client options that include keep alive and redirect options.
* This type combines the standard CommonClientOptions with additional options
* for controlling HTTP connection keep-alive and redirect behavior.
*/
export type ExtendedCommonClientOptions = CommonClientOptions & {
/**
* Options to configure keep alive behavior for HTTP connections.
*/
keepAliveOptions?: KeepAliveOptions;
/**
* Options to configure how redirect responses are handled.
*/
redirectOptions?: RedirectOptions;
};
//# sourceMappingURL=extendedClientOptions.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"extendedClientOptions.d.ts","sourceRoot":"","sources":["../../src/extendedClientOptions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;OAEG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;OAEG;IACH,kBAAkB,CAAC,EAAE,sBAAsB,EAAE,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAAG;IAC9D;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC"}

View File

@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export {};
//# sourceMappingURL=extendedClientOptions.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"extendedClientOptions.js","sourceRoot":"","sources":["../../src/extendedClientOptions.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { HttpClient, PipelineOptions } from \"@azure/core-rest-pipeline\";\nimport type { AdditionalPolicyConfig } from \"@azure-rest/core-client\";\n\n/**\n * The common set of options that high level clients are expected to expose.\n */\nexport interface CommonClientOptions extends PipelineOptions {\n /**\n * The HttpClient that will be used to send HTTP requests.\n */\n httpClient?: HttpClient;\n /**\n * Set to true if the request is sent over HTTP instead of HTTPS\n */\n allowInsecureConnection?: boolean;\n /**\n * Additional policies to include in the HTTP pipeline.\n */\n additionalPolicies?: AdditionalPolicyConfig[];\n}\n\n/**\n * Options for keep alive behavior of HTTP connections.\n */\nexport interface KeepAliveOptions {\n /**\n * When true, connections will be kept alive for multiple requests.\n * Defaults to true.\n */\n enable?: boolean;\n}\n\n/**\n * Options for how redirect responses are handled.\n */\nexport interface RedirectOptions {\n /**\n * When true, redirect responses are followed. Defaults to true.\n */\n handleRedirects?: boolean;\n\n /**\n * The maximum number of times the redirect URL will be tried before\n * failing. Defaults to 20.\n */\n maxRetries?: number;\n}\n\n/**\n * Extended common client options that include keep alive and redirect options.\n * This type combines the standard CommonClientOptions with additional options\n * for controlling HTTP connection keep-alive and redirect behavior.\n */\nexport type ExtendedCommonClientOptions = CommonClientOptions & {\n /**\n * Options to configure keep alive behavior for HTTP connections.\n */\n keepAliveOptions?: KeepAliveOptions;\n /**\n * Options to configure how redirect responses are handled.\n */\n redirectOptions?: RedirectOptions;\n};\n"]}

View File

@ -0,0 +1,5 @@
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
export type * from "./extendedClientOptions.js";
export type { AdditionalPolicyConfig } from "@azure-rest/core-client";
//# sourceMappingURL=index.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,mBAAmB,4BAA4B,CAAC;AAChD,YAAY,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC"}

View File

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport * from \"./keyVaultAuthenticationPolicy.js\";\nexport * from \"./parseKeyVaultIdentifier.js\";\nexport type * from \"./extendedClientOptions.js\";\nexport type { AdditionalPolicyConfig } from \"@azure-rest/core-client\";\n"]}

View File

@ -0,0 +1,32 @@
import type { PipelinePolicy } from "@azure/core-rest-pipeline";
import type { TokenCredential } from "@azure/core-auth";
/**
* Additional options for the challenge based authentication policy.
*/
export interface KeyVaultAuthenticationPolicyOptions {
/**
* Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.
*
* Defaults to false.
*/
disableChallengeResourceVerification?: boolean;
}
/**
* Name of the Key Vault authentication policy.
*/
export declare const keyVaultAuthenticationPolicyName = "keyVaultAuthenticationPolicy";
/**
* A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.
*
* Key Vault supports other authentication schemes, but we ensure challenge authentication
* is used by first sending a copy of the request, without authorization or content.
*
* when the challenge is received, it will be authenticated and used to send the original
* request with authorization.
*
* Following the first request of a client, follow-up requests will get the cached token
* if possible.
*
*/
export declare function keyVaultAuthenticationPolicy(credential: TokenCredential, options?: KeyVaultAuthenticationPolicyOptions): PipelinePolicy;
//# sourceMappingURL=keyVaultAuthenticationPolicy.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"keyVaultAuthenticationPolicy.d.ts","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AA8BzE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;OAIG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC;CAChD;AAmBD;;GAEG;AACH,eAAO,MAAM,gCAAgC,iCAAiC,CAAC;AAE/E;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,eAAe,EAC3B,OAAO,GAAE,mCAAwC,GAChD,cAAc,CA2KhB"}

View File

@ -0,0 +1,164 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { parseWWWAuthenticateHeader } from "./parseWWWAuthenticate.js";
import { createTokenCycler } from "./tokenCycler.js";
import { logger } from "./logger.js";
function verifyChallengeResource(scope, request) {
let scopeAsUrl;
try {
scopeAsUrl = new URL(scope);
}
catch (e) {
throw new Error(`The challenge contains invalid scope '${scope}'`);
}
const requestUrl = new URL(request.url);
if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {
throw new Error(`The challenge resource '${scopeAsUrl.hostname}' does not match the requested domain. Set disableChallengeResourceVerification to true in your client options to disable. See https://aka.ms/azsdk/blog/vault-uri for more information.`);
}
}
/**
* Name of the Key Vault authentication policy.
*/
export const keyVaultAuthenticationPolicyName = "keyVaultAuthenticationPolicy";
/**
* A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.
*
* Key Vault supports other authentication schemes, but we ensure challenge authentication
* is used by first sending a copy of the request, without authorization or content.
*
* when the challenge is received, it will be authenticated and used to send the original
* request with authorization.
*
* Following the first request of a client, follow-up requests will get the cached token
* if possible.
*
*/
export function keyVaultAuthenticationPolicy(credential, options = {}) {
const { disableChallengeResourceVerification } = options;
let challengeState = { status: "none" };
const getAccessToken = createTokenCycler(credential);
function requestToOptions(request) {
return {
abortSignal: request.abortSignal,
requestOptions: {
timeout: request.timeout > 0 ? request.timeout : undefined,
},
tracingOptions: request.tracingOptions,
};
}
async function authorizeRequest(request) {
const requestOptions = requestToOptions(request);
switch (challengeState.status) {
case "none":
challengeState = {
status: "started",
originalBody: request.body,
};
request.body = null;
break;
case "started":
break; // Retry, we should not overwrite the original body
case "complete": {
const token = await getAccessToken(challengeState.scopes, {
...requestOptions,
enableCae: true,
tenantId: challengeState.tenantId,
});
if (token) {
request.headers.set("authorization", `Bearer ${token.token}`);
}
break;
}
}
}
async function handleChallenge(request, response, next) {
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
if (request.body === null && challengeState.status === "started") {
// Reset the original body before doing anything else.
// Note: If successful status will be "complete", otherwise "none" will
// restart the process.
request.body = challengeState.originalBody;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
logger.warning("keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.");
return response;
}
const parsedChallenge = parseWWWAuthenticateHeader(challenge);
const scope = parsedChallenge.resource
? parsedChallenge.resource + "/.default"
: parsedChallenge.scope;
if (!scope) {
// Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)
return response;
}
if (!disableChallengeResourceVerification) {
verifyChallengeResource(scope, request);
}
const accessToken = await getAccessToken([scope], {
...getTokenOptions,
enableCae: true,
tenantId: parsedChallenge.tenantId,
});
if (!accessToken) {
// No access token provided, treat as no-op
return response;
}
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
challengeState = {
status: "complete",
scopes: [scope],
tenantId: parsedChallenge.tenantId,
};
// We have a token now, so try send the request again
return next(request);
}
async function handleCaeChallenge(request, response, next) {
// Cannot handle CAE challenge if a regular challenge has not been completed first
if (challengeState.status !== "complete") {
return response;
}
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
return response;
}
const { claims: base64EncodedClaims, error } = parseWWWAuthenticateHeader(challenge);
if (error !== "insufficient_claims" || base64EncodedClaims === undefined) {
return response;
}
const claims = atob(base64EncodedClaims);
const accessToken = await getAccessToken(challengeState.scopes, {
...getTokenOptions,
enableCae: true,
tenantId: challengeState.tenantId,
claims,
});
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
return next(request);
}
async function sendRequest(request, next) {
// Add token if possible
await authorizeRequest(request);
// Try send request (first attempt)
let response = await next(request);
// Handle standard challenge if present
response = await handleChallenge(request, response, next);
// Handle CAE challenge if present
response = await handleCaeChallenge(request, response, next);
return response;
}
return {
name: keyVaultAuthenticationPolicyName,
sendRequest,
};
}
//# sourceMappingURL=keyVaultAuthenticationPolicy.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
export declare const logger: import("@azure/logger").AzureLogger;
//# sourceMappingURL=logger.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,qCAAwC,CAAC"}

View File

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createClientLogger } from "@azure/logger";
export const logger = createClientLogger("keyvault-common");
//# sourceMappingURL=logger.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,CAAC,MAAM,MAAM,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { createClientLogger } from \"@azure/logger\";\n\nexport const logger = createClientLogger(\"keyvault-common\");\n"]}

View File

@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@ -0,0 +1,25 @@
/**
* The parsed components of a Key Vault entity identifier.
*/
export interface KeyVaultEntityIdentifier {
/**
* The vault URI.
*/
vaultUrl: string;
/**
* The version of key/secret/certificate. May be undefined.
*/
version?: string;
/**
* The name of key/secret/certificate.
*/
name: string;
}
/**
* Parses a Key Vault identifier into its components.
*
* @param collection - The collection of the Key Vault identifier.
* @param identifier - The Key Vault identifier to be parsed.
*/
export declare function parseKeyVaultIdentifier(collection: string, identifier: string | undefined): KeyVaultEntityIdentifier;
//# sourceMappingURL=parseKeyVaultIdentifier.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseKeyVaultIdentifier.d.ts","sourceRoot":"","sources":["../../src/parseKeyVaultIdentifier.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,wBAAwB,CAsC1B"}

View File

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* Parses a Key Vault identifier into its components.
*
* @param collection - The collection of the Key Vault identifier.
* @param identifier - The Key Vault identifier to be parsed.
*/
export function parseKeyVaultIdentifier(collection, identifier) {
if (typeof collection !== "string" || !(collection = collection.trim())) {
throw new Error("Invalid collection argument");
}
if (typeof identifier !== "string" || !(identifier = identifier.trim())) {
throw new Error("Invalid identifier argument");
}
let baseUri;
try {
baseUri = new URL(identifier);
}
catch (e) {
throw new Error(`Invalid ${collection} identifier: ${identifier}. Not a valid URI`);
}
// Path is of the form '/collection/name[/version]'
const segments = (baseUri.pathname || "").split("/");
if (segments.length !== 3 && segments.length !== 4) {
throw new Error(`Invalid ${collection} identifier: ${identifier}. Bad number of segments: ${segments.length}`);
}
if (collection !== segments[1]) {
throw new Error(`Invalid ${collection} identifier: ${identifier}. segment [1] should be "${collection}", found "${segments[1]}"`);
}
const vaultUrl = `${baseUri.protocol}//${baseUri.host}`;
const name = segments[2];
const version = segments.length === 4 ? segments[3] : undefined;
return {
vaultUrl,
name,
version,
};
}
//# sourceMappingURL=parseKeyVaultIdentifier.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseKeyVaultIdentifier.js","sourceRoot":"","sources":["../../src/parseKeyVaultIdentifier.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAoBlC;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAkB,EAClB,UAA8B;IAE9B,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,WAAW,UAAU,gBAAgB,UAAU,mBAAmB,CAAC,CAAC;IACtF,CAAC;IAED,mDAAmD;IACnD,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,WAAW,UAAU,gBAAgB,UAAU,6BAA6B,QAAQ,CAAC,MAAM,EAAE,CAC9F,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,WAAW,UAAU,gBAAgB,UAAU,4BAA4B,UAAU,aAAa,QAAQ,CAAC,CAAC,CAAC,GAAG,CACjH,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;IACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,OAAO;QACL,QAAQ;QACR,IAAI;QACJ,OAAO;KACR,CAAC;AACJ,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * The parsed components of a Key Vault entity identifier.\n */\nexport interface KeyVaultEntityIdentifier {\n /**\n * The vault URI.\n */\n vaultUrl: string;\n /**\n * The version of key/secret/certificate. May be undefined.\n */\n version?: string;\n /**\n * The name of key/secret/certificate.\n */\n name: string;\n}\n\n/**\n * Parses a Key Vault identifier into its components.\n *\n * @param collection - The collection of the Key Vault identifier.\n * @param identifier - The Key Vault identifier to be parsed.\n */\nexport function parseKeyVaultIdentifier(\n collection: string,\n identifier: string | undefined,\n): KeyVaultEntityIdentifier {\n if (typeof collection !== \"string\" || !(collection = collection.trim())) {\n throw new Error(\"Invalid collection argument\");\n }\n\n if (typeof identifier !== \"string\" || !(identifier = identifier.trim())) {\n throw new Error(\"Invalid identifier argument\");\n }\n\n let baseUri;\n try {\n baseUri = new URL(identifier);\n } catch (e: any) {\n throw new Error(`Invalid ${collection} identifier: ${identifier}. Not a valid URI`);\n }\n\n // Path is of the form '/collection/name[/version]'\n const segments = (baseUri.pathname || \"\").split(\"/\");\n if (segments.length !== 3 && segments.length !== 4) {\n throw new Error(\n `Invalid ${collection} identifier: ${identifier}. Bad number of segments: ${segments.length}`,\n );\n }\n\n if (collection !== segments[1]) {\n throw new Error(\n `Invalid ${collection} identifier: ${identifier}. segment [1] should be \"${collection}\", found \"${segments[1]}\"`,\n );\n }\n\n const vaultUrl = `${baseUri.protocol}//${baseUri.host}`;\n const name = segments[2];\n const version = segments.length === 4 ? segments[3] : undefined;\n return {\n vaultUrl,\n name,\n version,\n };\n}\n"]}

View File

@ -0,0 +1,43 @@
/**
* Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.
*/
export interface WWWAuthenticate {
/**
* The authorization parameter, if present.
*/
authorization?: string;
/**
* The authorization_url parameter, if present.
*/
authorization_url?: string;
/**
* The resource parameter, if present.
*/
resource?: string;
/**
* The scope parameter, if present.
*/
scope?: string;
/**
* The tenantId parameter, if present.
*/
tenantId?: string;
/**
* The claims parameter, if present.
*/
claims?: string;
/**
* The error parameter, if present.
*/
error?: string;
}
/**
* Parses an WWW-Authenticate response header.
* This transforms a string value like:
* `Bearer authorization="https://some.url/tenantId", resource="https://some.url"`
* into an object like:
* `{ authorization: "https://some.url/tenantId", resource: "https://some.url" }`
* @param headerValue - String value in the WWW-Authenticate header
*/
export declare function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate;
//# sourceMappingURL=parseWWWAuthenticate.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseWWWAuthenticate.d.ts","sourceRoot":"","sources":["../../src/parseWWWAuthenticate.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAYD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA2B/E"}

View File

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const validWWWAuthenticateProperties = [
"authorization",
"authorization_url",
"resource",
"scope",
"tenantId",
"claims",
"error",
];
/**
* Parses an WWW-Authenticate response header.
* This transforms a string value like:
* `Bearer authorization="https://some.url/tenantId", resource="https://some.url"`
* into an object like:
* `{ authorization: "https://some.url/tenantId", resource: "https://some.url" }`
* @param headerValue - String value in the WWW-Authenticate header
*/
export function parseWWWAuthenticateHeader(headerValue) {
const pairDelimiter = /,? +/;
const parsed = headerValue.split(pairDelimiter).reduce((kvPairs, p) => {
if (p.match(/\w="/)) {
// 'sampleKey="sample_value"' -> [sampleKey, "sample_value"] -> { sampleKey: sample_value }
const [key, ...value] = p.split("=");
if (validWWWAuthenticateProperties.includes(key)) {
// The values will be wrapped in quotes, which need to be stripped out.
return { ...kvPairs, [key]: value.join("=").slice(1, -1) };
}
}
return kvPairs;
}, {});
// Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.
if (parsed.authorization) {
try {
const tenantId = new URL(parsed.authorization).pathname.substring(1);
if (tenantId) {
parsed.tenantId = tenantId;
}
}
catch (_) {
throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);
}
}
return parsed;
}
//# sourceMappingURL=parseWWWAuthenticate.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseWWWAuthenticate.js","sourceRoot":"","sources":["../../src/parseWWWAuthenticate.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AA0ClC,MAAM,8BAA8B,GAAuC;IACzE,eAAe;IACf,mBAAmB;IACnB,UAAU;IACV,OAAO;IACP,UAAU;IACV,QAAQ;IACR,OAAO;CACC,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,WAAmB;IAC5D,MAAM,aAAa,GAAG,MAAM,CAAC;IAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,MAAM,CAAkB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QACrF,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,2FAA2F;YAC3F,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,8BAA8B,CAAC,QAAQ,CAAC,GAA4B,CAAC,EAAE,CAAC;gBAC1E,uEAAuE;gBACvE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,sGAAsG;IACtG,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACrE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,aAAa,eAAe,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.\n */\nexport interface WWWAuthenticate {\n /**\n * The authorization parameter, if present.\n */\n authorization?: string;\n\n /**\n * The authorization_url parameter, if present.\n */\n authorization_url?: string;\n\n /**\n * The resource parameter, if present.\n */\n resource?: string;\n\n /**\n * The scope parameter, if present.\n */\n scope?: string;\n\n /**\n * The tenantId parameter, if present.\n */\n tenantId?: string;\n\n /**\n * The claims parameter, if present.\n */\n claims?: string;\n\n /**\n * The error parameter, if present.\n */\n error?: string;\n}\n\nconst validWWWAuthenticateProperties: readonly (keyof WWWAuthenticate)[] = [\n \"authorization\",\n \"authorization_url\",\n \"resource\",\n \"scope\",\n \"tenantId\",\n \"claims\",\n \"error\",\n] as const;\n\n/**\n * Parses an WWW-Authenticate response header.\n * This transforms a string value like:\n * `Bearer authorization=\"https://some.url/tenantId\", resource=\"https://some.url\"`\n * into an object like:\n * `{ authorization: \"https://some.url/tenantId\", resource: \"https://some.url\" }`\n * @param headerValue - String value in the WWW-Authenticate header\n */\nexport function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate {\n const pairDelimiter = /,? +/;\n const parsed = headerValue.split(pairDelimiter).reduce<WWWAuthenticate>((kvPairs, p) => {\n if (p.match(/\\w=\"/)) {\n // 'sampleKey=\"sample_value\"' -> [sampleKey, \"sample_value\"] -> { sampleKey: sample_value }\n const [key, ...value] = p.split(\"=\");\n if (validWWWAuthenticateProperties.includes(key as keyof WWWAuthenticate)) {\n // The values will be wrapped in quotes, which need to be stripped out.\n return { ...kvPairs, [key]: value.join(\"=\").slice(1, -1) };\n }\n }\n return kvPairs;\n }, {});\n\n // Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.\n if (parsed.authorization) {\n try {\n const tenantId = new URL(parsed.authorization).pathname.substring(1);\n if (tenantId) {\n parsed.tenantId = tenantId;\n }\n } catch (_) {\n throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);\n }\n }\n\n return parsed;\n}\n"]}

View File

@ -0,0 +1,45 @@
import type { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-auth";
/**
* A function that gets a promise of an access token and allows providing
* options.
*
* @param options - the options to pass to the underlying token provider
*/
export type AccessTokenGetter = (scopes: string | string[], options: GetTokenOptions) => Promise<AccessToken>;
export interface TokenCyclerOptions {
/**
* The window of time before token expiration during which the token will be
* considered unusable due to risk of the token expiring before sending the
* request.
*
* This will only become meaningful if the refresh fails for over
* (refreshWindow - forcedRefreshWindow) milliseconds.
*/
forcedRefreshWindowInMs: number;
/**
* Interval in milliseconds to retry failed token refreshes.
*/
retryIntervalInMs: number;
/**
* The window of time before token expiration during which
* we will attempt to refresh the token.
*/
refreshWindowInMs: number;
}
export declare const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions;
/**
* Creates a token cycler from a credential, scopes, and optional settings.
*
* A token cycler represents a way to reliably retrieve a valid access token
* from a TokenCredential. It will handle initializing the token, refreshing it
* when it nears expiration, and synchronizes refresh attempts to avoid
* concurrency hazards.
*
* @param credential - the underlying TokenCredential that provides the access
* token
* @param tokenCyclerOptions - optionally override default settings for the cycler
*
* @returns - a function that reliably produces a valid access token
*/
export declare function createTokenCycler(credential: TokenCredential, tokenCyclerOptions?: Partial<TokenCyclerOptions>): AccessTokenGetter;
//# sourceMappingURL=tokenCycler.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"tokenCycler.d.ts","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGtF;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EACzB,OAAO,EAAE,eAAe,KACrB,OAAO,CAAC,WAAW,CAAC,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,uBAAuB,EAAE,MAAM,CAAC;IAChC;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAGD,eAAO,MAAM,sBAAsB,EAAE,kBAIpC,CAAC;AAiDF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAC/C,iBAAiB,CA0HnB"}

View File

@ -0,0 +1,163 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { delay } from "@azure/core-util";
// Default options for the cycler if none are provided
export const DEFAULT_CYCLER_OPTIONS = {
forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires
retryIntervalInMs: 3000, // Allow refresh attempts every 3s
refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry
};
/**
* Converts an an unreliable access token getter (which may resolve with null)
* into an AccessTokenGetter by retrying the unreliable getter in a regular
* interval.
*
* @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.
* @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.
* @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.
* @returns - A promise that, if it resolves, will resolve with an access token.
*/
async function beginRefresh(getAccessToken, retryIntervalInMs, refreshTimeout) {
// This wrapper handles exceptions gracefully as long as we haven't exceeded
// the timeout.
async function tryGetAccessToken() {
if (Date.now() < refreshTimeout) {
try {
return await getAccessToken();
}
catch {
return null;
}
}
else {
const finalToken = await getAccessToken();
// Timeout is up, so throw if it's still null
if (finalToken === null) {
throw new Error("Failed to refresh access token.");
}
return finalToken;
}
}
let token = await tryGetAccessToken();
while (token === null) {
await delay(retryIntervalInMs);
token = await tryGetAccessToken();
}
return token;
}
/**
* Creates a token cycler from a credential, scopes, and optional settings.
*
* A token cycler represents a way to reliably retrieve a valid access token
* from a TokenCredential. It will handle initializing the token, refreshing it
* when it nears expiration, and synchronizes refresh attempts to avoid
* concurrency hazards.
*
* @param credential - the underlying TokenCredential that provides the access
* token
* @param tokenCyclerOptions - optionally override default settings for the cycler
*
* @returns - a function that reliably produces a valid access token
*/
export function createTokenCycler(credential, tokenCyclerOptions) {
let refreshWorker = null;
let token = null;
let tenantId;
const options = {
...DEFAULT_CYCLER_OPTIONS,
...tokenCyclerOptions,
};
/**
* This little holder defines several predicates that we use to construct
* the rules of refreshing the token.
*/
const cycler = {
/**
* Produces true if a refresh job is currently in progress.
*/
get isRefreshing() {
return refreshWorker !== null;
},
/**
* Produces true if the cycler SHOULD refresh (we are within the refresh
* window and not already refreshing)
*/
get shouldRefresh() {
if (cycler.isRefreshing) {
return false;
}
if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {
return true;
}
return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();
},
/**
* Produces true if the cycler MUST refresh (null or nearly-expired
* token).
*/
get mustRefresh() {
return (token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now());
},
};
/**
* Starts a refresh job or returns the existing job if one is already
* running.
*/
function refresh(scopes, getTokenOptions) {
if (!cycler.isRefreshing) {
// We bind `scopes` here to avoid passing it around a lot
const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
// Take advantage of promise chaining to insert an assignment to `token`
// before the refresh can be considered done.
refreshWorker = beginRefresh(tryGetAccessToken, options.retryIntervalInMs,
// If we don't have a token, then we should timeout immediately
token?.expiresOnTimestamp ?? Date.now())
.then((_token) => {
refreshWorker = null;
token = _token;
tenantId = getTokenOptions.tenantId;
return token;
})
.catch((reason) => {
// We also should reset the refresher if we enter a failed state. All
// existing awaiters will throw, but subsequent requests will start a
// new retry chain.
refreshWorker = null;
token = null;
tenantId = undefined;
throw reason;
});
}
return refreshWorker;
}
return async (scopes, tokenOptions) => {
//
// Simple rules:
// - If we MUST refresh, then return the refresh task, blocking
// the pipeline until a token is available.
// - If we SHOULD refresh, then run refresh but don't return it
// (we can still use the cached token).
// - Return the token, since it's fine if we didn't return in
// step 1.
//
const hasClaimChallenge = Boolean(tokenOptions.claims);
const tenantIdChanged = tenantId !== tokenOptions.tenantId;
if (hasClaimChallenge) {
// If we've received a claim, we know the existing token isn't valid
// We want to clear it so that that refresh worker won't use the old expiration time as a timeout
token = null;
}
// If the tenantId passed in token options is different to the one we have
// Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to
// refresh the token with the new tenantId or token.
const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;
if (mustRefresh) {
return refresh(scopes, tokenOptions);
}
if (cycler.shouldRefresh) {
refresh(scopes, tokenOptions);
}
return token;
};
}
//# sourceMappingURL=tokenCycler.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,59 @@
import type { HttpClient, PipelineOptions } from "@azure/core-rest-pipeline";
import type { AdditionalPolicyConfig } from "@azure-rest/core-client";
/**
* The common set of options that high level clients are expected to expose.
*/
export interface CommonClientOptions extends PipelineOptions {
/**
* The HttpClient that will be used to send HTTP requests.
*/
httpClient?: HttpClient;
/**
* Set to true if the request is sent over HTTP instead of HTTPS
*/
allowInsecureConnection?: boolean;
/**
* Additional policies to include in the HTTP pipeline.
*/
additionalPolicies?: AdditionalPolicyConfig[];
}
/**
* Options for keep alive behavior of HTTP connections.
*/
export interface KeepAliveOptions {
/**
* When true, connections will be kept alive for multiple requests.
* Defaults to true.
*/
enable?: boolean;
}
/**
* Options for how redirect responses are handled.
*/
export interface RedirectOptions {
/**
* When true, redirect responses are followed. Defaults to true.
*/
handleRedirects?: boolean;
/**
* The maximum number of times the redirect URL will be tried before
* failing. Defaults to 20.
*/
maxRetries?: number;
}
/**
* Extended common client options that include keep alive and redirect options.
* This type combines the standard CommonClientOptions with additional options
* for controlling HTTP connection keep-alive and redirect behavior.
*/
export type ExtendedCommonClientOptions = CommonClientOptions & {
/**
* Options to configure keep alive behavior for HTTP connections.
*/
keepAliveOptions?: KeepAliveOptions;
/**
* Options to configure how redirect responses are handled.
*/
redirectOptions?: RedirectOptions;
};
//# sourceMappingURL=extendedClientOptions.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"extendedClientOptions.d.ts","sourceRoot":"","sources":["../../src/extendedClientOptions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;OAEG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;OAEG;IACH,kBAAkB,CAAC,EAAE,sBAAsB,EAAE,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAAG;IAC9D;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC"}

View File

@ -0,0 +1,16 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var extendedClientOptions_exports = {};
module.exports = __toCommonJS(extendedClientOptions_exports);
//# sourceMappingURL=extendedClientOptions.js.map

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/extendedClientOptions.ts"],
"sourcesContent": ["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { HttpClient, PipelineOptions } from \"@azure/core-rest-pipeline\";\nimport type { AdditionalPolicyConfig } from \"@azure-rest/core-client\";\n\n/**\n * The common set of options that high level clients are expected to expose.\n */\nexport interface CommonClientOptions extends PipelineOptions {\n /**\n * The HttpClient that will be used to send HTTP requests.\n */\n httpClient?: HttpClient;\n /**\n * Set to true if the request is sent over HTTP instead of HTTPS\n */\n allowInsecureConnection?: boolean;\n /**\n * Additional policies to include in the HTTP pipeline.\n */\n additionalPolicies?: AdditionalPolicyConfig[];\n}\n\n/**\n * Options for keep alive behavior of HTTP connections.\n */\nexport interface KeepAliveOptions {\n /**\n * When true, connections will be kept alive for multiple requests.\n * Defaults to true.\n */\n enable?: boolean;\n}\n\n/**\n * Options for how redirect responses are handled.\n */\nexport interface RedirectOptions {\n /**\n * When true, redirect responses are followed. Defaults to true.\n */\n handleRedirects?: boolean;\n\n /**\n * The maximum number of times the redirect URL will be tried before\n * failing. Defaults to 20.\n */\n maxRetries?: number;\n}\n\n/**\n * Extended common client options that include keep alive and redirect options.\n * This type combines the standard CommonClientOptions with additional options\n * for controlling HTTP connection keep-alive and redirect behavior.\n */\nexport type ExtendedCommonClientOptions = CommonClientOptions & {\n /**\n * Options to configure keep alive behavior for HTTP connections.\n */\n keepAliveOptions?: KeepAliveOptions;\n /**\n * Options to configure how redirect responses are handled.\n */\n redirectOptions?: RedirectOptions;\n};\n"],
"mappings": ";;;;;;;;;;;;;AAAA;AAAA;",
"names": []
}

View File

@ -0,0 +1,5 @@
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
export type * from "./extendedClientOptions.js";
export type { AdditionalPolicyConfig } from "@azure-rest/core-client";
//# sourceMappingURL=index.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,mBAAmB,4BAA4B,CAAC;AAChD,YAAY,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC"}

View File

@ -0,0 +1,24 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var src_exports = {};
module.exports = __toCommonJS(src_exports);
__reExport(src_exports, require("./keyVaultAuthenticationPolicy.js"), module.exports);
__reExport(src_exports, require("./parseKeyVaultIdentifier.js"), module.exports);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
...require("./keyVaultAuthenticationPolicy.js"),
...require("./parseKeyVaultIdentifier.js")
});
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/index.ts"],
"sourcesContent": ["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport * from \"./keyVaultAuthenticationPolicy.js\";\nexport * from \"./parseKeyVaultIdentifier.js\";\nexport type * from \"./extendedClientOptions.js\";\nexport type { AdditionalPolicyConfig } from \"@azure-rest/core-client\";\n"],
"mappings": ";;;;;;;;;;;;;;AAAA;AAAA;AAGA,wBAAc,8CAHd;AAIA,wBAAc,yCAJd;",
"names": []
}

View File

@ -0,0 +1,32 @@
import type { PipelinePolicy } from "@azure/core-rest-pipeline";
import type { TokenCredential } from "@azure/core-auth";
/**
* Additional options for the challenge based authentication policy.
*/
export interface KeyVaultAuthenticationPolicyOptions {
/**
* Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.
*
* Defaults to false.
*/
disableChallengeResourceVerification?: boolean;
}
/**
* Name of the Key Vault authentication policy.
*/
export declare const keyVaultAuthenticationPolicyName = "keyVaultAuthenticationPolicy";
/**
* A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.
*
* Key Vault supports other authentication schemes, but we ensure challenge authentication
* is used by first sending a copy of the request, without authorization or content.
*
* when the challenge is received, it will be authenticated and used to send the original
* request with authorization.
*
* Following the first request of a client, follow-up requests will get the cached token
* if possible.
*
*/
export declare function keyVaultAuthenticationPolicy(credential: TokenCredential, options?: KeyVaultAuthenticationPolicyOptions): PipelinePolicy;
//# sourceMappingURL=keyVaultAuthenticationPolicy.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"keyVaultAuthenticationPolicy.d.ts","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AA8BzE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;OAIG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC;CAChD;AAmBD;;GAEG;AACH,eAAO,MAAM,gCAAgC,iCAAiC,CAAC;AAE/E;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,eAAe,EAC3B,OAAO,GAAE,mCAAwC,GAChD,cAAc,CA2KhB"}

View File

@ -0,0 +1,163 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var keyVaultAuthenticationPolicy_exports = {};
__export(keyVaultAuthenticationPolicy_exports, {
keyVaultAuthenticationPolicy: () => keyVaultAuthenticationPolicy,
keyVaultAuthenticationPolicyName: () => keyVaultAuthenticationPolicyName
});
module.exports = __toCommonJS(keyVaultAuthenticationPolicy_exports);
var import_parseWWWAuthenticate = require("./parseWWWAuthenticate.js");
var import_tokenCycler = require("./tokenCycler.js");
var import_logger = require("./logger.js");
function verifyChallengeResource(scope, request) {
let scopeAsUrl;
try {
scopeAsUrl = new URL(scope);
} catch (e) {
throw new Error(`The challenge contains invalid scope '${scope}'`);
}
const requestUrl = new URL(request.url);
if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {
throw new Error(
`The challenge resource '${scopeAsUrl.hostname}' does not match the requested domain. Set disableChallengeResourceVerification to true in your client options to disable. See https://aka.ms/azsdk/blog/vault-uri for more information.`
);
}
}
const keyVaultAuthenticationPolicyName = "keyVaultAuthenticationPolicy";
function keyVaultAuthenticationPolicy(credential, options = {}) {
const { disableChallengeResourceVerification } = options;
let challengeState = { status: "none" };
const getAccessToken = (0, import_tokenCycler.createTokenCycler)(credential);
function requestToOptions(request) {
return {
abortSignal: request.abortSignal,
requestOptions: {
timeout: request.timeout > 0 ? request.timeout : void 0
},
tracingOptions: request.tracingOptions
};
}
async function authorizeRequest(request) {
const requestOptions = requestToOptions(request);
switch (challengeState.status) {
case "none":
challengeState = {
status: "started",
originalBody: request.body
};
request.body = null;
break;
case "started":
break;
// Retry, we should not overwrite the original body
case "complete": {
const token = await getAccessToken(challengeState.scopes, {
...requestOptions,
enableCae: true,
tenantId: challengeState.tenantId
});
if (token) {
request.headers.set("authorization", `Bearer ${token.token}`);
}
break;
}
}
}
async function handleChallenge(request, response, next) {
if (response.status !== 401) {
return response;
}
if (request.body === null && challengeState.status === "started") {
request.body = challengeState.originalBody;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
import_logger.logger.warning(
"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response."
);
return response;
}
const parsedChallenge = (0, import_parseWWWAuthenticate.parseWWWAuthenticateHeader)(challenge);
const scope = parsedChallenge.resource ? parsedChallenge.resource + "/.default" : parsedChallenge.scope;
if (!scope) {
return response;
}
if (!disableChallengeResourceVerification) {
verifyChallengeResource(scope, request);
}
const accessToken = await getAccessToken([scope], {
...getTokenOptions,
enableCae: true,
tenantId: parsedChallenge.tenantId
});
if (!accessToken) {
return response;
}
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
challengeState = {
status: "complete",
scopes: [scope],
tenantId: parsedChallenge.tenantId
};
return next(request);
}
async function handleCaeChallenge(request, response, next) {
if (challengeState.status !== "complete") {
return response;
}
if (response.status !== 401) {
return response;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
return response;
}
const { claims: base64EncodedClaims, error } = (0, import_parseWWWAuthenticate.parseWWWAuthenticateHeader)(challenge);
if (error !== "insufficient_claims" || base64EncodedClaims === void 0) {
return response;
}
const claims = atob(base64EncodedClaims);
const accessToken = await getAccessToken(challengeState.scopes, {
...getTokenOptions,
enableCae: true,
tenantId: challengeState.tenantId,
claims
});
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
return next(request);
}
async function sendRequest(request, next) {
await authorizeRequest(request);
let response = await next(request);
response = await handleChallenge(request, response, next);
response = await handleCaeChallenge(request, response, next);
return response;
}
return {
name: keyVaultAuthenticationPolicyName,
sendRequest
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
keyVaultAuthenticationPolicy,
keyVaultAuthenticationPolicyName
});
//# sourceMappingURL=keyVaultAuthenticationPolicy.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
export declare const logger: import("@azure/logger").AzureLogger;
//# sourceMappingURL=logger.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,qCAAwC,CAAC"}

View File

@ -0,0 +1,29 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var logger_exports = {};
__export(logger_exports, {
logger: () => logger
});
module.exports = __toCommonJS(logger_exports);
var import_logger = require("@azure/logger");
const logger = (0, import_logger.createClientLogger)("keyvault-common");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
logger
});
//# sourceMappingURL=logger.js.map

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/logger.ts"],
"sourcesContent": ["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { createClientLogger } from \"@azure/logger\";\n\nexport const logger = createClientLogger(\"keyvault-common\");\n"],
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAAmC;AAE5B,MAAM,aAAS,kCAAmB,iBAAiB;",
"names": []
}

View File

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@ -0,0 +1,25 @@
/**
* The parsed components of a Key Vault entity identifier.
*/
export interface KeyVaultEntityIdentifier {
/**
* The vault URI.
*/
vaultUrl: string;
/**
* The version of key/secret/certificate. May be undefined.
*/
version?: string;
/**
* The name of key/secret/certificate.
*/
name: string;
}
/**
* Parses a Key Vault identifier into its components.
*
* @param collection - The collection of the Key Vault identifier.
* @param identifier - The Key Vault identifier to be parsed.
*/
export declare function parseKeyVaultIdentifier(collection: string, identifier: string | undefined): KeyVaultEntityIdentifier;
//# sourceMappingURL=parseKeyVaultIdentifier.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseKeyVaultIdentifier.d.ts","sourceRoot":"","sources":["../../src/parseKeyVaultIdentifier.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,wBAAwB,CAsC1B"}

View File

@ -0,0 +1,60 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var parseKeyVaultIdentifier_exports = {};
__export(parseKeyVaultIdentifier_exports, {
parseKeyVaultIdentifier: () => parseKeyVaultIdentifier
});
module.exports = __toCommonJS(parseKeyVaultIdentifier_exports);
function parseKeyVaultIdentifier(collection, identifier) {
if (typeof collection !== "string" || !(collection = collection.trim())) {
throw new Error("Invalid collection argument");
}
if (typeof identifier !== "string" || !(identifier = identifier.trim())) {
throw new Error("Invalid identifier argument");
}
let baseUri;
try {
baseUri = new URL(identifier);
} catch (e) {
throw new Error(`Invalid ${collection} identifier: ${identifier}. Not a valid URI`);
}
const segments = (baseUri.pathname || "").split("/");
if (segments.length !== 3 && segments.length !== 4) {
throw new Error(
`Invalid ${collection} identifier: ${identifier}. Bad number of segments: ${segments.length}`
);
}
if (collection !== segments[1]) {
throw new Error(
`Invalid ${collection} identifier: ${identifier}. segment [1] should be "${collection}", found "${segments[1]}"`
);
}
const vaultUrl = `${baseUri.protocol}//${baseUri.host}`;
const name = segments[2];
const version = segments.length === 4 ? segments[3] : void 0;
return {
vaultUrl,
name,
version
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
parseKeyVaultIdentifier
});
//# sourceMappingURL=parseKeyVaultIdentifier.js.map

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/parseKeyVaultIdentifier.ts"],
"sourcesContent": ["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * The parsed components of a Key Vault entity identifier.\n */\nexport interface KeyVaultEntityIdentifier {\n /**\n * The vault URI.\n */\n vaultUrl: string;\n /**\n * The version of key/secret/certificate. May be undefined.\n */\n version?: string;\n /**\n * The name of key/secret/certificate.\n */\n name: string;\n}\n\n/**\n * Parses a Key Vault identifier into its components.\n *\n * @param collection - The collection of the Key Vault identifier.\n * @param identifier - The Key Vault identifier to be parsed.\n */\nexport function parseKeyVaultIdentifier(\n collection: string,\n identifier: string | undefined,\n): KeyVaultEntityIdentifier {\n if (typeof collection !== \"string\" || !(collection = collection.trim())) {\n throw new Error(\"Invalid collection argument\");\n }\n\n if (typeof identifier !== \"string\" || !(identifier = identifier.trim())) {\n throw new Error(\"Invalid identifier argument\");\n }\n\n let baseUri;\n try {\n baseUri = new URL(identifier);\n } catch (e: any) {\n throw new Error(`Invalid ${collection} identifier: ${identifier}. Not a valid URI`);\n }\n\n // Path is of the form '/collection/name[/version]'\n const segments = (baseUri.pathname || \"\").split(\"/\");\n if (segments.length !== 3 && segments.length !== 4) {\n throw new Error(\n `Invalid ${collection} identifier: ${identifier}. Bad number of segments: ${segments.length}`,\n );\n }\n\n if (collection !== segments[1]) {\n throw new Error(\n `Invalid ${collection} identifier: ${identifier}. segment [1] should be \"${collection}\", found \"${segments[1]}\"`,\n );\n }\n\n const vaultUrl = `${baseUri.protocol}//${baseUri.host}`;\n const name = segments[2];\n const version = segments.length === 4 ? segments[3] : undefined;\n return {\n vaultUrl,\n name,\n version,\n };\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BO,SAAS,wBACd,YACA,YAC0B;AAC1B,MAAI,OAAO,eAAe,YAAY,EAAE,aAAa,WAAW,KAAK,IAAI;AACvE,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAEA,MAAI,OAAO,eAAe,YAAY,EAAE,aAAa,WAAW,KAAK,IAAI;AACvE,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAEA,MAAI;AACJ,MAAI;AACF,cAAU,IAAI,IAAI,UAAU;AAAA,EAC9B,SAAS,GAAQ;AACf,UAAM,IAAI,MAAM,WAAW,UAAU,gBAAgB,UAAU,mBAAmB;AAAA,EACpF;AAGA,QAAM,YAAY,QAAQ,YAAY,IAAI,MAAM,GAAG;AACnD,MAAI,SAAS,WAAW,KAAK,SAAS,WAAW,GAAG;AAClD,UAAM,IAAI;AAAA,MACR,WAAW,UAAU,gBAAgB,UAAU,6BAA6B,SAAS,MAAM;AAAA,IAC7F;AAAA,EACF;AAEA,MAAI,eAAe,SAAS,CAAC,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR,WAAW,UAAU,gBAAgB,UAAU,4BAA4B,UAAU,aAAa,SAAS,CAAC,CAAC;AAAA,IAC/G;AAAA,EACF;AAEA,QAAM,WAAW,GAAG,QAAQ,QAAQ,KAAK,QAAQ,IAAI;AACrD,QAAM,OAAO,SAAS,CAAC;AACvB,QAAM,UAAU,SAAS,WAAW,IAAI,SAAS,CAAC,IAAI;AACtD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
"names": []
}

View File

@ -0,0 +1,43 @@
/**
* Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.
*/
export interface WWWAuthenticate {
/**
* The authorization parameter, if present.
*/
authorization?: string;
/**
* The authorization_url parameter, if present.
*/
authorization_url?: string;
/**
* The resource parameter, if present.
*/
resource?: string;
/**
* The scope parameter, if present.
*/
scope?: string;
/**
* The tenantId parameter, if present.
*/
tenantId?: string;
/**
* The claims parameter, if present.
*/
claims?: string;
/**
* The error parameter, if present.
*/
error?: string;
}
/**
* Parses an WWW-Authenticate response header.
* This transforms a string value like:
* `Bearer authorization="https://some.url/tenantId", resource="https://some.url"`
* into an object like:
* `{ authorization: "https://some.url/tenantId", resource: "https://some.url" }`
* @param headerValue - String value in the WWW-Authenticate header
*/
export declare function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate;
//# sourceMappingURL=parseWWWAuthenticate.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseWWWAuthenticate.d.ts","sourceRoot":"","sources":["../../src/parseWWWAuthenticate.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAYD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA2B/E"}

View File

@ -0,0 +1,59 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var parseWWWAuthenticate_exports = {};
__export(parseWWWAuthenticate_exports, {
parseWWWAuthenticateHeader: () => parseWWWAuthenticateHeader
});
module.exports = __toCommonJS(parseWWWAuthenticate_exports);
const validWWWAuthenticateProperties = [
"authorization",
"authorization_url",
"resource",
"scope",
"tenantId",
"claims",
"error"
];
function parseWWWAuthenticateHeader(headerValue) {
const pairDelimiter = /,? +/;
const parsed = headerValue.split(pairDelimiter).reduce((kvPairs, p) => {
if (p.match(/\w="/)) {
const [key, ...value] = p.split("=");
if (validWWWAuthenticateProperties.includes(key)) {
return { ...kvPairs, [key]: value.join("=").slice(1, -1) };
}
}
return kvPairs;
}, {});
if (parsed.authorization) {
try {
const tenantId = new URL(parsed.authorization).pathname.substring(1);
if (tenantId) {
parsed.tenantId = tenantId;
}
} catch (_) {
throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);
}
}
return parsed;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
parseWWWAuthenticateHeader
});
//# sourceMappingURL=parseWWWAuthenticate.js.map

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": ["../../src/parseWWWAuthenticate.ts"],
"sourcesContent": ["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.\n */\nexport interface WWWAuthenticate {\n /**\n * The authorization parameter, if present.\n */\n authorization?: string;\n\n /**\n * The authorization_url parameter, if present.\n */\n authorization_url?: string;\n\n /**\n * The resource parameter, if present.\n */\n resource?: string;\n\n /**\n * The scope parameter, if present.\n */\n scope?: string;\n\n /**\n * The tenantId parameter, if present.\n */\n tenantId?: string;\n\n /**\n * The claims parameter, if present.\n */\n claims?: string;\n\n /**\n * The error parameter, if present.\n */\n error?: string;\n}\n\nconst validWWWAuthenticateProperties: readonly (keyof WWWAuthenticate)[] = [\n \"authorization\",\n \"authorization_url\",\n \"resource\",\n \"scope\",\n \"tenantId\",\n \"claims\",\n \"error\",\n] as const;\n\n/**\n * Parses an WWW-Authenticate response header.\n * This transforms a string value like:\n * `Bearer authorization=\"https://some.url/tenantId\", resource=\"https://some.url\"`\n * into an object like:\n * `{ authorization: \"https://some.url/tenantId\", resource: \"https://some.url\" }`\n * @param headerValue - String value in the WWW-Authenticate header\n */\nexport function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate {\n const pairDelimiter = /,? +/;\n const parsed = headerValue.split(pairDelimiter).reduce<WWWAuthenticate>((kvPairs, p) => {\n if (p.match(/\\w=\"/)) {\n // 'sampleKey=\"sample_value\"' -> [sampleKey, \"sample_value\"] -> { sampleKey: sample_value }\n const [key, ...value] = p.split(\"=\");\n if (validWWWAuthenticateProperties.includes(key as keyof WWWAuthenticate)) {\n // The values will be wrapped in quotes, which need to be stripped out.\n return { ...kvPairs, [key]: value.join(\"=\").slice(1, -1) };\n }\n }\n return kvPairs;\n }, {});\n\n // Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.\n if (parsed.authorization) {\n try {\n const tenantId = new URL(parsed.authorization).pathname.substring(1);\n if (tenantId) {\n parsed.tenantId = tenantId;\n }\n } catch (_) {\n throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);\n }\n }\n\n return parsed;\n}\n"],
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2CA,MAAM,iCAAqE;AAAA,EACzE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUO,SAAS,2BAA2B,aAAsC;AAC/E,QAAM,gBAAgB;AACtB,QAAM,SAAS,YAAY,MAAM,aAAa,EAAE,OAAwB,CAAC,SAAS,MAAM;AACtF,QAAI,EAAE,MAAM,MAAM,GAAG;AAEnB,YAAM,CAAC,KAAK,GAAG,KAAK,IAAI,EAAE,MAAM,GAAG;AACnC,UAAI,+BAA+B,SAAS,GAA4B,GAAG;AAEzE,eAAO,EAAE,GAAG,SAAS,CAAC,GAAG,GAAG,MAAM,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE;AAAA,MAC3D;AAAA,IACF;AACA,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAGL,MAAI,OAAO,eAAe;AACxB,QAAI;AACF,YAAM,WAAW,IAAI,IAAI,OAAO,aAAa,EAAE,SAAS,UAAU,CAAC;AACnE,UAAI,UAAU;AACZ,eAAO,WAAW;AAAA,MACpB;AAAA,IACF,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,oCAAoC,OAAO,aAAa,eAAe;AAAA,IACzF;AAAA,EACF;AAEA,SAAO;AACT;",
"names": []
}

View File

@ -0,0 +1,45 @@
import type { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-auth";
/**
* A function that gets a promise of an access token and allows providing
* options.
*
* @param options - the options to pass to the underlying token provider
*/
export type AccessTokenGetter = (scopes: string | string[], options: GetTokenOptions) => Promise<AccessToken>;
export interface TokenCyclerOptions {
/**
* The window of time before token expiration during which the token will be
* considered unusable due to risk of the token expiring before sending the
* request.
*
* This will only become meaningful if the refresh fails for over
* (refreshWindow - forcedRefreshWindow) milliseconds.
*/
forcedRefreshWindowInMs: number;
/**
* Interval in milliseconds to retry failed token refreshes.
*/
retryIntervalInMs: number;
/**
* The window of time before token expiration during which
* we will attempt to refresh the token.
*/
refreshWindowInMs: number;
}
export declare const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions;
/**
* Creates a token cycler from a credential, scopes, and optional settings.
*
* A token cycler represents a way to reliably retrieve a valid access token
* from a TokenCredential. It will handle initializing the token, refreshing it
* when it nears expiration, and synchronizes refresh attempts to avoid
* concurrency hazards.
*
* @param credential - the underlying TokenCredential that provides the access
* token
* @param tokenCyclerOptions - optionally override default settings for the cycler
*
* @returns - a function that reliably produces a valid access token
*/
export declare function createTokenCycler(credential: TokenCredential, tokenCyclerOptions?: Partial<TokenCyclerOptions>): AccessTokenGetter;
//# sourceMappingURL=tokenCycler.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"tokenCycler.d.ts","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGtF;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EACzB,OAAO,EAAE,eAAe,KACrB,OAAO,CAAC,WAAW,CAAC,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,uBAAuB,EAAE,MAAM,CAAC;IAChC;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAGD,eAAO,MAAM,sBAAsB,EAAE,kBAIpC,CAAC;AAiDF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAC/C,iBAAiB,CA0HnB"}

View File

@ -0,0 +1,135 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var tokenCycler_exports = {};
__export(tokenCycler_exports, {
DEFAULT_CYCLER_OPTIONS: () => DEFAULT_CYCLER_OPTIONS,
createTokenCycler: () => createTokenCycler
});
module.exports = __toCommonJS(tokenCycler_exports);
var import_core_util = require("@azure/core-util");
const DEFAULT_CYCLER_OPTIONS = {
forcedRefreshWindowInMs: 1e3,
// Force waiting for a refresh 1s before the token expires
retryIntervalInMs: 3e3,
// Allow refresh attempts every 3s
refreshWindowInMs: 1e3 * 60 * 2
// Start refreshing 2m before expiry
};
async function beginRefresh(getAccessToken, retryIntervalInMs, refreshTimeout) {
async function tryGetAccessToken() {
if (Date.now() < refreshTimeout) {
try {
return await getAccessToken();
} catch {
return null;
}
} else {
const finalToken = await getAccessToken();
if (finalToken === null) {
throw new Error("Failed to refresh access token.");
}
return finalToken;
}
}
let token = await tryGetAccessToken();
while (token === null) {
await (0, import_core_util.delay)(retryIntervalInMs);
token = await tryGetAccessToken();
}
return token;
}
function createTokenCycler(credential, tokenCyclerOptions) {
let refreshWorker = null;
let token = null;
let tenantId;
const options = {
...DEFAULT_CYCLER_OPTIONS,
...tokenCyclerOptions
};
const cycler = {
/**
* Produces true if a refresh job is currently in progress.
*/
get isRefreshing() {
return refreshWorker !== null;
},
/**
* Produces true if the cycler SHOULD refresh (we are within the refresh
* window and not already refreshing)
*/
get shouldRefresh() {
if (cycler.isRefreshing) {
return false;
}
if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {
return true;
}
return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();
},
/**
* Produces true if the cycler MUST refresh (null or nearly-expired
* token).
*/
get mustRefresh() {
return token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now();
}
};
function refresh(scopes, getTokenOptions) {
if (!cycler.isRefreshing) {
const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
refreshWorker = beginRefresh(
tryGetAccessToken,
options.retryIntervalInMs,
// If we don't have a token, then we should timeout immediately
token?.expiresOnTimestamp ?? Date.now()
).then((_token) => {
refreshWorker = null;
token = _token;
tenantId = getTokenOptions.tenantId;
return token;
}).catch((reason) => {
refreshWorker = null;
token = null;
tenantId = void 0;
throw reason;
});
}
return refreshWorker;
}
return async (scopes, tokenOptions) => {
const hasClaimChallenge = Boolean(tokenOptions.claims);
const tenantIdChanged = tenantId !== tokenOptions.tenantId;
if (hasClaimChallenge) {
token = null;
}
const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;
if (mustRefresh) {
return refresh(scopes, tokenOptions);
}
if (cycler.shouldRefresh) {
refresh(scopes, tokenOptions);
}
return token;
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
DEFAULT_CYCLER_OPTIONS,
createTokenCycler
});
//# sourceMappingURL=tokenCycler.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,11 @@
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
// It should be published with your NPM package. It should not be tracked by Git.
{
"tsdocVersion": "0.12",
"toolPackages": [
{
"packageName": "@microsoft/api-extractor",
"packageVersion": "7.58.1"
}
]
}

View File

@ -0,0 +1,59 @@
import type { HttpClient, PipelineOptions } from "@azure/core-rest-pipeline";
import type { AdditionalPolicyConfig } from "@azure-rest/core-client";
/**
* The common set of options that high level clients are expected to expose.
*/
export interface CommonClientOptions extends PipelineOptions {
/**
* The HttpClient that will be used to send HTTP requests.
*/
httpClient?: HttpClient;
/**
* Set to true if the request is sent over HTTP instead of HTTPS
*/
allowInsecureConnection?: boolean;
/**
* Additional policies to include in the HTTP pipeline.
*/
additionalPolicies?: AdditionalPolicyConfig[];
}
/**
* Options for keep alive behavior of HTTP connections.
*/
export interface KeepAliveOptions {
/**
* When true, connections will be kept alive for multiple requests.
* Defaults to true.
*/
enable?: boolean;
}
/**
* Options for how redirect responses are handled.
*/
export interface RedirectOptions {
/**
* When true, redirect responses are followed. Defaults to true.
*/
handleRedirects?: boolean;
/**
* The maximum number of times the redirect URL will be tried before
* failing. Defaults to 20.
*/
maxRetries?: number;
}
/**
* Extended common client options that include keep alive and redirect options.
* This type combines the standard CommonClientOptions with additional options
* for controlling HTTP connection keep-alive and redirect behavior.
*/
export type ExtendedCommonClientOptions = CommonClientOptions & {
/**
* Options to configure keep alive behavior for HTTP connections.
*/
keepAliveOptions?: KeepAliveOptions;
/**
* Options to configure how redirect responses are handled.
*/
redirectOptions?: RedirectOptions;
};
//# sourceMappingURL=extendedClientOptions.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"extendedClientOptions.d.ts","sourceRoot":"","sources":["../../src/extendedClientOptions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;OAEG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;OAEG;IACH,kBAAkB,CAAC,EAAE,sBAAsB,EAAE,CAAC;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAAG;IAC9D;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC,CAAC"}

View File

@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export {};
//# sourceMappingURL=extendedClientOptions.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"extendedClientOptions.js","sourceRoot":"","sources":["../../src/extendedClientOptions.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { HttpClient, PipelineOptions } from \"@azure/core-rest-pipeline\";\nimport type { AdditionalPolicyConfig } from \"@azure-rest/core-client\";\n\n/**\n * The common set of options that high level clients are expected to expose.\n */\nexport interface CommonClientOptions extends PipelineOptions {\n /**\n * The HttpClient that will be used to send HTTP requests.\n */\n httpClient?: HttpClient;\n /**\n * Set to true if the request is sent over HTTP instead of HTTPS\n */\n allowInsecureConnection?: boolean;\n /**\n * Additional policies to include in the HTTP pipeline.\n */\n additionalPolicies?: AdditionalPolicyConfig[];\n}\n\n/**\n * Options for keep alive behavior of HTTP connections.\n */\nexport interface KeepAliveOptions {\n /**\n * When true, connections will be kept alive for multiple requests.\n * Defaults to true.\n */\n enable?: boolean;\n}\n\n/**\n * Options for how redirect responses are handled.\n */\nexport interface RedirectOptions {\n /**\n * When true, redirect responses are followed. Defaults to true.\n */\n handleRedirects?: boolean;\n\n /**\n * The maximum number of times the redirect URL will be tried before\n * failing. Defaults to 20.\n */\n maxRetries?: number;\n}\n\n/**\n * Extended common client options that include keep alive and redirect options.\n * This type combines the standard CommonClientOptions with additional options\n * for controlling HTTP connection keep-alive and redirect behavior.\n */\nexport type ExtendedCommonClientOptions = CommonClientOptions & {\n /**\n * Options to configure keep alive behavior for HTTP connections.\n */\n keepAliveOptions?: KeepAliveOptions;\n /**\n * Options to configure how redirect responses are handled.\n */\n redirectOptions?: RedirectOptions;\n};\n"]}

View File

@ -0,0 +1,5 @@
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
export type * from "./extendedClientOptions.js";
export type { AdditionalPolicyConfig } from "@azure-rest/core-client";
//# sourceMappingURL=index.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,mBAAmB,4BAA4B,CAAC;AAChD,YAAY,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC"}

View File

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport * from \"./keyVaultAuthenticationPolicy.js\";\nexport * from \"./parseKeyVaultIdentifier.js\";\nexport type * from \"./extendedClientOptions.js\";\nexport type { AdditionalPolicyConfig } from \"@azure-rest/core-client\";\n"]}

View File

@ -0,0 +1,32 @@
import type { PipelinePolicy } from "@azure/core-rest-pipeline";
import type { TokenCredential } from "@azure/core-auth";
/**
* Additional options for the challenge based authentication policy.
*/
export interface KeyVaultAuthenticationPolicyOptions {
/**
* Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.
*
* Defaults to false.
*/
disableChallengeResourceVerification?: boolean;
}
/**
* Name of the Key Vault authentication policy.
*/
export declare const keyVaultAuthenticationPolicyName = "keyVaultAuthenticationPolicy";
/**
* A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.
*
* Key Vault supports other authentication schemes, but we ensure challenge authentication
* is used by first sending a copy of the request, without authorization or content.
*
* when the challenge is received, it will be authenticated and used to send the original
* request with authorization.
*
* Following the first request of a client, follow-up requests will get the cached token
* if possible.
*
*/
export declare function keyVaultAuthenticationPolicy(credential: TokenCredential, options?: KeyVaultAuthenticationPolicyOptions): PipelinePolicy;
//# sourceMappingURL=keyVaultAuthenticationPolicy.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"keyVaultAuthenticationPolicy.d.ts","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AA8BzE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;OAIG;IACH,oCAAoC,CAAC,EAAE,OAAO,CAAC;CAChD;AAmBD;;GAEG;AACH,eAAO,MAAM,gCAAgC,iCAAiC,CAAC;AAE/E;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,eAAe,EAC3B,OAAO,GAAE,mCAAwC,GAChD,cAAc,CA2KhB"}

View File

@ -0,0 +1,164 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { parseWWWAuthenticateHeader } from "./parseWWWAuthenticate.js";
import { createTokenCycler } from "./tokenCycler.js";
import { logger } from "./logger.js";
function verifyChallengeResource(scope, request) {
let scopeAsUrl;
try {
scopeAsUrl = new URL(scope);
}
catch (e) {
throw new Error(`The challenge contains invalid scope '${scope}'`);
}
const requestUrl = new URL(request.url);
if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {
throw new Error(`The challenge resource '${scopeAsUrl.hostname}' does not match the requested domain. Set disableChallengeResourceVerification to true in your client options to disable. See https://aka.ms/azsdk/blog/vault-uri for more information.`);
}
}
/**
* Name of the Key Vault authentication policy.
*/
export const keyVaultAuthenticationPolicyName = "keyVaultAuthenticationPolicy";
/**
* A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.
*
* Key Vault supports other authentication schemes, but we ensure challenge authentication
* is used by first sending a copy of the request, without authorization or content.
*
* when the challenge is received, it will be authenticated and used to send the original
* request with authorization.
*
* Following the first request of a client, follow-up requests will get the cached token
* if possible.
*
*/
export function keyVaultAuthenticationPolicy(credential, options = {}) {
const { disableChallengeResourceVerification } = options;
let challengeState = { status: "none" };
const getAccessToken = createTokenCycler(credential);
function requestToOptions(request) {
return {
abortSignal: request.abortSignal,
requestOptions: {
timeout: request.timeout > 0 ? request.timeout : undefined,
},
tracingOptions: request.tracingOptions,
};
}
async function authorizeRequest(request) {
const requestOptions = requestToOptions(request);
switch (challengeState.status) {
case "none":
challengeState = {
status: "started",
originalBody: request.body,
};
request.body = null;
break;
case "started":
break; // Retry, we should not overwrite the original body
case "complete": {
const token = await getAccessToken(challengeState.scopes, {
...requestOptions,
enableCae: true,
tenantId: challengeState.tenantId,
});
if (token) {
request.headers.set("authorization", `Bearer ${token.token}`);
}
break;
}
}
}
async function handleChallenge(request, response, next) {
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
if (request.body === null && challengeState.status === "started") {
// Reset the original body before doing anything else.
// Note: If successful status will be "complete", otherwise "none" will
// restart the process.
request.body = challengeState.originalBody;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
logger.warning("keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.");
return response;
}
const parsedChallenge = parseWWWAuthenticateHeader(challenge);
const scope = parsedChallenge.resource
? parsedChallenge.resource + "/.default"
: parsedChallenge.scope;
if (!scope) {
// Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)
return response;
}
if (!disableChallengeResourceVerification) {
verifyChallengeResource(scope, request);
}
const accessToken = await getAccessToken([scope], {
...getTokenOptions,
enableCae: true,
tenantId: parsedChallenge.tenantId,
});
if (!accessToken) {
// No access token provided, treat as no-op
return response;
}
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
challengeState = {
status: "complete",
scopes: [scope],
tenantId: parsedChallenge.tenantId,
};
// We have a token now, so try send the request again
return next(request);
}
async function handleCaeChallenge(request, response, next) {
// Cannot handle CAE challenge if a regular challenge has not been completed first
if (challengeState.status !== "complete") {
return response;
}
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
return response;
}
const { claims: base64EncodedClaims, error } = parseWWWAuthenticateHeader(challenge);
if (error !== "insufficient_claims" || base64EncodedClaims === undefined) {
return response;
}
const claims = atob(base64EncodedClaims);
const accessToken = await getAccessToken(challengeState.scopes, {
...getTokenOptions,
enableCae: true,
tenantId: challengeState.tenantId,
claims,
});
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
return next(request);
}
async function sendRequest(request, next) {
// Add token if possible
await authorizeRequest(request);
// Try send request (first attempt)
let response = await next(request);
// Handle standard challenge if present
response = await handleChallenge(request, response, next);
// Handle CAE challenge if present
response = await handleCaeChallenge(request, response, next);
return response;
}
return {
name: keyVaultAuthenticationPolicyName,
sendRequest,
};
}
//# sourceMappingURL=keyVaultAuthenticationPolicy.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
export declare const logger: import("@azure/logger").AzureLogger;
//# sourceMappingURL=logger.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,qCAAwC,CAAC"}

View File

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { createClientLogger } from "@azure/logger";
export const logger = createClientLogger("keyvault-common");
//# sourceMappingURL=logger.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,CAAC,MAAM,MAAM,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { createClientLogger } from \"@azure/logger\";\n\nexport const logger = createClientLogger(\"keyvault-common\");\n"]}

View File

@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@ -0,0 +1,25 @@
/**
* The parsed components of a Key Vault entity identifier.
*/
export interface KeyVaultEntityIdentifier {
/**
* The vault URI.
*/
vaultUrl: string;
/**
* The version of key/secret/certificate. May be undefined.
*/
version?: string;
/**
* The name of key/secret/certificate.
*/
name: string;
}
/**
* Parses a Key Vault identifier into its components.
*
* @param collection - The collection of the Key Vault identifier.
* @param identifier - The Key Vault identifier to be parsed.
*/
export declare function parseKeyVaultIdentifier(collection: string, identifier: string | undefined): KeyVaultEntityIdentifier;
//# sourceMappingURL=parseKeyVaultIdentifier.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseKeyVaultIdentifier.d.ts","sourceRoot":"","sources":["../../src/parseKeyVaultIdentifier.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,wBAAwB,CAsC1B"}

View File

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
/**
* Parses a Key Vault identifier into its components.
*
* @param collection - The collection of the Key Vault identifier.
* @param identifier - The Key Vault identifier to be parsed.
*/
export function parseKeyVaultIdentifier(collection, identifier) {
if (typeof collection !== "string" || !(collection = collection.trim())) {
throw new Error("Invalid collection argument");
}
if (typeof identifier !== "string" || !(identifier = identifier.trim())) {
throw new Error("Invalid identifier argument");
}
let baseUri;
try {
baseUri = new URL(identifier);
}
catch (e) {
throw new Error(`Invalid ${collection} identifier: ${identifier}. Not a valid URI`);
}
// Path is of the form '/collection/name[/version]'
const segments = (baseUri.pathname || "").split("/");
if (segments.length !== 3 && segments.length !== 4) {
throw new Error(`Invalid ${collection} identifier: ${identifier}. Bad number of segments: ${segments.length}`);
}
if (collection !== segments[1]) {
throw new Error(`Invalid ${collection} identifier: ${identifier}. segment [1] should be "${collection}", found "${segments[1]}"`);
}
const vaultUrl = `${baseUri.protocol}//${baseUri.host}`;
const name = segments[2];
const version = segments.length === 4 ? segments[3] : undefined;
return {
vaultUrl,
name,
version,
};
}
//# sourceMappingURL=parseKeyVaultIdentifier.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseKeyVaultIdentifier.js","sourceRoot":"","sources":["../../src/parseKeyVaultIdentifier.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAoBlC;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAkB,EAClB,UAA8B;IAE9B,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,WAAW,UAAU,gBAAgB,UAAU,mBAAmB,CAAC,CAAC;IACtF,CAAC;IAED,mDAAmD;IACnD,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,WAAW,UAAU,gBAAgB,UAAU,6BAA6B,QAAQ,CAAC,MAAM,EAAE,CAC9F,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,WAAW,UAAU,gBAAgB,UAAU,4BAA4B,UAAU,aAAa,QAAQ,CAAC,CAAC,CAAC,GAAG,CACjH,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;IACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,OAAO;QACL,QAAQ;QACR,IAAI;QACJ,OAAO;KACR,CAAC;AACJ,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * The parsed components of a Key Vault entity identifier.\n */\nexport interface KeyVaultEntityIdentifier {\n /**\n * The vault URI.\n */\n vaultUrl: string;\n /**\n * The version of key/secret/certificate. May be undefined.\n */\n version?: string;\n /**\n * The name of key/secret/certificate.\n */\n name: string;\n}\n\n/**\n * Parses a Key Vault identifier into its components.\n *\n * @param collection - The collection of the Key Vault identifier.\n * @param identifier - The Key Vault identifier to be parsed.\n */\nexport function parseKeyVaultIdentifier(\n collection: string,\n identifier: string | undefined,\n): KeyVaultEntityIdentifier {\n if (typeof collection !== \"string\" || !(collection = collection.trim())) {\n throw new Error(\"Invalid collection argument\");\n }\n\n if (typeof identifier !== \"string\" || !(identifier = identifier.trim())) {\n throw new Error(\"Invalid identifier argument\");\n }\n\n let baseUri;\n try {\n baseUri = new URL(identifier);\n } catch (e: any) {\n throw new Error(`Invalid ${collection} identifier: ${identifier}. Not a valid URI`);\n }\n\n // Path is of the form '/collection/name[/version]'\n const segments = (baseUri.pathname || \"\").split(\"/\");\n if (segments.length !== 3 && segments.length !== 4) {\n throw new Error(\n `Invalid ${collection} identifier: ${identifier}. Bad number of segments: ${segments.length}`,\n );\n }\n\n if (collection !== segments[1]) {\n throw new Error(\n `Invalid ${collection} identifier: ${identifier}. segment [1] should be \"${collection}\", found \"${segments[1]}\"`,\n );\n }\n\n const vaultUrl = `${baseUri.protocol}//${baseUri.host}`;\n const name = segments[2];\n const version = segments.length === 4 ? segments[3] : undefined;\n return {\n vaultUrl,\n name,\n version,\n };\n}\n"]}

View File

@ -0,0 +1,43 @@
/**
* Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.
*/
export interface WWWAuthenticate {
/**
* The authorization parameter, if present.
*/
authorization?: string;
/**
* The authorization_url parameter, if present.
*/
authorization_url?: string;
/**
* The resource parameter, if present.
*/
resource?: string;
/**
* The scope parameter, if present.
*/
scope?: string;
/**
* The tenantId parameter, if present.
*/
tenantId?: string;
/**
* The claims parameter, if present.
*/
claims?: string;
/**
* The error parameter, if present.
*/
error?: string;
}
/**
* Parses an WWW-Authenticate response header.
* This transforms a string value like:
* `Bearer authorization="https://some.url/tenantId", resource="https://some.url"`
* into an object like:
* `{ authorization: "https://some.url/tenantId", resource: "https://some.url" }`
* @param headerValue - String value in the WWW-Authenticate header
*/
export declare function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate;
//# sourceMappingURL=parseWWWAuthenticate.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseWWWAuthenticate.d.ts","sourceRoot":"","sources":["../../src/parseWWWAuthenticate.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAYD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA2B/E"}

View File

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const validWWWAuthenticateProperties = [
"authorization",
"authorization_url",
"resource",
"scope",
"tenantId",
"claims",
"error",
];
/**
* Parses an WWW-Authenticate response header.
* This transforms a string value like:
* `Bearer authorization="https://some.url/tenantId", resource="https://some.url"`
* into an object like:
* `{ authorization: "https://some.url/tenantId", resource: "https://some.url" }`
* @param headerValue - String value in the WWW-Authenticate header
*/
export function parseWWWAuthenticateHeader(headerValue) {
const pairDelimiter = /,? +/;
const parsed = headerValue.split(pairDelimiter).reduce((kvPairs, p) => {
if (p.match(/\w="/)) {
// 'sampleKey="sample_value"' -> [sampleKey, "sample_value"] -> { sampleKey: sample_value }
const [key, ...value] = p.split("=");
if (validWWWAuthenticateProperties.includes(key)) {
// The values will be wrapped in quotes, which need to be stripped out.
return { ...kvPairs, [key]: value.join("=").slice(1, -1) };
}
}
return kvPairs;
}, {});
// Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.
if (parsed.authorization) {
try {
const tenantId = new URL(parsed.authorization).pathname.substring(1);
if (tenantId) {
parsed.tenantId = tenantId;
}
}
catch (_) {
throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);
}
}
return parsed;
}
//# sourceMappingURL=parseWWWAuthenticate.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"parseWWWAuthenticate.js","sourceRoot":"","sources":["../../src/parseWWWAuthenticate.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AA0ClC,MAAM,8BAA8B,GAAuC;IACzE,eAAe;IACf,mBAAmB;IACnB,UAAU;IACV,OAAO;IACP,UAAU;IACV,QAAQ;IACR,OAAO;CACC,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,WAAmB;IAC5D,MAAM,aAAa,GAAG,MAAM,CAAC;IAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,MAAM,CAAkB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QACrF,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,2FAA2F;YAC3F,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,8BAA8B,CAAC,QAAQ,CAAC,GAA4B,CAAC,EAAE,CAAC;gBAC1E,uEAAuE;gBACvE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,sGAAsG;IACtG,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACrE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,aAAa,eAAe,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.\n */\nexport interface WWWAuthenticate {\n /**\n * The authorization parameter, if present.\n */\n authorization?: string;\n\n /**\n * The authorization_url parameter, if present.\n */\n authorization_url?: string;\n\n /**\n * The resource parameter, if present.\n */\n resource?: string;\n\n /**\n * The scope parameter, if present.\n */\n scope?: string;\n\n /**\n * The tenantId parameter, if present.\n */\n tenantId?: string;\n\n /**\n * The claims parameter, if present.\n */\n claims?: string;\n\n /**\n * The error parameter, if present.\n */\n error?: string;\n}\n\nconst validWWWAuthenticateProperties: readonly (keyof WWWAuthenticate)[] = [\n \"authorization\",\n \"authorization_url\",\n \"resource\",\n \"scope\",\n \"tenantId\",\n \"claims\",\n \"error\",\n] as const;\n\n/**\n * Parses an WWW-Authenticate response header.\n * This transforms a string value like:\n * `Bearer authorization=\"https://some.url/tenantId\", resource=\"https://some.url\"`\n * into an object like:\n * `{ authorization: \"https://some.url/tenantId\", resource: \"https://some.url\" }`\n * @param headerValue - String value in the WWW-Authenticate header\n */\nexport function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate {\n const pairDelimiter = /,? +/;\n const parsed = headerValue.split(pairDelimiter).reduce<WWWAuthenticate>((kvPairs, p) => {\n if (p.match(/\\w=\"/)) {\n // 'sampleKey=\"sample_value\"' -> [sampleKey, \"sample_value\"] -> { sampleKey: sample_value }\n const [key, ...value] = p.split(\"=\");\n if (validWWWAuthenticateProperties.includes(key as keyof WWWAuthenticate)) {\n // The values will be wrapped in quotes, which need to be stripped out.\n return { ...kvPairs, [key]: value.join(\"=\").slice(1, -1) };\n }\n }\n return kvPairs;\n }, {});\n\n // Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.\n if (parsed.authorization) {\n try {\n const tenantId = new URL(parsed.authorization).pathname.substring(1);\n if (tenantId) {\n parsed.tenantId = tenantId;\n }\n } catch (_) {\n throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);\n }\n }\n\n return parsed;\n}\n"]}

View File

@ -0,0 +1,45 @@
import type { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-auth";
/**
* A function that gets a promise of an access token and allows providing
* options.
*
* @param options - the options to pass to the underlying token provider
*/
export type AccessTokenGetter = (scopes: string | string[], options: GetTokenOptions) => Promise<AccessToken>;
export interface TokenCyclerOptions {
/**
* The window of time before token expiration during which the token will be
* considered unusable due to risk of the token expiring before sending the
* request.
*
* This will only become meaningful if the refresh fails for over
* (refreshWindow - forcedRefreshWindow) milliseconds.
*/
forcedRefreshWindowInMs: number;
/**
* Interval in milliseconds to retry failed token refreshes.
*/
retryIntervalInMs: number;
/**
* The window of time before token expiration during which
* we will attempt to refresh the token.
*/
refreshWindowInMs: number;
}
export declare const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions;
/**
* Creates a token cycler from a credential, scopes, and optional settings.
*
* A token cycler represents a way to reliably retrieve a valid access token
* from a TokenCredential. It will handle initializing the token, refreshing it
* when it nears expiration, and synchronizes refresh attempts to avoid
* concurrency hazards.
*
* @param credential - the underlying TokenCredential that provides the access
* token
* @param tokenCyclerOptions - optionally override default settings for the cycler
*
* @returns - a function that reliably produces a valid access token
*/
export declare function createTokenCycler(credential: TokenCredential, tokenCyclerOptions?: Partial<TokenCyclerOptions>): AccessTokenGetter;
//# sourceMappingURL=tokenCycler.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"tokenCycler.d.ts","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGtF;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EACzB,OAAO,EAAE,eAAe,KACrB,OAAO,CAAC,WAAW,CAAC,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,uBAAuB,EAAE,MAAM,CAAC;IAChC;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAGD,eAAO,MAAM,sBAAsB,EAAE,kBAIpC,CAAC;AAiDF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAC/C,iBAAiB,CA0HnB"}

View File

@ -0,0 +1,163 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { delay } from "@azure/core-util";
// Default options for the cycler if none are provided
export const DEFAULT_CYCLER_OPTIONS = {
forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires
retryIntervalInMs: 3000, // Allow refresh attempts every 3s
refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry
};
/**
* Converts an an unreliable access token getter (which may resolve with null)
* into an AccessTokenGetter by retrying the unreliable getter in a regular
* interval.
*
* @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.
* @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.
* @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.
* @returns - A promise that, if it resolves, will resolve with an access token.
*/
async function beginRefresh(getAccessToken, retryIntervalInMs, refreshTimeout) {
// This wrapper handles exceptions gracefully as long as we haven't exceeded
// the timeout.
async function tryGetAccessToken() {
if (Date.now() < refreshTimeout) {
try {
return await getAccessToken();
}
catch {
return null;
}
}
else {
const finalToken = await getAccessToken();
// Timeout is up, so throw if it's still null
if (finalToken === null) {
throw new Error("Failed to refresh access token.");
}
return finalToken;
}
}
let token = await tryGetAccessToken();
while (token === null) {
await delay(retryIntervalInMs);
token = await tryGetAccessToken();
}
return token;
}
/**
* Creates a token cycler from a credential, scopes, and optional settings.
*
* A token cycler represents a way to reliably retrieve a valid access token
* from a TokenCredential. It will handle initializing the token, refreshing it
* when it nears expiration, and synchronizes refresh attempts to avoid
* concurrency hazards.
*
* @param credential - the underlying TokenCredential that provides the access
* token
* @param tokenCyclerOptions - optionally override default settings for the cycler
*
* @returns - a function that reliably produces a valid access token
*/
export function createTokenCycler(credential, tokenCyclerOptions) {
let refreshWorker = null;
let token = null;
let tenantId;
const options = {
...DEFAULT_CYCLER_OPTIONS,
...tokenCyclerOptions,
};
/**
* This little holder defines several predicates that we use to construct
* the rules of refreshing the token.
*/
const cycler = {
/**
* Produces true if a refresh job is currently in progress.
*/
get isRefreshing() {
return refreshWorker !== null;
},
/**
* Produces true if the cycler SHOULD refresh (we are within the refresh
* window and not already refreshing)
*/
get shouldRefresh() {
if (cycler.isRefreshing) {
return false;
}
if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {
return true;
}
return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();
},
/**
* Produces true if the cycler MUST refresh (null or nearly-expired
* token).
*/
get mustRefresh() {
return (token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now());
},
};
/**
* Starts a refresh job or returns the existing job if one is already
* running.
*/
function refresh(scopes, getTokenOptions) {
if (!cycler.isRefreshing) {
// We bind `scopes` here to avoid passing it around a lot
const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
// Take advantage of promise chaining to insert an assignment to `token`
// before the refresh can be considered done.
refreshWorker = beginRefresh(tryGetAccessToken, options.retryIntervalInMs,
// If we don't have a token, then we should timeout immediately
token?.expiresOnTimestamp ?? Date.now())
.then((_token) => {
refreshWorker = null;
token = _token;
tenantId = getTokenOptions.tenantId;
return token;
})
.catch((reason) => {
// We also should reset the refresher if we enter a failed state. All
// existing awaiters will throw, but subsequent requests will start a
// new retry chain.
refreshWorker = null;
token = null;
tenantId = undefined;
throw reason;
});
}
return refreshWorker;
}
return async (scopes, tokenOptions) => {
//
// Simple rules:
// - If we MUST refresh, then return the refresh task, blocking
// the pipeline until a token is available.
// - If we SHOULD refresh, then run refresh but don't return it
// (we can still use the cached token).
// - Return the token, since it's fine if we didn't return in
// step 1.
//
const hasClaimChallenge = Boolean(tokenOptions.claims);
const tenantIdChanged = tenantId !== tokenOptions.tenantId;
if (hasClaimChallenge) {
// If we've received a claim, we know the existing token isn't valid
// We want to clear it so that that refresh worker won't use the old expiration time as a timeout
token = null;
}
// If the tenantId passed in token options is different to the one we have
// Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to
// refresh the token with the new tenantId or token.
const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;
if (mustRefresh) {
return refresh(scopes, tokenOptions);
}
if (cycler.shouldRefresh) {
refresh(scopes, tokenOptions);
}
return token;
};
}
//# sourceMappingURL=tokenCycler.js.map

File diff suppressed because one or more lines are too long