Commit iniziale

This commit is contained in:
Paolo A
2025-02-18 22:59:07 +00:00
commit 4bbf35cefb
6879 changed files with 623784 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
//# 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"}

View File

@@ -0,0 +1,8 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./keyVaultAuthenticationPolicy.js"), exports);
tslib_1.__exportStar(require("./parseKeyVaultIdentifier.js"), exports);
//# 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,4EAAkD;AAClD,uEAA6C","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport * from \"./keyVaultAuthenticationPolicy.js\";\nexport * from \"./parseKeyVaultIdentifier.js\";\n"]}

View File

@@ -0,0 +1,32 @@
import { PipelinePolicy } from "@azure/core-rest-pipeline";
import { 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,EACL,cAAc,EAKf,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAmB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AA8BpE;;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,155 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.keyVaultAuthenticationPolicyName = void 0;
exports.keyVaultAuthenticationPolicy = keyVaultAuthenticationPolicy;
const parseWWWAuthenticate_js_1 = require("./parseWWWAuthenticate.js");
const tokenCycler_js_1 = require("./tokenCycler.js");
const logger_js_1 = 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.`);
}
}
/**
* Name of the Key Vault authentication policy.
*/
exports.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.
*
*/
function keyVaultAuthenticationPolicy(credential, options = {}) {
const { disableChallengeResourceVerification } = options;
let challengeState = { status: "none" };
const getAccessToken = (0, tokenCycler_js_1.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, Object.assign(Object.assign({}, 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_js_1.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, parseWWWAuthenticate_js_1.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], Object.assign(Object.assign({}, 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 } = (0, parseWWWAuthenticate_js_1.parseWWWAuthenticateHeader)(challenge);
if (error !== "insufficient_claims" || base64EncodedClaims === undefined) {
return response;
}
const claims = atob(base64EncodedClaims);
const accessToken = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, 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: exports.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,8 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = void 0;
const logger_1 = require("@azure/logger");
exports.logger = (0, logger_1.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,0CAAmD;AAEtC,QAAA,MAAM,GAAG,IAAA,2BAAkB,EAAC,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": "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,43 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseKeyVaultIdentifier = parseKeyVaultIdentifier;
/**
* 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.
*/
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;;AA0BlC,0DAyCC;AA/CD;;;;;GAKG;AACH,SAAgB,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,50 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseWWWAuthenticateHeader = parseWWWAuthenticateHeader;
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
*/
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 Object.assign(Object.assign({}, 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;;AA4DlC,gEA2BC;AA7CD,MAAM,8BAA8B,GAAuC;IACzE,eAAe;IACf,mBAAmB;IACnB,UAAU;IACV,OAAO;IACP,UAAU;IACV,QAAQ;IACR,OAAO;CACC,CAAC;AAEX;;;;;;;GAOG;AACH,SAAgB,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,uCAAY,OAAO,KAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAG;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,166 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_CYCLER_OPTIONS = void 0;
exports.createTokenCycler = createTokenCycler;
const core_util_1 = require("@azure/core-util");
// Default options for the cycler if none are provided
exports.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 (_a) {
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 (0, core_util_1.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
*/
function createTokenCycler(credential, tokenCyclerOptions) {
let refreshWorker = null;
let token = null;
let tenantId;
const options = Object.assign(Object.assign({}, exports.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() {
var _a;
if (cycler.isRefreshing) {
return false;
}
if ((token === null || token === void 0 ? void 0 : token.refreshAfterTimestamp) && token.refreshAfterTimestamp < Date.now()) {
return true;
}
return ((_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : 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) {
var _a;
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
(_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : 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,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.47.9"
}
]
}