Commit iniziale

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

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

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2022 Microsoft
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.

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

@@ -0,0 +1,40 @@
# 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).
## 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.
![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-js%2Fsdk%2Fkeyvault%2Fkeyvault-common%2FREADME.png)

View File

@@ -0,0 +1,3 @@
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC"}

View File

@@ -0,0 +1,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\";\n"]}

View File

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

View File

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

View File

@@ -0,0 +1,151 @@
// 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, Object.assign(Object.assign({}, requestOptions), { enableCae: true, tenantId: challengeState.tenantId }));
if (token) {
request.headers.set("authorization", `Bearer ${token.token}`);
}
break;
}
}
}
async function handleChallenge(request, response, next) {
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
if (request.body === null && challengeState.status === "started") {
// Reset the original body before doing anything else.
// Note: If successful status will be "complete", otherwise "none" will
// restart the process.
request.body = challengeState.originalBody;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
logger.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], Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: parsedChallenge.tenantId }));
if (!accessToken) {
// No access token provided, treat as no-op
return response;
}
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
challengeState = {
status: "complete",
scopes: [scope],
tenantId: parsedChallenge.tenantId,
};
// We have a token now, so try send the request again
return next(request);
}
async function handleCaeChallenge(request, response, next) {
// Cannot handle CAE challenge if a regular challenge has not been completed first
if (challengeState.status !== "complete") {
return response;
}
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
return response;
}
const { claims: base64EncodedClaims, error } = parseWWWAuthenticateHeader(challenge);
if (error !== "insufficient_claims" || base64EncodedClaims === undefined) {
return response;
}
const claims = atob(base64EncodedClaims);
const accessToken = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: challengeState.tenantId, claims }));
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
return next(request);
}
async function sendRequest(request, next) {
// Add token if possible
await authorizeRequest(request);
// Try send request (first attempt)
let response = await next(request);
// Handle standard challenge if present
response = await handleChallenge(request, response, next);
// Handle CAE challenge if present
response = await handleCaeChallenge(request, response, next);
return response;
}
return {
name: 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 Object.assign(Object.assign({}, kvPairs), { [key]: value.join("=").slice(1, -1) });
}
}
return kvPairs;
}, {});
// Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.
if (parsed.authorization) {
try {
const tenantId = new URL(parsed.authorization).pathname.substring(1);
if (tenantId) {
parsed.tenantId = tenantId;
}
}
catch (_) {
throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);
}
}
return parsed;
}
//# sourceMappingURL=parseWWWAuthenticate.js.map

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,162 @@
// 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 (_a) {
return null;
}
}
else {
const finalToken = await getAccessToken();
// Timeout is up, so throw if it's still null
if (finalToken === null) {
throw new Error("Failed to refresh access token.");
}
return finalToken;
}
}
let token = await tryGetAccessToken();
while (token === null) {
await 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 = Object.assign(Object.assign({}, DEFAULT_CYCLER_OPTIONS), tokenCyclerOptions);
/**
* This little holder defines several predicates that we use to construct
* the rules of refreshing the token.
*/
const cycler = {
/**
* Produces true if a refresh job is currently in progress.
*/
get isRefreshing() {
return refreshWorker !== null;
},
/**
* Produces true if the cycler SHOULD refresh (we are within the refresh
* window and not already refreshing)
*/
get shouldRefresh() {
var _a;
if (cycler.isRefreshing) {
return false;
}
if ((token === null || token === void 0 ? void 0 : token.refreshAfterTimestamp) && token.refreshAfterTimestamp < Date.now()) {
return true;
}
return ((_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : 0) - options.refreshWindowInMs < Date.now();
},
/**
* Produces true if the cycler MUST refresh (null or nearly-expired
* token).
*/
get mustRefresh() {
return (token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now());
},
};
/**
* Starts a refresh job or returns the existing job if one is already
* running.
*/
function refresh(scopes, getTokenOptions) {
var _a;
if (!cycler.isRefreshing) {
// We bind `scopes` here to avoid passing it around a lot
const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
// Take advantage of promise chaining to insert an assignment to `token`
// before the refresh can be considered done.
refreshWorker = beginRefresh(tryGetAccessToken, options.retryIntervalInMs,
// If we don't have a token, then we should timeout immediately
(_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : Date.now())
.then((_token) => {
refreshWorker = null;
token = _token;
tenantId = getTokenOptions.tenantId;
return token;
})
.catch((reason) => {
// We also should reset the refresher if we enter a failed state. All
// existing awaiters will throw, but subsequent requests will start a
// new retry chain.
refreshWorker = null;
token = null;
tenantId = undefined;
throw reason;
});
}
return refreshWorker;
}
return async (scopes, tokenOptions) => {
//
// Simple rules:
// - If we MUST refresh, then return the refresh task, blocking
// the pipeline until a token is available.
// - If we SHOULD refresh, then run refresh but don't return it
// (we can still use the cached token).
// - Return the token, since it's fine if we didn't return in
// step 1.
//
const hasClaimChallenge = Boolean(tokenOptions.claims);
const tenantIdChanged = tenantId !== tokenOptions.tenantId;
if (hasClaimChallenge) {
// If we've received a claim, we know the existing token isn't valid
// We want to clear it so that that refresh worker won't use the old expiration time as a timeout
token = null;
}
// If the tenantId passed in token options is different to the one we have
// Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to
// refresh the token with the new tenantId or token.
const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;
if (mustRefresh) {
return refresh(scopes, tokenOptions);
}
if (cycler.shouldRefresh) {
refresh(scopes, tokenOptions);
}
return token;
};
}
//# sourceMappingURL=tokenCycler.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC"}

View File

@@ -0,0 +1,8 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./keyVaultAuthenticationPolicy.js"), exports);
tslib_1.__exportStar(require("./parseKeyVaultIdentifier.js"), exports);
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,uCAAuC;AACvC,kCAAkC;;;AAElC,4EAAkD;AAClD,uEAA6C","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport * from \"./keyVaultAuthenticationPolicy.js\";\nexport * from \"./parseKeyVaultIdentifier.js\";\n"]}

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = void 0;
const logger_1 = require("@azure/logger");
exports.logger = (0, logger_1.createClientLogger)("keyvault-common");
//# sourceMappingURL=logger.js.map

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -0,0 +1,3 @@
export * from "./keyVaultAuthenticationPolicy.js";
export * from "./parseKeyVaultIdentifier.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC"}

