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

21
backend/node_modules/@azure/keyvault-common/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) Microsoft Corporation.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

48
backend/node_modules/@azure/keyvault-common/README.md generated vendored Normal file
View File

@ -0,0 +1,48 @@
# Azure Key Vault Common client library for JavaScript
An internal support library for the various Azure Key Vault client libraries.
This package contains common code that needs to be shared among the other Azure Key Vault libraries. **It is not meant for usage by any other consumers**.
## Key Vault client libraries
The following client libraries use this package:
- [@azure/keyvault-admin](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/keyvault/keyvault-admin/README.md)
- [@azure/keyvault-certificates](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/keyvault/keyvault-certificates/README.md)
- [@azure/keyvault-keys](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/keyvault/keyvault-keys/README.md)
- [@azure/keyvault-secrets](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/keyvault/keyvault-secrets/README.md)
## Getting started
For information on getting started, please see our [Key Vault client libraries](#key-vault-client-libraries).
## Key concepts
For information on key concepts, please see our [Key Vault client libraries](#key-vault-client-libraries).
## Examples
For examples, please see our [Key Vault client libraries](#key-vault-client-libraries).
## Next steps
For information on next steps, please see our [Key Vault client libraries](#key-vault-client-libraries).
## Troubleshooting
If you run into issues while using this library, directly or indirectly, please feel free to [file an issue](https://github.com/Azure/azure-sdk-for-js/issues/new).
### Logging
Enabling logging may help uncover useful information about failures. In order to see a log of HTTP requests and responses, set the `AZURE_LOG_LEVEL` environment variable to `info`. Alternatively, logging can be enabled at runtime by calling `setLogLevel` in the `@azure/logger`:
```ts snippet:SetLogLevel
import { setLogLevel } from "@azure/logger";
setLogLevel("info");
```
## Contributing
If you'd like to contribute to this library, please read the [contributing guide](https://github.com/Azure/azure-sdk-for-js/blob/main/CONTRIBUTING.md) to learn more about how to build and test the code.

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

View File

@ -0,0 +1,91 @@
{
"name": "@azure/keyvault-common",
"version": "2.1.0",
"description": "Common internal functionality for all of the Azure Key Vault clients in the Azure SDK for JavaScript",
"sdk-type": "client",
"author": "Microsoft Corporation",
"license": "MIT",
"main": "./dist/commonjs/index.js",
"browser": "./dist/browser/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/commonjs/index.d.ts",
"files": [
"dist/",
"README.md",
"LICENSE"
],
"repository": "github:Azure/azure-sdk-for-js",
"engines": {
"node": ">=20.0.0"
},
"keywords": [
"azure",
"cloud",
"typescript"
],
"bugs": {
"url": "https://github.com/Azure/azure-sdk-for-js/issues"
},
"homepage": "https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/keyvault/keyvault-common/README.md",
"sideEffects": false,
"prettier": "@azure/eslint-plugin-azure-sdk/prettier.json",
"dependencies": {
"@azure-rest/core-client": "^2.3.3",
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-rest-pipeline": "^1.8.0",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.10.0",
"@azure/logger": "^1.1.4",
"tslib": "^2.2.0"
},
"devDependencies": {
"@types/node": "^20.19.25",
"@vitest/browser-playwright": "^4.1.2",
"@vitest/coverage-istanbul": "^4.1.2",
"cross-env": "^10.1.0",
"eslint": "^9.39.1",
"playwright": "^1.58.2",
"prettier": "^3.6.2",
"rimraf": "^6.1.0",
"typescript": "~6.0.2",
"vitest": "^4.1.2",
"@azure-tools/test-utils-vitest": "^2.0.1",
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
"@azure/dev-tool": "^1.0.0"
},
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"browser": {
"types": "./dist/browser/index.d.ts",
"default": "./dist/browser/index.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"scripts": {
"build": "npm run clean && dev-tool run build-package && dev-tool run extract-api",
"build:samples": "echo Skipped.",
"check-format": "prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
"clean": "rimraf --glob dist dist-* temp types *.tgz *.log",
"execute:samples": "dev-tool samples run samples-dev",
"extract-api": "dev-tool run build-package && dev-tool run extract-api",
"format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
"lint": "eslint package.json src test",
"lint:fix": "eslint package.json src test --fix --fix-type [problem,suggestion]",
"pack": "pnpm pack 2>&1",
"test": "npm run test:node && npm run test:browser",
"test:browser": "echo skipped",
"test:node": "dev-tool run build-test --no-browser-test && dev-tool run test:vitest --no-test-proxy",
"update-snippets": "dev-tool run update-snippets"
}
}