View File

@@ -0,0 +1,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\";\n"]}

View File

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

View File

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

View File

@@ -0,0 +1,151 @@
// 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, Object.assign(Object.assign({}, requestOptions), { enableCae: true, tenantId: challengeState.tenantId }));
if (token) {
request.headers.set("authorization", `Bearer ${token.token}`);
}
break;
}
}
}
async function handleChallenge(request, response, next) {
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
if (request.body === null && challengeState.status === "started") {
// Reset the original body before doing anything else.
// Note: If successful status will be "complete", otherwise "none" will
// restart the process.
request.body = challengeState.originalBody;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
logger.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], Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: parsedChallenge.tenantId }));
if (!accessToken) {
// No access token provided, treat as no-op
return response;
}
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
challengeState = {
status: "complete",
scopes: [scope],
tenantId: parsedChallenge.tenantId,
};
// We have a token now, so try send the request again
return next(request);
}
async function handleCaeChallenge(request, response, next) {
// Cannot handle CAE challenge if a regular challenge has not been completed first
if (challengeState.status !== "complete") {
return response;
}
// If status is not 401, this is a no-op
if (response.status !== 401) {
return response;
}
const getTokenOptions = requestToOptions(request);
const challenge = response.headers.get("WWW-Authenticate");
if (!challenge) {
return response;
}
const { claims: base64EncodedClaims, error } = parseWWWAuthenticateHeader(challenge);
if (error !== "insufficient_claims" || base64EncodedClaims === undefined) {
return response;
}
const claims = atob(base64EncodedClaims);
const accessToken = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: challengeState.tenantId, claims }));
request.headers.set("Authorization", `Bearer ${accessToken.token}`);
return next(request);
}
async function sendRequest(request, next) {
// Add token if possible
await authorizeRequest(request);
// Try send request (first attempt)
let response = await next(request);
// Handle standard challenge if present
response = await handleChallenge(request, response, next);
// Handle CAE challenge if present
response = await handleCaeChallenge(request, response, next);
return response;
}
return {
name: 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 Object.assign(Object.assign({}, kvPairs), { [key]: value.join("=").slice(1, -1) });
}
}
return kvPairs;
}, {});
// Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.
if (parsed.authorization) {
try {
const tenantId = new URL(parsed.authorization).pathname.substring(1);
if (tenantId) {
parsed.tenantId = tenantId;
}
}
catch (_) {
throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);
}
}
return parsed;
}
//# sourceMappingURL=parseWWWAuthenticate.js.map

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,162 @@
// 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 (_a) {
return null;
}
}
else {
const finalToken = await getAccessToken();
// Timeout is up, so throw if it's still null
if (finalToken === null) {
throw new Error("Failed to refresh access token.");
}
return finalToken;
}
}
let token = await tryGetAccessToken();
while (token === null) {
await 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 = Object.assign(Object.assign({}, DEFAULT_CYCLER_OPTIONS), tokenCyclerOptions);
/**
* This little holder defines several predicates that we use to construct
* the rules of refreshing the token.
*/
const cycler = {
/**
* Produces true if a refresh job is currently in progress.
*/
get isRefreshing() {
return refreshWorker !== null;
},
/**
* Produces true if the cycler SHOULD refresh (we are within the refresh
* window and not already refreshing)
*/
get shouldRefresh() {
var _a;
if (cycler.isRefreshing) {
return false;
}
if ((token === null || token === void 0 ? void 0 : token.refreshAfterTimestamp) && token.refreshAfterTimestamp < Date.now()) {
return true;
}
return ((_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : 0) - options.refreshWindowInMs < Date.now();
},
/**
* Produces true if the cycler MUST refresh (null or nearly-expired
* token).
*/
get mustRefresh() {
return (token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now());
},
};
/**
* Starts a refresh job or returns the existing job if one is already
* running.
*/
function refresh(scopes, getTokenOptions) {
var _a;
if (!cycler.isRefreshing) {
// We bind `scopes` here to avoid passing it around a lot
const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
// Take advantage of promise chaining to insert an assignment to `token`
// before the refresh can be considered done.
refreshWorker = beginRefresh(tryGetAccessToken, options.retryIntervalInMs,
// If we don't have a token, then we should timeout immediately
(_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : Date.now())
.then((_token) => {
refreshWorker = null;
token = _token;
tenantId = getTokenOptions.tenantId;
return token;
})
.catch((reason) => {
// We also should reset the refresher if we enter a failed state. All
// existing awaiters will throw, but subsequent requests will start a
// new retry chain.
refreshWorker = null;
token = null;
tenantId = undefined;
throw reason;
});
}
return refreshWorker;
}
return async (scopes, tokenOptions) => {
//
// Simple rules:
// - If we MUST refresh, then return the refresh task, blocking
// the pipeline until a token is available.
// - If we SHOULD refresh, then run refresh but don't return it
// (we can still use the cached token).
// - Return the token, since it's fine if we didn't return in
// step 1.
//
const hasClaimChallenge = Boolean(tokenOptions.claims);
const tenantIdChanged = tenantId !== tokenOptions.tenantId;
if (hasClaimChallenge) {
// If we've received a claim, we know the existing token isn't valid
// We want to clear it so that that refresh worker won't use the old expiration time as a timeout
token = null;
}
// If the tenantId passed in token options is different to the one we have
// Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to
// refresh the token with the new tenantId or token.
const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;
if (mustRefresh) {
return refresh(scopes, tokenOptions);
}
if (cycler.shouldRefresh) {
refresh(scopes, tokenOptions);
}
return token;
};
}
//# sourceMappingURL=tokenCycler.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
import { PipelinePolicy } from '@azure/core-rest-pipeline';
import { TokenCredential } from '@azure/core-auth';
/**
* 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;
/**
* Name of the Key Vault authentication policy.
*/
export declare const keyVaultAuthenticationPolicyName = "keyVaultAuthenticationPolicy";
/**
* Additional options for the challenge based authentication policy.
*/
export declare interface KeyVaultAuthenticationPolicyOptions {
/**
* Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.
*
* Defaults to false.
*/
disableChallengeResourceVerification?: boolean;
}
/**
* The parsed components of a Key Vault entity identifier.
*/
export declare 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;
export { }

113
node_modules/@azure/keyvault-common/package.json generated vendored Normal file
View File

@@ -0,0 +1,113 @@
{
"name": "@azure/keyvault-common",
"version": "2.0.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",
"scripts": {
"audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit",
"build": "npm run clean && dev-tool run build-package && dev-tool run extract-api",
"build:samples": "echo skipped",
"build:test": "npm run clean && dev-tool run build-package",
"check-format": "dev-tool run vendored 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": "dev-tool run vendored prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
"integration-test": "npm run integration-test:node && npm run integration-test:browser",
"integration-test:browser": "echo skipped",
"integration-test:node": "echo skipped",
"lint": "eslint README.md package.json api-extractor.json src test",
"lint:fix": "eslint README.md package.json api-extractor.json src test --fix --fix-type [problem,suggestion]",
"pack": "npm pack 2>&1",
"test": "npm run build:test && npm run unit-test:node && dev-tool run bundle && npm run unit-test:browser && npm run integration-test",
"test:browser": "npm run clean && npm run build:test && npm run integration-test:browser",
"test:node": "npm run clean && tsc -p . && npm run integration-test:node",
"unit-test": "npm run unit-test:node && npm run unit-test:browser",
"unit-test:browser": "echo skipped",
"unit-test:node": "dev-tool run test:vitest --no-test-proxy",
"update-snippets": "echo skipped"
},
"files": [
"dist/",
"README.md",
"LICENSE"
],
"repository": "github:Azure/azure-sdk-for-js",
"engines": {
"node": ">=18.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/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-client": "^1.5.0",
"@azure/core-rest-pipeline": "^1.8.0",
"@azure/core-tracing": "^1.0.0",
"@azure/logger": "^1.1.4",
"tslib": "^2.2.0",
"@azure/core-util": "^1.10.0"
},
"devDependencies": {
"@azure-tools/test-utils-vitest": "^1.0.0",
"@azure/dev-tool": "^1.0.0",
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
"@microsoft/api-extractor": "^7.31.1",
"@types/node": "^18.0.0",
"@vitest/browser": "^2.0.5",
"@vitest/coverage-istanbul": "^2.0.5",
"cross-env": "^7.0.2",
"eslint": "^9.9.0",
"playwright": "^1.46.0",
"rimraf": "^5.0.5",
"typescript": "~5.6.2",
"vitest": "^2.0.5"
},
"type": "module",
"tshy": {
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
},
"dialects": [
"esm",
"commonjs"
],
"esmDialects": [
"browser"
],
"selfLink": false
},
"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"
}
}
}
}