@azure/keyvault-common 2.0.1-alpha.20250619.1 → 2.0.1-alpha.20250730.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,7 +59,11 @@ export function keyVaultAuthenticationPolicy(credential, options = {}) {
59
59
  case "started":
60
60
  break; // Retry, we should not overwrite the original body
61
61
  case "complete": {
62
- const token = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, requestOptions), { enableCae: true, tenantId: challengeState.tenantId }));
62
+ const token = await getAccessToken(challengeState.scopes, {
63
+ ...requestOptions,
64
+ enableCae: true,
65
+ tenantId: challengeState.tenantId,
66
+ });
63
67
  if (token) {
64
68
  request.headers.set("authorization", `Bearer ${token.token}`);
65
69
  }
@@ -95,7 +99,11 @@ export function keyVaultAuthenticationPolicy(credential, options = {}) {
95
99
  if (!disableChallengeResourceVerification) {
96
100
  verifyChallengeResource(scope, request);
97
101
  }
98
- const accessToken = await getAccessToken([scope], Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: parsedChallenge.tenantId }));
102
+ const accessToken = await getAccessToken([scope], {
103
+ ...getTokenOptions,
104
+ enableCae: true,
105
+ tenantId: parsedChallenge.tenantId,
106
+ });
99
107
  if (!accessToken) {
100
108
  // No access token provided, treat as no-op
101
109
  return response;
@@ -128,7 +136,12 @@ export function keyVaultAuthenticationPolicy(credential, options = {}) {
128
136
  return response;
129
137
  }
130
138
  const claims = atob(base64EncodedClaims);
131
- const accessToken = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: challengeState.tenantId, claims }));
139
+ const accessToken = await getAccessToken(challengeState.scopes, {
140
+ ...getTokenOptions,
141
+ enableCae: true,
142
+ tenantId: challengeState.tenantId,
143
+ claims,
144
+ });
132
145
  request.headers.set("Authorization", `Bearer ${accessToken.token}`);
133
146
  return next(request);
134
147
  }
@@ -1 +1 @@
1
- {"version":3,"file":"keyVaultAuthenticationPolicy.js","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n PipelinePolicy,\n PipelineRequest,\n PipelineResponse,\n RequestBodyType,\n SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { WWWAuthenticate } from \"./parseWWWAuthenticate.js\";\nimport { parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate.js\";\n\nimport type { GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { createTokenCycler } from \"./tokenCycler.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * @internal\n * Holds the state of Challenge Auth.\n * When making the first request we force Key Vault to begin a challenge\n * by clearing out the request body and storing it locally.\n *\n * Later on, the authorizeRequestOnChallenge callback will process the\n * challenge and, if ready to resend the original request, reset the body\n * so that it may be sent again.\n *\n * Once a client has succeeded once, we can start skipping CAE.\n */\ntype ChallengeState =\n | {\n status: \"none\";\n }\n | {\n status: \"started\";\n originalBody?: RequestBodyType;\n }\n | {\n status: \"complete\";\n scopes: string[];\n tenantId?: string;\n };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface KeyVaultAuthenticationPolicyOptions {\n /**\n * Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.\n *\n * Defaults to false.\n */\n disableChallengeResourceVerification?: boolean;\n}\n\nfunction verifyChallengeResource(scope: string, request: PipelineRequest): void {\n let scopeAsUrl: URL;\n try {\n scopeAsUrl = new URL(scope);\n } catch (e) {\n throw new Error(`The challenge contains invalid scope '${scope}'`);\n }\n\n const requestUrl = new URL(request.url);\n\n if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {\n throw new Error(\n `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.`,\n );\n }\n}\n\n/**\n * Name of the Key Vault authentication policy.\n */\nexport const keyVaultAuthenticationPolicyName = \"keyVaultAuthenticationPolicy\";\n\n/**\n * A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.\n *\n * Key Vault supports other authentication schemes, but we ensure challenge authentication\n * is used by first sending a copy of the request, without authorization or content.\n *\n * when the challenge is received, it will be authenticated and used to send the original\n * request with authorization.\n *\n * Following the first request of a client, follow-up requests will get the cached token\n * if possible.\n *\n */\nexport function keyVaultAuthenticationPolicy(\n credential: TokenCredential,\n options: KeyVaultAuthenticationPolicyOptions = {},\n): PipelinePolicy {\n const { disableChallengeResourceVerification } = options;\n let challengeState: ChallengeState = { status: \"none\" };\n const getAccessToken = createTokenCycler(credential);\n\n function requestToOptions(request: PipelineRequest): GetTokenOptions {\n return {\n abortSignal: request.abortSignal,\n requestOptions: {\n timeout: request.timeout > 0 ? request.timeout : undefined,\n },\n tracingOptions: request.tracingOptions,\n };\n }\n\n async function authorizeRequest(request: PipelineRequest): Promise<void> {\n const requestOptions: GetTokenOptions = requestToOptions(request);\n\n switch (challengeState.status) {\n case \"none\":\n challengeState = {\n status: \"started\",\n originalBody: request.body,\n };\n request.body = null;\n break;\n case \"started\":\n break; // Retry, we should not overwrite the original body\n case \"complete\": {\n const token = await getAccessToken(challengeState.scopes, {\n ...requestOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n });\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token.token}`);\n }\n break;\n }\n }\n }\n\n async function handleChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n if (request.body === null && challengeState.status === \"started\") {\n // Reset the original body before doing anything else.\n // Note: If successful status will be \"complete\", otherwise \"none\" will\n // restart the process.\n request.body = challengeState.originalBody;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n logger.warning(\n \"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.\",\n );\n return response;\n }\n const parsedChallenge: WWWAuthenticate = parseWWWAuthenticateHeader(challenge);\n\n const scope = parsedChallenge.resource\n ? parsedChallenge.resource + \"/.default\"\n : parsedChallenge.scope;\n\n if (!scope) {\n // Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)\n return response;\n }\n\n if (!disableChallengeResourceVerification) {\n verifyChallengeResource(scope, request);\n }\n\n const accessToken = await getAccessToken([scope], {\n ...getTokenOptions,\n enableCae: true,\n tenantId: parsedChallenge.tenantId,\n });\n\n if (!accessToken) {\n // No access token provided, treat as no-op\n return response;\n }\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n challengeState = {\n status: \"complete\",\n scopes: [scope],\n tenantId: parsedChallenge.tenantId,\n };\n\n // We have a token now, so try send the request again\n return next(request);\n }\n\n async function handleCaeChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Cannot handle CAE challenge if a regular challenge has not been completed first\n if (challengeState.status !== \"complete\") {\n return response;\n }\n\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n return response;\n }\n const { claims: base64EncodedClaims, error }: WWWAuthenticate =\n parseWWWAuthenticateHeader(challenge);\n\n if (error !== \"insufficient_claims\" || base64EncodedClaims === undefined) {\n return response;\n }\n\n const claims = atob(base64EncodedClaims);\n\n const accessToken = await getAccessToken(challengeState.scopes, {\n ...getTokenOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n claims,\n });\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n return next(request);\n }\n\n async function sendRequest(\n request: PipelineRequest,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Add token if possible\n await authorizeRequest(request);\n\n // Try send request (first attempt)\n let response = await next(request);\n\n // Handle standard challenge if present\n response = await handleChallenge(request, response, next);\n\n // Handle CAE challenge if present\n response = await handleCaeChallenge(request, response, next);\n\n return response;\n }\n\n return {\n name: keyVaultAuthenticationPolicyName,\n sendRequest,\n };\n}\n"]}
1
+ {"version":3,"file":"keyVaultAuthenticationPolicy.js","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n PipelinePolicy,\n PipelineRequest,\n PipelineResponse,\n RequestBodyType,\n SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { WWWAuthenticate } from \"./parseWWWAuthenticate.js\";\nimport { parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate.js\";\n\nimport type { GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { createTokenCycler } from \"./tokenCycler.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * @internal\n * Holds the state of Challenge Auth.\n * When making the first request we force Key Vault to begin a challenge\n * by clearing out the request body and storing it locally.\n *\n * Later on, the authorizeRequestOnChallenge callback will process the\n * challenge and, if ready to resend the original request, reset the body\n * so that it may be sent again.\n *\n * Once a client has succeeded once, we can start skipping CAE.\n */\ntype ChallengeState =\n | {\n status: \"none\";\n }\n | {\n status: \"started\";\n originalBody?: RequestBodyType;\n }\n | {\n status: \"complete\";\n scopes: string[];\n tenantId?: string;\n };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface KeyVaultAuthenticationPolicyOptions {\n /**\n * Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.\n *\n * Defaults to false.\n */\n disableChallengeResourceVerification?: boolean;\n}\n\nfunction verifyChallengeResource(scope: string, request: PipelineRequest): void {\n let scopeAsUrl: URL;\n try {\n scopeAsUrl = new URL(scope);\n } catch (e) {\n throw new Error(`The challenge contains invalid scope '${scope}'`);\n }\n\n const requestUrl = new URL(request.url);\n\n if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {\n throw new Error(\n `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.`,\n );\n }\n}\n\n/**\n * Name of the Key Vault authentication policy.\n */\nexport const keyVaultAuthenticationPolicyName = \"keyVaultAuthenticationPolicy\";\n\n/**\n * A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.\n *\n * Key Vault supports other authentication schemes, but we ensure challenge authentication\n * is used by first sending a copy of the request, without authorization or content.\n *\n * when the challenge is received, it will be authenticated and used to send the original\n * request with authorization.\n *\n * Following the first request of a client, follow-up requests will get the cached token\n * if possible.\n *\n */\nexport function keyVaultAuthenticationPolicy(\n credential: TokenCredential,\n options: KeyVaultAuthenticationPolicyOptions = {},\n): PipelinePolicy {\n const { disableChallengeResourceVerification } = options;\n let challengeState: ChallengeState = { status: \"none\" };\n const getAccessToken = createTokenCycler(credential);\n\n function requestToOptions(request: PipelineRequest): GetTokenOptions {\n return {\n abortSignal: request.abortSignal,\n requestOptions: {\n timeout: request.timeout > 0 ? request.timeout : undefined,\n },\n tracingOptions: request.tracingOptions,\n };\n }\n\n async function authorizeRequest(request: PipelineRequest): Promise<void> {\n const requestOptions: GetTokenOptions = requestToOptions(request);\n\n switch (challengeState.status) {\n case \"none\":\n challengeState = {\n status: \"started\",\n originalBody: request.body,\n };\n request.body = null;\n break;\n case \"started\":\n break; // Retry, we should not overwrite the original body\n case \"complete\": {\n const token = await getAccessToken(challengeState.scopes, {\n ...requestOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n });\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token.token}`);\n }\n break;\n }\n }\n }\n\n async function handleChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n if (request.body === null && challengeState.status === \"started\") {\n // Reset the original body before doing anything else.\n // Note: If successful status will be \"complete\", otherwise \"none\" will\n // restart the process.\n request.body = challengeState.originalBody;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n logger.warning(\n \"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.\",\n );\n return response;\n }\n const parsedChallenge: WWWAuthenticate = parseWWWAuthenticateHeader(challenge);\n\n const scope = parsedChallenge.resource\n ? parsedChallenge.resource + \"/.default\"\n : parsedChallenge.scope;\n\n if (!scope) {\n // Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)\n return response;\n }\n\n if (!disableChallengeResourceVerification) {\n verifyChallengeResource(scope, request);\n }\n\n const accessToken = await getAccessToken([scope], {\n ...getTokenOptions,\n enableCae: true,\n tenantId: parsedChallenge.tenantId,\n });\n\n if (!accessToken) {\n // No access token provided, treat as no-op\n return response;\n }\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n challengeState = {\n status: \"complete\",\n scopes: [scope],\n tenantId: parsedChallenge.tenantId,\n };\n\n // We have a token now, so try send the request again\n return next(request);\n }\n\n async function handleCaeChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Cannot handle CAE challenge if a regular challenge has not been completed first\n if (challengeState.status !== \"complete\") {\n return response;\n }\n\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n return response;\n }\n const { claims: base64EncodedClaims, error }: WWWAuthenticate =\n parseWWWAuthenticateHeader(challenge);\n\n if (error !== \"insufficient_claims\" || base64EncodedClaims === undefined) {\n return response;\n }\n\n const claims = atob(base64EncodedClaims);\n\n const accessToken = await getAccessToken(challengeState.scopes, {\n ...getTokenOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n claims,\n });\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n return next(request);\n }\n\n async function sendRequest(\n request: PipelineRequest,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Add token if possible\n await authorizeRequest(request);\n\n // Try send request (first attempt)\n let response = await next(request);\n\n // Handle standard challenge if present\n response = await handleChallenge(request, response, next);\n\n // Handle CAE challenge if present\n response = await handleCaeChallenge(request, response, next);\n\n return response;\n }\n\n return {\n name: keyVaultAuthenticationPolicyName,\n sendRequest,\n };\n}\n"]}
@@ -25,7 +25,7 @@ export function parseWWWAuthenticateHeader(headerValue) {
25
25
  const [key, ...value] = p.split("=");
26
26
  if (validWWWAuthenticateProperties.includes(key)) {
27
27
  // The values will be wrapped in quotes, which need to be stripped out.
28
- return Object.assign(Object.assign({}, kvPairs), { [key]: value.join("=").slice(1, -1) });
28
+ return { ...kvPairs, [key]: value.join("=").slice(1, -1) };
29
29
  }
30
30
  }
31
31
  return kvPairs;
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"parseWWWAuthenticate.js","sourceRoot":"","sources":["../../src/parseWWWAuthenticate.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AA0ClC,MAAM,8BAA8B,GAAuC;IACzE,eAAe;IACf,mBAAmB;IACnB,UAAU;IACV,OAAO;IACP,UAAU;IACV,QAAQ;IACR,OAAO;CACC,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,WAAmB;IAC5D,MAAM,aAAa,GAAG,MAAM,CAAC;IAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,MAAM,CAAkB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QACrF,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,2FAA2F;YAC3F,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,8BAA8B,CAAC,QAAQ,CAAC,GAA4B,CAAC,EAAE,CAAC;gBAC1E,uEAAuE;gBACvE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,sGAAsG;IACtG,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACrE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,aAAa,eAAe,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.\n */\nexport interface WWWAuthenticate {\n /**\n * The authorization parameter, if present.\n */\n authorization?: string;\n\n /**\n * The authorization_url parameter, if present.\n */\n authorization_url?: string;\n\n /**\n * The resource parameter, if present.\n */\n resource?: string;\n\n /**\n * The scope parameter, if present.\n */\n scope?: string;\n\n /**\n * The tenantId parameter, if present.\n */\n tenantId?: string;\n\n /**\n * The claims parameter, if present.\n */\n claims?: string;\n\n /**\n * The error parameter, if present.\n */\n error?: string;\n}\n\nconst validWWWAuthenticateProperties: readonly (keyof WWWAuthenticate)[] = [\n \"authorization\",\n \"authorization_url\",\n \"resource\",\n \"scope\",\n \"tenantId\",\n \"claims\",\n \"error\",\n] as const;\n\n/**\n * Parses an WWW-Authenticate response header.\n * This transforms a string value like:\n * `Bearer authorization=\"https://some.url/tenantId\", resource=\"https://some.url\"`\n * into an object like:\n * `{ authorization: \"https://some.url/tenantId\", resource: \"https://some.url\" }`\n * @param headerValue - String value in the WWW-Authenticate header\n */\nexport function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate {\n const pairDelimiter = /,? +/;\n const parsed = headerValue.split(pairDelimiter).reduce<WWWAuthenticate>((kvPairs, p) => {\n if (p.match(/\\w=\"/)) {\n // 'sampleKey=\"sample_value\"' -> [sampleKey, \"sample_value\"] -> { sampleKey: sample_value }\n const [key, ...value] = p.split(\"=\");\n if (validWWWAuthenticateProperties.includes(key as keyof WWWAuthenticate)) {\n // The values will be wrapped in quotes, which need to be stripped out.\n return { ...kvPairs, [key]: value.join(\"=\").slice(1, -1) };\n }\n }\n return kvPairs;\n }, {});\n\n // Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.\n if (parsed.authorization) {\n try {\n const tenantId = new URL(parsed.authorization).pathname.substring(1);\n if (tenantId) {\n parsed.tenantId = tenantId;\n }\n } catch (_) {\n throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);\n }\n }\n\n return parsed;\n}\n"]}
@@ -25,7 +25,7 @@ async function beginRefresh(getAccessToken, retryIntervalInMs, refreshTimeout) {
25
25
  try {
26
26
  return await getAccessToken();
27
27
  }
28
- catch (_a) {
28
+ catch {
29
29
  return null;
30
30
  }
31
31
  }
@@ -63,7 +63,10 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
63
63
  let refreshWorker = null;
64
64
  let token = null;
65
65
  let tenantId;
66
- const options = Object.assign(Object.assign({}, DEFAULT_CYCLER_OPTIONS), tokenCyclerOptions);
66
+ const options = {
67
+ ...DEFAULT_CYCLER_OPTIONS,
68
+ ...tokenCyclerOptions,
69
+ };
67
70
  /**
68
71
  * This little holder defines several predicates that we use to construct
69
72
  * the rules of refreshing the token.
@@ -80,14 +83,13 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
80
83
  * window and not already refreshing)
81
84
  */
82
85
  get shouldRefresh() {
83
- var _a;
84
86
  if (cycler.isRefreshing) {
85
87
  return false;
86
88
  }
87
- if ((token === null || token === void 0 ? void 0 : token.refreshAfterTimestamp) && token.refreshAfterTimestamp < Date.now()) {
89
+ if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {
88
90
  return true;
89
91
  }
90
- return ((_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : 0) - options.refreshWindowInMs < Date.now();
92
+ return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();
91
93
  },
92
94
  /**
93
95
  * Produces true if the cycler MUST refresh (null or nearly-expired
@@ -102,7 +104,6 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
102
104
  * running.
103
105
  */
104
106
  function refresh(scopes, getTokenOptions) {
105
- var _a;
106
107
  if (!cycler.isRefreshing) {
107
108
  // We bind `scopes` here to avoid passing it around a lot
108
109
  const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
@@ -110,7 +111,7 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
110
111
  // before the refresh can be considered done.
111
112
  refreshWorker = beginRefresh(tryGetAccessToken, options.retryIntervalInMs,
112
113
  // If we don't have a token, then we should timeout immediately
113
- (_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : Date.now())
114
+ token?.expiresOnTimestamp ?? Date.now())
114
115
  .then((_token) => {
115
116
  refreshWorker = null;
116
117
  token = _token;
@@ -1 +1 @@
1
- {"version":3,"file":"tokenCycler.js","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file is a direct copy of the tokenCycler implementation in core-rest-pipeline.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { delay } from \"@azure/core-util\";\n\n/**\n * A function that gets a promise of an access token and allows providing\n * options.\n *\n * @param options - the options to pass to the underlying token provider\n */\nexport type AccessTokenGetter = (\n scopes: string | string[],\n options: GetTokenOptions,\n) => Promise<AccessToken>;\n\nexport interface TokenCyclerOptions {\n /**\n * The window of time before token expiration during which the token will be\n * considered unusable due to risk of the token expiring before sending the\n * request.\n *\n * This will only become meaningful if the refresh fails for over\n * (refreshWindow - forcedRefreshWindow) milliseconds.\n */\n forcedRefreshWindowInMs: number;\n /**\n * Interval in milliseconds to retry failed token refreshes.\n */\n retryIntervalInMs: number;\n /**\n * The window of time before token expiration during which\n * we will attempt to refresh the token.\n */\n refreshWindowInMs: number;\n}\n\n// Default options for the cycler if none are provided\nexport const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions = {\n forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires\n retryIntervalInMs: 3000, // Allow refresh attempts every 3s\n refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry\n};\n\n/**\n * Converts an an unreliable access token getter (which may resolve with null)\n * into an AccessTokenGetter by retrying the unreliable getter in a regular\n * interval.\n *\n * @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.\n * @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.\n * @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.\n * @returns - A promise that, if it resolves, will resolve with an access token.\n */\nasync function beginRefresh(\n getAccessToken: () => Promise<AccessToken | null>,\n retryIntervalInMs: number,\n refreshTimeout: number,\n): Promise<AccessToken> {\n // This wrapper handles exceptions gracefully as long as we haven't exceeded\n // the timeout.\n async function tryGetAccessToken(): Promise<AccessToken | null> {\n if (Date.now() < refreshTimeout) {\n try {\n return await getAccessToken();\n } catch {\n return null;\n }\n } else {\n const finalToken = await getAccessToken();\n\n // Timeout is up, so throw if it's still null\n if (finalToken === null) {\n throw new Error(\"Failed to refresh access token.\");\n }\n\n return finalToken;\n }\n }\n\n let token: AccessToken | null = await tryGetAccessToken();\n\n while (token === null) {\n await delay(retryIntervalInMs);\n\n token = await tryGetAccessToken();\n }\n\n return token;\n}\n\n/**\n * Creates a token cycler from a credential, scopes, and optional settings.\n *\n * A token cycler represents a way to reliably retrieve a valid access token\n * from a TokenCredential. It will handle initializing the token, refreshing it\n * when it nears expiration, and synchronizes refresh attempts to avoid\n * concurrency hazards.\n *\n * @param credential - the underlying TokenCredential that provides the access\n * token\n * @param tokenCyclerOptions - optionally override default settings for the cycler\n *\n * @returns - a function that reliably produces a valid access token\n */\nexport function createTokenCycler(\n credential: TokenCredential,\n tokenCyclerOptions?: Partial<TokenCyclerOptions>,\n): AccessTokenGetter {\n let refreshWorker: Promise<AccessToken> | null = null;\n let token: AccessToken | null = null;\n let tenantId: string | undefined;\n\n const options = {\n ...DEFAULT_CYCLER_OPTIONS,\n ...tokenCyclerOptions,\n };\n\n /**\n * This little holder defines several predicates that we use to construct\n * the rules of refreshing the token.\n */\n const cycler = {\n /**\n * Produces true if a refresh job is currently in progress.\n */\n get isRefreshing(): boolean {\n return refreshWorker !== null;\n },\n /**\n * Produces true if the cycler SHOULD refresh (we are within the refresh\n * window and not already refreshing)\n */\n get shouldRefresh(): boolean {\n if (cycler.isRefreshing) {\n return false;\n }\n if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {\n return true;\n }\n\n return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();\n },\n /**\n * Produces true if the cycler MUST refresh (null or nearly-expired\n * token).\n */\n get mustRefresh(): boolean {\n return (\n token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now()\n );\n },\n };\n\n /**\n * Starts a refresh job or returns the existing job if one is already\n * running.\n */\n function refresh(\n scopes: string | string[],\n getTokenOptions: GetTokenOptions,\n ): Promise<AccessToken> {\n if (!cycler.isRefreshing) {\n // We bind `scopes` here to avoid passing it around a lot\n const tryGetAccessToken = (): Promise<AccessToken | null> =>\n credential.getToken(scopes, getTokenOptions);\n\n // Take advantage of promise chaining to insert an assignment to `token`\n // before the refresh can be considered done.\n refreshWorker = beginRefresh(\n tryGetAccessToken,\n options.retryIntervalInMs,\n // If we don't have a token, then we should timeout immediately\n token?.expiresOnTimestamp ?? Date.now(),\n )\n .then((_token) => {\n refreshWorker = null;\n token = _token;\n tenantId = getTokenOptions.tenantId;\n return token;\n })\n .catch((reason) => {\n // We also should reset the refresher if we enter a failed state. All\n // existing awaiters will throw, but subsequent requests will start a\n // new retry chain.\n refreshWorker = null;\n token = null;\n tenantId = undefined;\n throw reason;\n });\n }\n\n return refreshWorker as Promise<AccessToken>;\n }\n\n return async (scopes: string | string[], tokenOptions: GetTokenOptions): Promise<AccessToken> => {\n //\n // Simple rules:\n // - If we MUST refresh, then return the refresh task, blocking\n // the pipeline until a token is available.\n // - If we SHOULD refresh, then run refresh but don't return it\n // (we can still use the cached token).\n // - Return the token, since it's fine if we didn't return in\n // step 1.\n //\n\n const hasClaimChallenge = Boolean(tokenOptions.claims);\n const tenantIdChanged = tenantId !== tokenOptions.tenantId;\n\n if (hasClaimChallenge) {\n // If we've received a claim, we know the existing token isn't valid\n // We want to clear it so that that refresh worker won't use the old expiration time as a timeout\n token = null;\n }\n\n // If the tenantId passed in token options is different to the one we have\n // Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to\n // refresh the token with the new tenantId or token.\n const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;\n\n if (mustRefresh) {\n return refresh(scopes, tokenOptions);\n }\n\n if (cycler.shouldRefresh) {\n refresh(scopes, tokenOptions);\n }\n\n return token as AccessToken;\n };\n}\n"]}
1
+ {"version":3,"file":"tokenCycler.js","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file is a direct copy of the tokenCycler implementation in core-rest-pipeline.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { delay } from \"@azure/core-util\";\n\n/**\n * A function that gets a promise of an access token and allows providing\n * options.\n *\n * @param options - the options to pass to the underlying token provider\n */\nexport type AccessTokenGetter = (\n scopes: string | string[],\n options: GetTokenOptions,\n) => Promise<AccessToken>;\n\nexport interface TokenCyclerOptions {\n /**\n * The window of time before token expiration during which the token will be\n * considered unusable due to risk of the token expiring before sending the\n * request.\n *\n * This will only become meaningful if the refresh fails for over\n * (refreshWindow - forcedRefreshWindow) milliseconds.\n */\n forcedRefreshWindowInMs: number;\n /**\n * Interval in milliseconds to retry failed token refreshes.\n */\n retryIntervalInMs: number;\n /**\n * The window of time before token expiration during which\n * we will attempt to refresh the token.\n */\n refreshWindowInMs: number;\n}\n\n// Default options for the cycler if none are provided\nexport const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions = {\n forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires\n retryIntervalInMs: 3000, // Allow refresh attempts every 3s\n refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry\n};\n\n/**\n * Converts an an unreliable access token getter (which may resolve with null)\n * into an AccessTokenGetter by retrying the unreliable getter in a regular\n * interval.\n *\n * @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.\n * @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.\n * @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.\n * @returns - A promise that, if it resolves, will resolve with an access token.\n */\nasync function beginRefresh(\n getAccessToken: () => Promise<AccessToken | null>,\n retryIntervalInMs: number,\n refreshTimeout: number,\n): Promise<AccessToken> {\n // This wrapper handles exceptions gracefully as long as we haven't exceeded\n // the timeout.\n async function tryGetAccessToken(): Promise<AccessToken | null> {\n if (Date.now() < refreshTimeout) {\n try {\n return await getAccessToken();\n } catch {\n return null;\n }\n } else {\n const finalToken = await getAccessToken();\n\n // Timeout is up, so throw if it's still null\n if (finalToken === null) {\n throw new Error(\"Failed to refresh access token.\");\n }\n\n return finalToken;\n }\n }\n\n let token: AccessToken | null = await tryGetAccessToken();\n\n while (token === null) {\n await delay(retryIntervalInMs);\n\n token = await tryGetAccessToken();\n }\n\n return token;\n}\n\n/**\n * Creates a token cycler from a credential, scopes, and optional settings.\n *\n * A token cycler represents a way to reliably retrieve a valid access token\n * from a TokenCredential. It will handle initializing the token, refreshing it\n * when it nears expiration, and synchronizes refresh attempts to avoid\n * concurrency hazards.\n *\n * @param credential - the underlying TokenCredential that provides the access\n * token\n * @param tokenCyclerOptions - optionally override default settings for the cycler\n *\n * @returns - a function that reliably produces a valid access token\n */\nexport function createTokenCycler(\n credential: TokenCredential,\n tokenCyclerOptions?: Partial<TokenCyclerOptions>,\n): AccessTokenGetter {\n let refreshWorker: Promise<AccessToken> | null = null;\n let token: AccessToken | null = null;\n let tenantId: string | undefined;\n\n const options = {\n ...DEFAULT_CYCLER_OPTIONS,\n ...tokenCyclerOptions,\n };\n\n /**\n * This little holder defines several predicates that we use to construct\n * the rules of refreshing the token.\n */\n const cycler = {\n /**\n * Produces true if a refresh job is currently in progress.\n */\n get isRefreshing(): boolean {\n return refreshWorker !== null;\n },\n /**\n * Produces true if the cycler SHOULD refresh (we are within the refresh\n * window and not already refreshing)\n */\n get shouldRefresh(): boolean {\n if (cycler.isRefreshing) {\n return false;\n }\n if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {\n return true;\n }\n\n return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();\n },\n /**\n * Produces true if the cycler MUST refresh (null or nearly-expired\n * token).\n */\n get mustRefresh(): boolean {\n return (\n token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now()\n );\n },\n };\n\n /**\n * Starts a refresh job or returns the existing job if one is already\n * running.\n */\n function refresh(\n scopes: string | string[],\n getTokenOptions: GetTokenOptions,\n ): Promise<AccessToken> {\n if (!cycler.isRefreshing) {\n // We bind `scopes` here to avoid passing it around a lot\n const tryGetAccessToken = (): Promise<AccessToken | null> =>\n credential.getToken(scopes, getTokenOptions);\n\n // Take advantage of promise chaining to insert an assignment to `token`\n // before the refresh can be considered done.\n refreshWorker = beginRefresh(\n tryGetAccessToken,\n options.retryIntervalInMs,\n // If we don't have a token, then we should timeout immediately\n token?.expiresOnTimestamp ?? Date.now(),\n )\n .then((_token) => {\n refreshWorker = null;\n token = _token;\n tenantId = getTokenOptions.tenantId;\n return token;\n })\n .catch((reason) => {\n // We also should reset the refresher if we enter a failed state. All\n // existing awaiters will throw, but subsequent requests will start a\n // new retry chain.\n refreshWorker = null;\n token = null;\n tenantId = undefined;\n throw reason;\n });\n }\n\n return refreshWorker as Promise<AccessToken>;\n }\n\n return async (scopes: string | string[], tokenOptions: GetTokenOptions): Promise<AccessToken> => {\n //\n // Simple rules:\n // - If we MUST refresh, then return the refresh task, blocking\n // the pipeline until a token is available.\n // - If we SHOULD refresh, then run refresh but don't return it\n // (we can still use the cached token).\n // - Return the token, since it's fine if we didn't return in\n // step 1.\n //\n\n const hasClaimChallenge = Boolean(tokenOptions.claims);\n const tenantIdChanged = tenantId !== tokenOptions.tenantId;\n\n if (hasClaimChallenge) {\n // If we've received a claim, we know the existing token isn't valid\n // We want to clear it so that that refresh worker won't use the old expiration time as a timeout\n token = null;\n }\n\n // If the tenantId passed in token options is different to the one we have\n // Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to\n // refresh the token with the new tenantId or token.\n const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;\n\n if (mustRefresh) {\n return refresh(scopes, tokenOptions);\n }\n\n if (cycler.shouldRefresh) {\n refresh(scopes, tokenOptions);\n }\n\n return token as AccessToken;\n };\n}\n"]}
@@ -63,7 +63,11 @@ function keyVaultAuthenticationPolicy(credential, options = {}) {
63
63
  case "started":
64
64
  break; // Retry, we should not overwrite the original body
65
65
  case "complete": {
66
- const token = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, requestOptions), { enableCae: true, tenantId: challengeState.tenantId }));
66
+ const token = await getAccessToken(challengeState.scopes, {
67
+ ...requestOptions,
68
+ enableCae: true,
69
+ tenantId: challengeState.tenantId,
70
+ });
67
71
  if (token) {
68
72
  request.headers.set("authorization", `Bearer ${token.token}`);
69
73
  }
@@ -99,7 +103,11 @@ function keyVaultAuthenticationPolicy(credential, options = {}) {
99
103
  if (!disableChallengeResourceVerification) {
100
104
  verifyChallengeResource(scope, request);
101
105
  }
102
- const accessToken = await getAccessToken([scope], Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: parsedChallenge.tenantId }));
106
+ const accessToken = await getAccessToken([scope], {
107
+ ...getTokenOptions,
108
+ enableCae: true,
109
+ tenantId: parsedChallenge.tenantId,
110
+ });
103
111
  if (!accessToken) {
104
112
  // No access token provided, treat as no-op
105
113
  return response;
@@ -132,7 +140,12 @@ function keyVaultAuthenticationPolicy(credential, options = {}) {
132
140
  return response;
133
141
  }
134
142
  const claims = atob(base64EncodedClaims);
135
- const accessToken = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: challengeState.tenantId, claims }));
143
+ const accessToken = await getAccessToken(challengeState.scopes, {
144
+ ...getTokenOptions,
145
+ enableCae: true,
146
+ tenantId: challengeState.tenantId,
147
+ claims,
148
+ });
136
149
  request.headers.set("Authorization", `Bearer ${accessToken.token}`);
137
150
  return next(request);
138
151
  }
@@ -1 +1 @@
1
- {"version":3,"file":"keyVaultAuthenticationPolicy.js","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n PipelinePolicy,\n PipelineRequest,\n PipelineResponse,\n RequestBodyType,\n SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { WWWAuthenticate } from \"./parseWWWAuthenticate.js\";\nimport { parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate.js\";\n\nimport type { GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { createTokenCycler } from \"./tokenCycler.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * @internal\n * Holds the state of Challenge Auth.\n * When making the first request we force Key Vault to begin a challenge\n * by clearing out the request body and storing it locally.\n *\n * Later on, the authorizeRequestOnChallenge callback will process the\n * challenge and, if ready to resend the original request, reset the body\n * so that it may be sent again.\n *\n * Once a client has succeeded once, we can start skipping CAE.\n */\ntype ChallengeState =\n | {\n status: \"none\";\n }\n | {\n status: \"started\";\n originalBody?: RequestBodyType;\n }\n | {\n status: \"complete\";\n scopes: string[];\n tenantId?: string;\n };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface KeyVaultAuthenticationPolicyOptions {\n /**\n * Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.\n *\n * Defaults to false.\n */\n disableChallengeResourceVerification?: boolean;\n}\n\nfunction verifyChallengeResource(scope: string, request: PipelineRequest): void {\n let scopeAsUrl: URL;\n try {\n scopeAsUrl = new URL(scope);\n } catch (e) {\n throw new Error(`The challenge contains invalid scope '${scope}'`);\n }\n\n const requestUrl = new URL(request.url);\n\n if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {\n throw new Error(\n `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.`,\n );\n }\n}\n\n/**\n * Name of the Key Vault authentication policy.\n */\nexport const keyVaultAuthenticationPolicyName = \"keyVaultAuthenticationPolicy\";\n\n/**\n * A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.\n *\n * Key Vault supports other authentication schemes, but we ensure challenge authentication\n * is used by first sending a copy of the request, without authorization or content.\n *\n * when the challenge is received, it will be authenticated and used to send the original\n * request with authorization.\n *\n * Following the first request of a client, follow-up requests will get the cached token\n * if possible.\n *\n */\nexport function keyVaultAuthenticationPolicy(\n credential: TokenCredential,\n options: KeyVaultAuthenticationPolicyOptions = {},\n): PipelinePolicy {\n const { disableChallengeResourceVerification } = options;\n let challengeState: ChallengeState = { status: \"none\" };\n const getAccessToken = createTokenCycler(credential);\n\n function requestToOptions(request: PipelineRequest): GetTokenOptions {\n return {\n abortSignal: request.abortSignal,\n requestOptions: {\n timeout: request.timeout > 0 ? request.timeout : undefined,\n },\n tracingOptions: request.tracingOptions,\n };\n }\n\n async function authorizeRequest(request: PipelineRequest): Promise<void> {\n const requestOptions: GetTokenOptions = requestToOptions(request);\n\n switch (challengeState.status) {\n case \"none\":\n challengeState = {\n status: \"started\",\n originalBody: request.body,\n };\n request.body = null;\n break;\n case \"started\":\n break; // Retry, we should not overwrite the original body\n case \"complete\": {\n const token = await getAccessToken(challengeState.scopes, {\n ...requestOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n });\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token.token}`);\n }\n break;\n }\n }\n }\n\n async function handleChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n if (request.body === null && challengeState.status === \"started\") {\n // Reset the original body before doing anything else.\n // Note: If successful status will be \"complete\", otherwise \"none\" will\n // restart the process.\n request.body = challengeState.originalBody;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n logger.warning(\n \"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.\",\n );\n return response;\n }\n const parsedChallenge: WWWAuthenticate = parseWWWAuthenticateHeader(challenge);\n\n const scope = parsedChallenge.resource\n ? parsedChallenge.resource + \"/.default\"\n : parsedChallenge.scope;\n\n if (!scope) {\n // Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)\n return response;\n }\n\n if (!disableChallengeResourceVerification) {\n verifyChallengeResource(scope, request);\n }\n\n const accessToken = await getAccessToken([scope], {\n ...getTokenOptions,\n enableCae: true,\n tenantId: parsedChallenge.tenantId,\n });\n\n if (!accessToken) {\n // No access token provided, treat as no-op\n return response;\n }\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n challengeState = {\n status: \"complete\",\n scopes: [scope],\n tenantId: parsedChallenge.tenantId,\n };\n\n // We have a token now, so try send the request again\n return next(request);\n }\n\n async function handleCaeChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Cannot handle CAE challenge if a regular challenge has not been completed first\n if (challengeState.status !== \"complete\") {\n return response;\n }\n\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n return response;\n }\n const { claims: base64EncodedClaims, error }: WWWAuthenticate =\n parseWWWAuthenticateHeader(challenge);\n\n if (error !== \"insufficient_claims\" || base64EncodedClaims === undefined) {\n return response;\n }\n\n const claims = atob(base64EncodedClaims);\n\n const accessToken = await getAccessToken(challengeState.scopes, {\n ...getTokenOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n claims,\n });\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n return next(request);\n }\n\n async function sendRequest(\n request: PipelineRequest,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Add token if possible\n await authorizeRequest(request);\n\n // Try send request (first attempt)\n let response = await next(request);\n\n // Handle standard challenge if present\n response = await handleChallenge(request, response, next);\n\n // Handle CAE challenge if present\n response = await handleCaeChallenge(request, response, next);\n\n return response;\n }\n\n return {\n name: keyVaultAuthenticationPolicyName,\n sendRequest,\n };\n}\n"]}
1
+ {"version":3,"file":"keyVaultAuthenticationPolicy.js","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n PipelinePolicy,\n PipelineRequest,\n PipelineResponse,\n RequestBodyType,\n SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { WWWAuthenticate } from \"./parseWWWAuthenticate.js\";\nimport { parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate.js\";\n\nimport type { GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { createTokenCycler } from \"./tokenCycler.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * @internal\n * Holds the state of Challenge Auth.\n * When making the first request we force Key Vault to begin a challenge\n * by clearing out the request body and storing it locally.\n *\n * Later on, the authorizeRequestOnChallenge callback will process the\n * challenge and, if ready to resend the original request, reset the body\n * so that it may be sent again.\n *\n * Once a client has succeeded once, we can start skipping CAE.\n */\ntype ChallengeState =\n | {\n status: \"none\";\n }\n | {\n status: \"started\";\n originalBody?: RequestBodyType;\n }\n | {\n status: \"complete\";\n scopes: string[];\n tenantId?: string;\n };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface KeyVaultAuthenticationPolicyOptions {\n /**\n * Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.\n *\n * Defaults to false.\n */\n disableChallengeResourceVerification?: boolean;\n}\n\nfunction verifyChallengeResource(scope: string, request: PipelineRequest): void {\n let scopeAsUrl: URL;\n try {\n scopeAsUrl = new URL(scope);\n } catch (e) {\n throw new Error(`The challenge contains invalid scope '${scope}'`);\n }\n\n const requestUrl = new URL(request.url);\n\n if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {\n throw new Error(\n `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.`,\n );\n }\n}\n\n/**\n * Name of the Key Vault authentication policy.\n */\nexport const keyVaultAuthenticationPolicyName = \"keyVaultAuthenticationPolicy\";\n\n/**\n * A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.\n *\n * Key Vault supports other authentication schemes, but we ensure challenge authentication\n * is used by first sending a copy of the request, without authorization or content.\n *\n * when the challenge is received, it will be authenticated and used to send the original\n * request with authorization.\n *\n * Following the first request of a client, follow-up requests will get the cached token\n * if possible.\n *\n */\nexport function keyVaultAuthenticationPolicy(\n credential: TokenCredential,\n options: KeyVaultAuthenticationPolicyOptions = {},\n): PipelinePolicy {\n const { disableChallengeResourceVerification } = options;\n let challengeState: ChallengeState = { status: \"none\" };\n const getAccessToken = createTokenCycler(credential);\n\n function requestToOptions(request: PipelineRequest): GetTokenOptions {\n return {\n abortSignal: request.abortSignal,\n requestOptions: {\n timeout: request.timeout > 0 ? request.timeout : undefined,\n },\n tracingOptions: request.tracingOptions,\n };\n }\n\n async function authorizeRequest(request: PipelineRequest): Promise<void> {\n const requestOptions: GetTokenOptions = requestToOptions(request);\n\n switch (challengeState.status) {\n case \"none\":\n challengeState = {\n status: \"started\",\n originalBody: request.body,\n };\n request.body = null;\n break;\n case \"started\":\n break; // Retry, we should not overwrite the original body\n case \"complete\": {\n const token = await getAccessToken(challengeState.scopes, {\n ...requestOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n });\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token.token}`);\n }\n break;\n }\n }\n }\n\n async function handleChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n if (request.body === null && challengeState.status === \"started\") {\n // Reset the original body before doing anything else.\n // Note: If successful status will be \"complete\", otherwise \"none\" will\n // restart the process.\n request.body = challengeState.originalBody;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n logger.warning(\n \"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.\",\n );\n return response;\n }\n const parsedChallenge: WWWAuthenticate = parseWWWAuthenticateHeader(challenge);\n\n const scope = parsedChallenge.resource\n ? parsedChallenge.resource + \"/.default\"\n : parsedChallenge.scope;\n\n if (!scope) {\n // Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)\n return response;\n }\n\n if (!disableChallengeResourceVerification) {\n verifyChallengeResource(scope, request);\n }\n\n const accessToken = await getAccessToken([scope], {\n ...getTokenOptions,\n enableCae: true,\n tenantId: parsedChallenge.tenantId,\n });\n\n if (!accessToken) {\n // No access token provided, treat as no-op\n return response;\n }\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n challengeState = {\n status: \"complete\",\n scopes: [scope],\n tenantId: parsedChallenge.tenantId,\n };\n\n // We have a token now, so try send the request again\n return next(request);\n }\n\n async function handleCaeChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Cannot handle CAE challenge if a regular challenge has not been completed first\n if (challengeState.status !== \"complete\") {\n return response;\n }\n\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n return response;\n }\n const { claims: base64EncodedClaims, error }: WWWAuthenticate =\n parseWWWAuthenticateHeader(challenge);\n\n if (error !== \"insufficient_claims\" || base64EncodedClaims === undefined) {\n return response;\n }\n\n const claims = atob(base64EncodedClaims);\n\n const accessToken = await getAccessToken(challengeState.scopes, {\n ...getTokenOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n claims,\n });\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n return next(request);\n }\n\n async function sendRequest(\n request: PipelineRequest,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Add token if possible\n await authorizeRequest(request);\n\n // Try send request (first attempt)\n let response = await next(request);\n\n // Handle standard challenge if present\n response = await handleChallenge(request, response, next);\n\n // Handle CAE challenge if present\n response = await handleCaeChallenge(request, response, next);\n\n return response;\n }\n\n return {\n name: keyVaultAuthenticationPolicyName,\n sendRequest,\n };\n}\n"]}
@@ -28,7 +28,7 @@ function parseWWWAuthenticateHeader(headerValue) {
28
28
  const [key, ...value] = p.split("=");
29
29
  if (validWWWAuthenticateProperties.includes(key)) {
30
30
  // The values will be wrapped in quotes, which need to be stripped out.
31
- return Object.assign(Object.assign({}, kvPairs), { [key]: value.join("=").slice(1, -1) });
31
+ return { ...kvPairs, [key]: value.join("=").slice(1, -1) };
32
32
  }
33
33
  }
34
34
  return kvPairs;
@@ -1 +1 @@
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"]}
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,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,sGAAsG;IACtG,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACrE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,aAAa,eAAe,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.\n */\nexport interface WWWAuthenticate {\n /**\n * The authorization parameter, if present.\n */\n authorization?: string;\n\n /**\n * The authorization_url parameter, if present.\n */\n authorization_url?: string;\n\n /**\n * The resource parameter, if present.\n */\n resource?: string;\n\n /**\n * The scope parameter, if present.\n */\n scope?: string;\n\n /**\n * The tenantId parameter, if present.\n */\n tenantId?: string;\n\n /**\n * The claims parameter, if present.\n */\n claims?: string;\n\n /**\n * The error parameter, if present.\n */\n error?: string;\n}\n\nconst validWWWAuthenticateProperties: readonly (keyof WWWAuthenticate)[] = [\n \"authorization\",\n \"authorization_url\",\n \"resource\",\n \"scope\",\n \"tenantId\",\n \"claims\",\n \"error\",\n] as const;\n\n/**\n * Parses an WWW-Authenticate response header.\n * This transforms a string value like:\n * `Bearer authorization=\"https://some.url/tenantId\", resource=\"https://some.url\"`\n * into an object like:\n * `{ authorization: \"https://some.url/tenantId\", resource: \"https://some.url\" }`\n * @param headerValue - String value in the WWW-Authenticate header\n */\nexport function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate {\n const pairDelimiter = /,? +/;\n const parsed = headerValue.split(pairDelimiter).reduce<WWWAuthenticate>((kvPairs, p) => {\n if (p.match(/\\w=\"/)) {\n // 'sampleKey=\"sample_value\"' -> [sampleKey, \"sample_value\"] -> { sampleKey: sample_value }\n const [key, ...value] = p.split(\"=\");\n if (validWWWAuthenticateProperties.includes(key as keyof WWWAuthenticate)) {\n // The values will be wrapped in quotes, which need to be stripped out.\n return { ...kvPairs, [key]: value.join(\"=\").slice(1, -1) };\n }\n }\n return kvPairs;\n }, {});\n\n // Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.\n if (parsed.authorization) {\n try {\n const tenantId = new URL(parsed.authorization).pathname.substring(1);\n if (tenantId) {\n parsed.tenantId = tenantId;\n }\n } catch (_) {\n throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);\n }\n }\n\n return parsed;\n}\n"]}
@@ -29,7 +29,7 @@ async function beginRefresh(getAccessToken, retryIntervalInMs, refreshTimeout) {
29
29
  try {
30
30
  return await getAccessToken();
31
31
  }
32
- catch (_a) {
32
+ catch {
33
33
  return null;
34
34
  }
35
35
  }
@@ -67,7 +67,10 @@ function createTokenCycler(credential, tokenCyclerOptions) {
67
67
  let refreshWorker = null;
68
68
  let token = null;
69
69
  let tenantId;
70
- const options = Object.assign(Object.assign({}, exports.DEFAULT_CYCLER_OPTIONS), tokenCyclerOptions);
70
+ const options = {
71
+ ...exports.DEFAULT_CYCLER_OPTIONS,
72
+ ...tokenCyclerOptions,
73
+ };
71
74
  /**
72
75
  * This little holder defines several predicates that we use to construct
73
76
  * the rules of refreshing the token.
@@ -84,14 +87,13 @@ function createTokenCycler(credential, tokenCyclerOptions) {
84
87
  * window and not already refreshing)
85
88
  */
86
89
  get shouldRefresh() {
87
- var _a;
88
90
  if (cycler.isRefreshing) {
89
91
  return false;
90
92
  }
91
- if ((token === null || token === void 0 ? void 0 : token.refreshAfterTimestamp) && token.refreshAfterTimestamp < Date.now()) {
93
+ if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {
92
94
  return true;
93
95
  }
94
- return ((_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : 0) - options.refreshWindowInMs < Date.now();
96
+ return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();
95
97
  },
96
98
  /**
97
99
  * Produces true if the cycler MUST refresh (null or nearly-expired
@@ -106,7 +108,6 @@ function createTokenCycler(credential, tokenCyclerOptions) {
106
108
  * running.
107
109
  */
108
110
  function refresh(scopes, getTokenOptions) {
109
- var _a;
110
111
  if (!cycler.isRefreshing) {
111
112
  // We bind `scopes` here to avoid passing it around a lot
112
113
  const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
@@ -114,7 +115,7 @@ function createTokenCycler(credential, tokenCyclerOptions) {
114
115
  // before the refresh can be considered done.
115
116
  refreshWorker = beginRefresh(tryGetAccessToken, options.retryIntervalInMs,
116
117
  // If we don't have a token, then we should timeout immediately
117
- (_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : Date.now())
118
+ token?.expiresOnTimestamp ?? Date.now())
118
119
  .then((_token) => {
119
120
  refreshWorker = null;
120
121
  token = _token;
@@ -1 +1 @@
1
- {"version":3,"file":"tokenCycler.js","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file is a direct copy of the tokenCycler implementation in core-rest-pipeline.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { delay } from \"@azure/core-util\";\n\n/**\n * A function that gets a promise of an access token and allows providing\n * options.\n *\n * @param options - the options to pass to the underlying token provider\n */\nexport type AccessTokenGetter = (\n scopes: string | string[],\n options: GetTokenOptions,\n) => Promise<AccessToken>;\n\nexport interface TokenCyclerOptions {\n /**\n * The window of time before token expiration during which the token will be\n * considered unusable due to risk of the token expiring before sending the\n * request.\n *\n * This will only become meaningful if the refresh fails for over\n * (refreshWindow - forcedRefreshWindow) milliseconds.\n */\n forcedRefreshWindowInMs: number;\n /**\n * Interval in milliseconds to retry failed token refreshes.\n */\n retryIntervalInMs: number;\n /**\n * The window of time before token expiration during which\n * we will attempt to refresh the token.\n */\n refreshWindowInMs: number;\n}\n\n// Default options for the cycler if none are provided\nexport const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions = {\n forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires\n retryIntervalInMs: 3000, // Allow refresh attempts every 3s\n refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry\n};\n\n/**\n * Converts an an unreliable access token getter (which may resolve with null)\n * into an AccessTokenGetter by retrying the unreliable getter in a regular\n * interval.\n *\n * @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.\n * @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.\n * @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.\n * @returns - A promise that, if it resolves, will resolve with an access token.\n */\nasync function beginRefresh(\n getAccessToken: () => Promise<AccessToken | null>,\n retryIntervalInMs: number,\n refreshTimeout: number,\n): Promise<AccessToken> {\n // This wrapper handles exceptions gracefully as long as we haven't exceeded\n // the timeout.\n async function tryGetAccessToken(): Promise<AccessToken | null> {\n if (Date.now() < refreshTimeout) {\n try {\n return await getAccessToken();\n } catch {\n return null;\n }\n } else {\n const finalToken = await getAccessToken();\n\n // Timeout is up, so throw if it's still null\n if (finalToken === null) {\n throw new Error(\"Failed to refresh access token.\");\n }\n\n return finalToken;\n }\n }\n\n let token: AccessToken | null = await tryGetAccessToken();\n\n while (token === null) {\n await delay(retryIntervalInMs);\n\n token = await tryGetAccessToken();\n }\n\n return token;\n}\n\n/**\n * Creates a token cycler from a credential, scopes, and optional settings.\n *\n * A token cycler represents a way to reliably retrieve a valid access token\n * from a TokenCredential. It will handle initializing the token, refreshing it\n * when it nears expiration, and synchronizes refresh attempts to avoid\n * concurrency hazards.\n *\n * @param credential - the underlying TokenCredential that provides the access\n * token\n * @param tokenCyclerOptions - optionally override default settings for the cycler\n *\n * @returns - a function that reliably produces a valid access token\n */\nexport function createTokenCycler(\n credential: TokenCredential,\n tokenCyclerOptions?: Partial<TokenCyclerOptions>,\n): AccessTokenGetter {\n let refreshWorker: Promise<AccessToken> | null = null;\n let token: AccessToken | null = null;\n let tenantId: string | undefined;\n\n const options = {\n ...DEFAULT_CYCLER_OPTIONS,\n ...tokenCyclerOptions,\n };\n\n /**\n * This little holder defines several predicates that we use to construct\n * the rules of refreshing the token.\n */\n const cycler = {\n /**\n * Produces true if a refresh job is currently in progress.\n */\n get isRefreshing(): boolean {\n return refreshWorker !== null;\n },\n /**\n * Produces true if the cycler SHOULD refresh (we are within the refresh\n * window and not already refreshing)\n */\n get shouldRefresh(): boolean {\n if (cycler.isRefreshing) {\n return false;\n }\n if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {\n return true;\n }\n\n return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();\n },\n /**\n * Produces true if the cycler MUST refresh (null or nearly-expired\n * token).\n */\n get mustRefresh(): boolean {\n return (\n token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now()\n );\n },\n };\n\n /**\n * Starts a refresh job or returns the existing job if one is already\n * running.\n */\n function refresh(\n scopes: string | string[],\n getTokenOptions: GetTokenOptions,\n ): Promise<AccessToken> {\n if (!cycler.isRefreshing) {\n // We bind `scopes` here to avoid passing it around a lot\n const tryGetAccessToken = (): Promise<AccessToken | null> =>\n credential.getToken(scopes, getTokenOptions);\n\n // Take advantage of promise chaining to insert an assignment to `token`\n // before the refresh can be considered done.\n refreshWorker = beginRefresh(\n tryGetAccessToken,\n options.retryIntervalInMs,\n // If we don't have a token, then we should timeout immediately\n token?.expiresOnTimestamp ?? Date.now(),\n )\n .then((_token) => {\n refreshWorker = null;\n token = _token;\n tenantId = getTokenOptions.tenantId;\n return token;\n })\n .catch((reason) => {\n // We also should reset the refresher if we enter a failed state. All\n // existing awaiters will throw, but subsequent requests will start a\n // new retry chain.\n refreshWorker = null;\n token = null;\n tenantId = undefined;\n throw reason;\n });\n }\n\n return refreshWorker as Promise<AccessToken>;\n }\n\n return async (scopes: string | string[], tokenOptions: GetTokenOptions): Promise<AccessToken> => {\n //\n // Simple rules:\n // - If we MUST refresh, then return the refresh task, blocking\n // the pipeline until a token is available.\n // - If we SHOULD refresh, then run refresh but don't return it\n // (we can still use the cached token).\n // - Return the token, since it's fine if we didn't return in\n // step 1.\n //\n\n const hasClaimChallenge = Boolean(tokenOptions.claims);\n const tenantIdChanged = tenantId !== tokenOptions.tenantId;\n\n if (hasClaimChallenge) {\n // If we've received a claim, we know the existing token isn't valid\n // We want to clear it so that that refresh worker won't use the old expiration time as a timeout\n token = null;\n }\n\n // If the tenantId passed in token options is different to the one we have\n // Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to\n // refresh the token with the new tenantId or token.\n const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;\n\n if (mustRefresh) {\n return refresh(scopes, tokenOptions);\n }\n\n if (cycler.shouldRefresh) {\n refresh(scopes, tokenOptions);\n }\n\n return token as AccessToken;\n };\n}\n"]}
1
+ {"version":3,"file":"tokenCycler.js","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file is a direct copy of the tokenCycler implementation in core-rest-pipeline.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { delay } from \"@azure/core-util\";\n\n/**\n * A function that gets a promise of an access token and allows providing\n * options.\n *\n * @param options - the options to pass to the underlying token provider\n */\nexport type AccessTokenGetter = (\n scopes: string | string[],\n options: GetTokenOptions,\n) => Promise<AccessToken>;\n\nexport interface TokenCyclerOptions {\n /**\n * The window of time before token expiration during which the token will be\n * considered unusable due to risk of the token expiring before sending the\n * request.\n *\n * This will only become meaningful if the refresh fails for over\n * (refreshWindow - forcedRefreshWindow) milliseconds.\n */\n forcedRefreshWindowInMs: number;\n /**\n * Interval in milliseconds to retry failed token refreshes.\n */\n retryIntervalInMs: number;\n /**\n * The window of time before token expiration during which\n * we will attempt to refresh the token.\n */\n refreshWindowInMs: number;\n}\n\n// Default options for the cycler if none are provided\nexport const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions = {\n forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires\n retryIntervalInMs: 3000, // Allow refresh attempts every 3s\n refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry\n};\n\n/**\n * Converts an an unreliable access token getter (which may resolve with null)\n * into an AccessTokenGetter by retrying the unreliable getter in a regular\n * interval.\n *\n * @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.\n * @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.\n * @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.\n * @returns - A promise that, if it resolves, will resolve with an access token.\n */\nasync function beginRefresh(\n getAccessToken: () => Promise<AccessToken | null>,\n retryIntervalInMs: number,\n refreshTimeout: number,\n): Promise<AccessToken> {\n // This wrapper handles exceptions gracefully as long as we haven't exceeded\n // the timeout.\n async function tryGetAccessToken(): Promise<AccessToken | null> {\n if (Date.now() < refreshTimeout) {\n try {\n return await getAccessToken();\n } catch {\n return null;\n }\n } else {\n const finalToken = await getAccessToken();\n\n // Timeout is up, so throw if it's still null\n if (finalToken === null) {\n throw new Error(\"Failed to refresh access token.\");\n }\n\n return finalToken;\n }\n }\n\n let token: AccessToken | null = await tryGetAccessToken();\n\n while (token === null) {\n await delay(retryIntervalInMs);\n\n token = await tryGetAccessToken();\n }\n\n return token;\n}\n\n/**\n * Creates a token cycler from a credential, scopes, and optional settings.\n *\n * A token cycler represents a way to reliably retrieve a valid access token\n * from a TokenCredential. It will handle initializing the token, refreshing it\n * when it nears expiration, and synchronizes refresh attempts to avoid\n * concurrency hazards.\n *\n * @param credential - the underlying TokenCredential that provides the access\n * token\n * @param tokenCyclerOptions - optionally override default settings for the cycler\n *\n * @returns - a function that reliably produces a valid access token\n */\nexport function createTokenCycler(\n credential: TokenCredential,\n tokenCyclerOptions?: Partial<TokenCyclerOptions>,\n): AccessTokenGetter {\n let refreshWorker: Promise<AccessToken> | null = null;\n let token: AccessToken | null = null;\n let tenantId: string | undefined;\n\n const options = {\n ...DEFAULT_CYCLER_OPTIONS,\n ...tokenCyclerOptions,\n };\n\n /**\n * This little holder defines several predicates that we use to construct\n * the rules of refreshing the token.\n */\n const cycler = {\n /**\n * Produces true if a refresh job is currently in progress.\n */\n get isRefreshing(): boolean {\n return refreshWorker !== null;\n },\n /**\n * Produces true if the cycler SHOULD refresh (we are within the refresh\n * window and not already refreshing)\n */\n get shouldRefresh(): boolean {\n if (cycler.isRefreshing) {\n return false;\n }\n if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {\n return true;\n }\n\n return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();\n },\n /**\n * Produces true if the cycler MUST refresh (null or nearly-expired\n * token).\n */\n get mustRefresh(): boolean {\n return (\n token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now()\n );\n },\n };\n\n /**\n * Starts a refresh job or returns the existing job if one is already\n * running.\n */\n function refresh(\n scopes: string | string[],\n getTokenOptions: GetTokenOptions,\n ): Promise<AccessToken> {\n if (!cycler.isRefreshing) {\n // We bind `scopes` here to avoid passing it around a lot\n const tryGetAccessToken = (): Promise<AccessToken | null> =>\n credential.getToken(scopes, getTokenOptions);\n\n // Take advantage of promise chaining to insert an assignment to `token`\n // before the refresh can be considered done.\n refreshWorker = beginRefresh(\n tryGetAccessToken,\n options.retryIntervalInMs,\n // If we don't have a token, then we should timeout immediately\n token?.expiresOnTimestamp ?? Date.now(),\n )\n .then((_token) => {\n refreshWorker = null;\n token = _token;\n tenantId = getTokenOptions.tenantId;\n return token;\n })\n .catch((reason) => {\n // We also should reset the refresher if we enter a failed state. All\n // existing awaiters will throw, but subsequent requests will start a\n // new retry chain.\n refreshWorker = null;\n token = null;\n tenantId = undefined;\n throw reason;\n });\n }\n\n return refreshWorker as Promise<AccessToken>;\n }\n\n return async (scopes: string | string[], tokenOptions: GetTokenOptions): Promise<AccessToken> => {\n //\n // Simple rules:\n // - If we MUST refresh, then return the refresh task, blocking\n // the pipeline until a token is available.\n // - If we SHOULD refresh, then run refresh but don't return it\n // (we can still use the cached token).\n // - Return the token, since it's fine if we didn't return in\n // step 1.\n //\n\n const hasClaimChallenge = Boolean(tokenOptions.claims);\n const tenantIdChanged = tenantId !== tokenOptions.tenantId;\n\n if (hasClaimChallenge) {\n // If we've received a claim, we know the existing token isn't valid\n // We want to clear it so that that refresh worker won't use the old expiration time as a timeout\n token = null;\n }\n\n // If the tenantId passed in token options is different to the one we have\n // Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to\n // refresh the token with the new tenantId or token.\n const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;\n\n if (mustRefresh) {\n return refresh(scopes, tokenOptions);\n }\n\n if (cycler.shouldRefresh) {\n refresh(scopes, tokenOptions);\n }\n\n return token as AccessToken;\n };\n}\n"]}
@@ -1,11 +1,11 @@
1
- // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
- // It should be published with your NPM package. It should not be tracked by Git.
3
- {
4
- "tsdocVersion": "0.12",
5
- "toolPackages": [
6
- {
7
- "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.52.8"
9
- }
10
- ]
11
- }
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.52.9"
9
+ }
10
+ ]
11
+ }
@@ -59,7 +59,11 @@ export function keyVaultAuthenticationPolicy(credential, options = {}) {
59
59
  case "started":
60
60
  break; // Retry, we should not overwrite the original body
61
61
  case "complete": {
62
- const token = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, requestOptions), { enableCae: true, tenantId: challengeState.tenantId }));
62
+ const token = await getAccessToken(challengeState.scopes, {
63
+ ...requestOptions,
64
+ enableCae: true,
65
+ tenantId: challengeState.tenantId,
66
+ });
63
67
  if (token) {
64
68
  request.headers.set("authorization", `Bearer ${token.token}`);
65
69
  }
@@ -95,7 +99,11 @@ export function keyVaultAuthenticationPolicy(credential, options = {}) {
95
99
  if (!disableChallengeResourceVerification) {
96
100
  verifyChallengeResource(scope, request);
97
101
  }
98
- const accessToken = await getAccessToken([scope], Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: parsedChallenge.tenantId }));
102
+ const accessToken = await getAccessToken([scope], {
103
+ ...getTokenOptions,
104
+ enableCae: true,
105
+ tenantId: parsedChallenge.tenantId,
106
+ });
99
107
  if (!accessToken) {
100
108
  // No access token provided, treat as no-op
101
109
  return response;
@@ -128,7 +136,12 @@ export function keyVaultAuthenticationPolicy(credential, options = {}) {
128
136
  return response;
129
137
  }
130
138
  const claims = atob(base64EncodedClaims);
131
- const accessToken = await getAccessToken(challengeState.scopes, Object.assign(Object.assign({}, getTokenOptions), { enableCae: true, tenantId: challengeState.tenantId, claims }));
139
+ const accessToken = await getAccessToken(challengeState.scopes, {
140
+ ...getTokenOptions,
141
+ enableCae: true,
142
+ tenantId: challengeState.tenantId,
143
+ claims,
144
+ });
132
145
  request.headers.set("Authorization", `Bearer ${accessToken.token}`);
133
146
  return next(request);
134
147
  }
@@ -1 +1 @@
1
- {"version":3,"file":"keyVaultAuthenticationPolicy.js","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAUlC,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAGvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAwCrC,SAAS,uBAAuB,CAAC,KAAa,EAAE,OAAwB;IACtE,IAAI,UAAe,CAAC;IACpB,IAAI,CAAC;QACH,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,yCAAyC,KAAK,GAAG,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAExC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CACb,2BAA2B,UAAU,CAAC,QAAQ,0LAA0L,CACzO,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,8BAA8B,CAAC;AAE/E;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,4BAA4B,CAC1C,UAA2B,EAC3B,UAA+C,EAAE;IAEjD,MAAM,EAAE,oCAAoC,EAAE,GAAG,OAAO,CAAC;IACzD,IAAI,cAAc,GAAmB,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACxD,MAAM,cAAc,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAErD,SAAS,gBAAgB,CAAC,OAAwB;QAChD,OAAO;YACL,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,cAAc,EAAE;gBACd,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;aAC3D;YACD,cAAc,EAAE,OAAO,CAAC,cAAc;SACvC,CAAC;IACJ,CAAC;IAED,KAAK,UAAU,gBAAgB,CAAC,OAAwB;QACtD,MAAM,cAAc,GAAoB,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAElE,QAAQ,cAAc,CAAC,MAAM,EAAE,CAAC;YAC9B,KAAK,MAAM;gBACT,cAAc,GAAG;oBACf,MAAM,EAAE,SAAS;oBACjB,YAAY,EAAE,OAAO,CAAC,IAAI;iBAC3B,CAAC;gBACF,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;gBACpB,MAAM;YACR,KAAK,SAAS;gBACZ,MAAM,CAAC,mDAAmD;YAC5D,KAAK,UAAU,CAAC,CAAC,CAAC;gBAChB,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,cAAc,CAAC,MAAM,kCACnD,cAAc,KACjB,SAAS,EAAE,IAAI,EACf,QAAQ,EAAE,cAAc,CAAC,QAAQ,IACjC,CAAC;gBACH,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;gBAChE,CAAC;gBACD,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,UAAU,eAAe,CAC5B,OAAwB,EACxB,QAA0B,EAC1B,IAAiB;QAEjB,wCAAwC;QACxC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,cAAc,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjE,sDAAsD;YACtD,uEAAuE;YACvE,uBAAuB;YACvB,OAAO,CAAC,IAAI,GAAG,cAAc,CAAC,YAAY,CAAC;QAC7C,CAAC;QAED,MAAM,eAAe,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAElD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAC3D,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,OAAO,CACZ,8JAA8J,CAC/J,CAAC;YACF,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,MAAM,eAAe,GAAoB,0BAA0B,CAAC,SAAS,CAAC,CAAC;QAE/E,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ;YACpC,CAAC,CAAC,eAAe,CAAC,QAAQ,GAAG,WAAW;YACxC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC;QAE1B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,8FAA8F;YAC9F,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,IAAI,CAAC,oCAAoC,EAAE,CAAC;YAC1C,uBAAuB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,CAAC,KAAK,CAAC,kCAC3C,eAAe,KAClB,SAAS,EAAE,IAAI,EACf,QAAQ,EAAE,eAAe,CAAC,QAAQ,IAClC,CAAC;QAEH,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,2CAA2C;YAC3C,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;QAEpE,cAAc,GAAG;YACf,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,CAAC,KAAK,CAAC;YACf,QAAQ,EAAE,eAAe,CAAC,QAAQ;SACnC,CAAC;QAEF,qDAAqD;QACrD,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,UAAU,kBAAkB,CAC/B,OAAwB,EACxB,QAA0B,EAC1B,IAAiB;QAEjB,kFAAkF;QAClF,IAAI,cAAc,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACzC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,wCAAwC;QACxC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,eAAe,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAElD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAC3D,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,GAC1C,0BAA0B,CAAC,SAAS,CAAC,CAAC;QAExC,IAAI,KAAK,KAAK,qBAAqB,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;YACzE,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAEzC,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,cAAc,CAAC,MAAM,kCACzD,eAAe,KAClB,SAAS,EAAE,IAAI,EACf,QAAQ,EAAE,cAAc,CAAC,QAAQ,EACjC,MAAM,IACN,CAAC;QAEH,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;QAEpE,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,UAAU,WAAW,CACxB,OAAwB,EACxB,IAAiB;QAEjB,wBAAwB;QACxB,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAEhC,mCAAmC;QACnC,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;QAEnC,uCAAuC;QACvC,QAAQ,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAE1D,kCAAkC;QAClC,QAAQ,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAE7D,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO;QACL,IAAI,EAAE,gCAAgC;QACtC,WAAW;KACZ,CAAC;AACJ,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n PipelinePolicy,\n PipelineRequest,\n PipelineResponse,\n RequestBodyType,\n SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { WWWAuthenticate } from \"./parseWWWAuthenticate.js\";\nimport { parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate.js\";\n\nimport type { GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { createTokenCycler } from \"./tokenCycler.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * @internal\n * Holds the state of Challenge Auth.\n * When making the first request we force Key Vault to begin a challenge\n * by clearing out the request body and storing it locally.\n *\n * Later on, the authorizeRequestOnChallenge callback will process the\n * challenge and, if ready to resend the original request, reset the body\n * so that it may be sent again.\n *\n * Once a client has succeeded once, we can start skipping CAE.\n */\ntype ChallengeState =\n | {\n status: \"none\";\n }\n | {\n status: \"started\";\n originalBody?: RequestBodyType;\n }\n | {\n status: \"complete\";\n scopes: string[];\n tenantId?: string;\n };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface KeyVaultAuthenticationPolicyOptions {\n /**\n * Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.\n *\n * Defaults to false.\n */\n disableChallengeResourceVerification?: boolean;\n}\n\nfunction verifyChallengeResource(scope: string, request: PipelineRequest): void {\n let scopeAsUrl: URL;\n try {\n scopeAsUrl = new URL(scope);\n } catch (e) {\n throw new Error(`The challenge contains invalid scope '${scope}'`);\n }\n\n const requestUrl = new URL(request.url);\n\n if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {\n throw new Error(\n `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.`,\n );\n }\n}\n\n/**\n * Name of the Key Vault authentication policy.\n */\nexport const keyVaultAuthenticationPolicyName = \"keyVaultAuthenticationPolicy\";\n\n/**\n * A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.\n *\n * Key Vault supports other authentication schemes, but we ensure challenge authentication\n * is used by first sending a copy of the request, without authorization or content.\n *\n * when the challenge is received, it will be authenticated and used to send the original\n * request with authorization.\n *\n * Following the first request of a client, follow-up requests will get the cached token\n * if possible.\n *\n */\nexport function keyVaultAuthenticationPolicy(\n credential: TokenCredential,\n options: KeyVaultAuthenticationPolicyOptions = {},\n): PipelinePolicy {\n const { disableChallengeResourceVerification } = options;\n let challengeState: ChallengeState = { status: \"none\" };\n const getAccessToken = createTokenCycler(credential);\n\n function requestToOptions(request: PipelineRequest): GetTokenOptions {\n return {\n abortSignal: request.abortSignal,\n requestOptions: {\n timeout: request.timeout > 0 ? request.timeout : undefined,\n },\n tracingOptions: request.tracingOptions,\n };\n }\n\n async function authorizeRequest(request: PipelineRequest): Promise<void> {\n const requestOptions: GetTokenOptions = requestToOptions(request);\n\n switch (challengeState.status) {\n case \"none\":\n challengeState = {\n status: \"started\",\n originalBody: request.body,\n };\n request.body = null;\n break;\n case \"started\":\n break; // Retry, we should not overwrite the original body\n case \"complete\": {\n const token = await getAccessToken(challengeState.scopes, {\n ...requestOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n });\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token.token}`);\n }\n break;\n }\n }\n }\n\n async function handleChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n if (request.body === null && challengeState.status === \"started\") {\n // Reset the original body before doing anything else.\n // Note: If successful status will be \"complete\", otherwise \"none\" will\n // restart the process.\n request.body = challengeState.originalBody;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n logger.warning(\n \"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.\",\n );\n return response;\n }\n const parsedChallenge: WWWAuthenticate = parseWWWAuthenticateHeader(challenge);\n\n const scope = parsedChallenge.resource\n ? parsedChallenge.resource + \"/.default\"\n : parsedChallenge.scope;\n\n if (!scope) {\n // Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)\n return response;\n }\n\n if (!disableChallengeResourceVerification) {\n verifyChallengeResource(scope, request);\n }\n\n const accessToken = await getAccessToken([scope], {\n ...getTokenOptions,\n enableCae: true,\n tenantId: parsedChallenge.tenantId,\n });\n\n if (!accessToken) {\n // No access token provided, treat as no-op\n return response;\n }\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n challengeState = {\n status: \"complete\",\n scopes: [scope],\n tenantId: parsedChallenge.tenantId,\n };\n\n // We have a token now, so try send the request again\n return next(request);\n }\n\n async function handleCaeChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Cannot handle CAE challenge if a regular challenge has not been completed first\n if (challengeState.status !== \"complete\") {\n return response;\n }\n\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n return response;\n }\n const { claims: base64EncodedClaims, error }: WWWAuthenticate =\n parseWWWAuthenticateHeader(challenge);\n\n if (error !== \"insufficient_claims\" || base64EncodedClaims === undefined) {\n return response;\n }\n\n const claims = atob(base64EncodedClaims);\n\n const accessToken = await getAccessToken(challengeState.scopes, {\n ...getTokenOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n claims,\n });\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n return next(request);\n }\n\n async function sendRequest(\n request: PipelineRequest,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Add token if possible\n await authorizeRequest(request);\n\n // Try send request (first attempt)\n let response = await next(request);\n\n // Handle standard challenge if present\n response = await handleChallenge(request, response, next);\n\n // Handle CAE challenge if present\n response = await handleCaeChallenge(request, response, next);\n\n return response;\n }\n\n return {\n name: keyVaultAuthenticationPolicyName,\n sendRequest,\n };\n}\n"]}
1
+ {"version":3,"file":"keyVaultAuthenticationPolicy.js","sourceRoot":"","sources":["../../src/keyVaultAuthenticationPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n PipelinePolicy,\n PipelineRequest,\n PipelineResponse,\n RequestBodyType,\n SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { WWWAuthenticate } from \"./parseWWWAuthenticate.js\";\nimport { parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate.js\";\n\nimport type { GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { createTokenCycler } from \"./tokenCycler.js\";\nimport { logger } from \"./logger.js\";\n\n/**\n * @internal\n * Holds the state of Challenge Auth.\n * When making the first request we force Key Vault to begin a challenge\n * by clearing out the request body and storing it locally.\n *\n * Later on, the authorizeRequestOnChallenge callback will process the\n * challenge and, if ready to resend the original request, reset the body\n * so that it may be sent again.\n *\n * Once a client has succeeded once, we can start skipping CAE.\n */\ntype ChallengeState =\n | {\n status: \"none\";\n }\n | {\n status: \"started\";\n originalBody?: RequestBodyType;\n }\n | {\n status: \"complete\";\n scopes: string[];\n tenantId?: string;\n };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface KeyVaultAuthenticationPolicyOptions {\n /**\n * Whether to disable verification that the challenge resource matches the Key Vault or Managed HSM domain.\n *\n * Defaults to false.\n */\n disableChallengeResourceVerification?: boolean;\n}\n\nfunction verifyChallengeResource(scope: string, request: PipelineRequest): void {\n let scopeAsUrl: URL;\n try {\n scopeAsUrl = new URL(scope);\n } catch (e) {\n throw new Error(`The challenge contains invalid scope '${scope}'`);\n }\n\n const requestUrl = new URL(request.url);\n\n if (!requestUrl.hostname.endsWith(`.${scopeAsUrl.hostname}`)) {\n throw new Error(\n `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.`,\n );\n }\n}\n\n/**\n * Name of the Key Vault authentication policy.\n */\nexport const keyVaultAuthenticationPolicyName = \"keyVaultAuthenticationPolicy\";\n\n/**\n * A custom implementation of the bearer-token authentication policy that handles Key Vault and CAE challenges.\n *\n * Key Vault supports other authentication schemes, but we ensure challenge authentication\n * is used by first sending a copy of the request, without authorization or content.\n *\n * when the challenge is received, it will be authenticated and used to send the original\n * request with authorization.\n *\n * Following the first request of a client, follow-up requests will get the cached token\n * if possible.\n *\n */\nexport function keyVaultAuthenticationPolicy(\n credential: TokenCredential,\n options: KeyVaultAuthenticationPolicyOptions = {},\n): PipelinePolicy {\n const { disableChallengeResourceVerification } = options;\n let challengeState: ChallengeState = { status: \"none\" };\n const getAccessToken = createTokenCycler(credential);\n\n function requestToOptions(request: PipelineRequest): GetTokenOptions {\n return {\n abortSignal: request.abortSignal,\n requestOptions: {\n timeout: request.timeout > 0 ? request.timeout : undefined,\n },\n tracingOptions: request.tracingOptions,\n };\n }\n\n async function authorizeRequest(request: PipelineRequest): Promise<void> {\n const requestOptions: GetTokenOptions = requestToOptions(request);\n\n switch (challengeState.status) {\n case \"none\":\n challengeState = {\n status: \"started\",\n originalBody: request.body,\n };\n request.body = null;\n break;\n case \"started\":\n break; // Retry, we should not overwrite the original body\n case \"complete\": {\n const token = await getAccessToken(challengeState.scopes, {\n ...requestOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n });\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token.token}`);\n }\n break;\n }\n }\n }\n\n async function handleChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n if (request.body === null && challengeState.status === \"started\") {\n // Reset the original body before doing anything else.\n // Note: If successful status will be \"complete\", otherwise \"none\" will\n // restart the process.\n request.body = challengeState.originalBody;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n logger.warning(\n \"keyVaultAuthentication policy encountered a 401 response without a corresponding WWW-Authenticate header. This is unexpected. Not handling the 401 response.\",\n );\n return response;\n }\n const parsedChallenge: WWWAuthenticate = parseWWWAuthenticateHeader(challenge);\n\n const scope = parsedChallenge.resource\n ? parsedChallenge.resource + \"/.default\"\n : parsedChallenge.scope;\n\n if (!scope) {\n // Cannot handle this kind of challenge here (if scope is not present, may be a CAE challenge)\n return response;\n }\n\n if (!disableChallengeResourceVerification) {\n verifyChallengeResource(scope, request);\n }\n\n const accessToken = await getAccessToken([scope], {\n ...getTokenOptions,\n enableCae: true,\n tenantId: parsedChallenge.tenantId,\n });\n\n if (!accessToken) {\n // No access token provided, treat as no-op\n return response;\n }\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n challengeState = {\n status: \"complete\",\n scopes: [scope],\n tenantId: parsedChallenge.tenantId,\n };\n\n // We have a token now, so try send the request again\n return next(request);\n }\n\n async function handleCaeChallenge(\n request: PipelineRequest,\n response: PipelineResponse,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Cannot handle CAE challenge if a regular challenge has not been completed first\n if (challengeState.status !== \"complete\") {\n return response;\n }\n\n // If status is not 401, this is a no-op\n if (response.status !== 401) {\n return response;\n }\n\n const getTokenOptions = requestToOptions(request);\n\n const challenge = response.headers.get(\"WWW-Authenticate\");\n if (!challenge) {\n return response;\n }\n const { claims: base64EncodedClaims, error }: WWWAuthenticate =\n parseWWWAuthenticateHeader(challenge);\n\n if (error !== \"insufficient_claims\" || base64EncodedClaims === undefined) {\n return response;\n }\n\n const claims = atob(base64EncodedClaims);\n\n const accessToken = await getAccessToken(challengeState.scopes, {\n ...getTokenOptions,\n enableCae: true,\n tenantId: challengeState.tenantId,\n claims,\n });\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n return next(request);\n }\n\n async function sendRequest(\n request: PipelineRequest,\n next: SendRequest,\n ): Promise<PipelineResponse> {\n // Add token if possible\n await authorizeRequest(request);\n\n // Try send request (first attempt)\n let response = await next(request);\n\n // Handle standard challenge if present\n response = await handleChallenge(request, response, next);\n\n // Handle CAE challenge if present\n response = await handleCaeChallenge(request, response, next);\n\n return response;\n }\n\n return {\n name: keyVaultAuthenticationPolicyName,\n sendRequest,\n };\n}\n"]}
@@ -25,7 +25,7 @@ export function parseWWWAuthenticateHeader(headerValue) {
25
25
  const [key, ...value] = p.split("=");
26
26
  if (validWWWAuthenticateProperties.includes(key)) {
27
27
  // The values will be wrapped in quotes, which need to be stripped out.
28
- return Object.assign(Object.assign({}, kvPairs), { [key]: value.join("=").slice(1, -1) });
28
+ return { ...kvPairs, [key]: value.join("=").slice(1, -1) };
29
29
  }
30
30
  }
31
31
  return kvPairs;
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"parseWWWAuthenticate.js","sourceRoot":"","sources":["../../src/parseWWWAuthenticate.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AA0ClC,MAAM,8BAA8B,GAAuC;IACzE,eAAe;IACf,mBAAmB;IACnB,UAAU;IACV,OAAO;IACP,UAAU;IACV,QAAQ;IACR,OAAO;CACC,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,WAAmB;IAC5D,MAAM,aAAa,GAAG,MAAM,CAAC;IAC7B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,MAAM,CAAkB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QACrF,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,2FAA2F;YAC3F,MAAM,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,8BAA8B,CAAC,QAAQ,CAAC,GAA4B,CAAC,EAAE,CAAC;gBAC1E,uEAAuE;gBACvE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,sGAAsG;IACtG,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACrE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,aAAa,eAAe,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n/**\n * Parameters parsed out of the WWW-Authenticate header value by the parseWWWAuthenticate function.\n */\nexport interface WWWAuthenticate {\n /**\n * The authorization parameter, if present.\n */\n authorization?: string;\n\n /**\n * The authorization_url parameter, if present.\n */\n authorization_url?: string;\n\n /**\n * The resource parameter, if present.\n */\n resource?: string;\n\n /**\n * The scope parameter, if present.\n */\n scope?: string;\n\n /**\n * The tenantId parameter, if present.\n */\n tenantId?: string;\n\n /**\n * The claims parameter, if present.\n */\n claims?: string;\n\n /**\n * The error parameter, if present.\n */\n error?: string;\n}\n\nconst validWWWAuthenticateProperties: readonly (keyof WWWAuthenticate)[] = [\n \"authorization\",\n \"authorization_url\",\n \"resource\",\n \"scope\",\n \"tenantId\",\n \"claims\",\n \"error\",\n] as const;\n\n/**\n * Parses an WWW-Authenticate response header.\n * This transforms a string value like:\n * `Bearer authorization=\"https://some.url/tenantId\", resource=\"https://some.url\"`\n * into an object like:\n * `{ authorization: \"https://some.url/tenantId\", resource: \"https://some.url\" }`\n * @param headerValue - String value in the WWW-Authenticate header\n */\nexport function parseWWWAuthenticateHeader(headerValue: string): WWWAuthenticate {\n const pairDelimiter = /,? +/;\n const parsed = headerValue.split(pairDelimiter).reduce<WWWAuthenticate>((kvPairs, p) => {\n if (p.match(/\\w=\"/)) {\n // 'sampleKey=\"sample_value\"' -> [sampleKey, \"sample_value\"] -> { sampleKey: sample_value }\n const [key, ...value] = p.split(\"=\");\n if (validWWWAuthenticateProperties.includes(key as keyof WWWAuthenticate)) {\n // The values will be wrapped in quotes, which need to be stripped out.\n return { ...kvPairs, [key]: value.join(\"=\").slice(1, -1) };\n }\n }\n return kvPairs;\n }, {});\n\n // Finally, we pull the tenantId from the authorization header to support multi-tenant authentication.\n if (parsed.authorization) {\n try {\n const tenantId = new URL(parsed.authorization).pathname.substring(1);\n if (tenantId) {\n parsed.tenantId = tenantId;\n }\n } catch (_) {\n throw new Error(`The challenge authorization URI '${parsed.authorization}' is invalid.`);\n }\n }\n\n return parsed;\n}\n"]}
@@ -25,7 +25,7 @@ async function beginRefresh(getAccessToken, retryIntervalInMs, refreshTimeout) {
25
25
  try {
26
26
  return await getAccessToken();
27
27
  }
28
- catch (_a) {
28
+ catch {
29
29
  return null;
30
30
  }
31
31
  }
@@ -63,7 +63,10 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
63
63
  let refreshWorker = null;
64
64
  let token = null;
65
65
  let tenantId;
66
- const options = Object.assign(Object.assign({}, DEFAULT_CYCLER_OPTIONS), tokenCyclerOptions);
66
+ const options = {
67
+ ...DEFAULT_CYCLER_OPTIONS,
68
+ ...tokenCyclerOptions,
69
+ };
67
70
  /**
68
71
  * This little holder defines several predicates that we use to construct
69
72
  * the rules of refreshing the token.
@@ -80,14 +83,13 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
80
83
  * window and not already refreshing)
81
84
  */
82
85
  get shouldRefresh() {
83
- var _a;
84
86
  if (cycler.isRefreshing) {
85
87
  return false;
86
88
  }
87
- if ((token === null || token === void 0 ? void 0 : token.refreshAfterTimestamp) && token.refreshAfterTimestamp < Date.now()) {
89
+ if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {
88
90
  return true;
89
91
  }
90
- return ((_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : 0) - options.refreshWindowInMs < Date.now();
92
+ return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();
91
93
  },
92
94
  /**
93
95
  * Produces true if the cycler MUST refresh (null or nearly-expired
@@ -102,7 +104,6 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
102
104
  * running.
103
105
  */
104
106
  function refresh(scopes, getTokenOptions) {
105
- var _a;
106
107
  if (!cycler.isRefreshing) {
107
108
  // We bind `scopes` here to avoid passing it around a lot
108
109
  const tryGetAccessToken = () => credential.getToken(scopes, getTokenOptions);
@@ -110,7 +111,7 @@ export function createTokenCycler(credential, tokenCyclerOptions) {
110
111
  // before the refresh can be considered done.
111
112
  refreshWorker = beginRefresh(tryGetAccessToken, options.retryIntervalInMs,
112
113
  // If we don't have a token, then we should timeout immediately
113
- (_a = token === null || token === void 0 ? void 0 : token.expiresOnTimestamp) !== null && _a !== void 0 ? _a : Date.now())
114
+ token?.expiresOnTimestamp ?? Date.now())
114
115
  .then((_token) => {
115
116
  refreshWorker = null;
116
117
  token = _token;
@@ -1 +1 @@
1
- {"version":3,"file":"tokenCycler.js","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file is a direct copy of the tokenCycler implementation in core-rest-pipeline.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { delay } from \"@azure/core-util\";\n\n/**\n * A function that gets a promise of an access token and allows providing\n * options.\n *\n * @param options - the options to pass to the underlying token provider\n */\nexport type AccessTokenGetter = (\n scopes: string | string[],\n options: GetTokenOptions,\n) => Promise<AccessToken>;\n\nexport interface TokenCyclerOptions {\n /**\n * The window of time before token expiration during which the token will be\n * considered unusable due to risk of the token expiring before sending the\n * request.\n *\n * This will only become meaningful if the refresh fails for over\n * (refreshWindow - forcedRefreshWindow) milliseconds.\n */\n forcedRefreshWindowInMs: number;\n /**\n * Interval in milliseconds to retry failed token refreshes.\n */\n retryIntervalInMs: number;\n /**\n * The window of time before token expiration during which\n * we will attempt to refresh the token.\n */\n refreshWindowInMs: number;\n}\n\n// Default options for the cycler if none are provided\nexport const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions = {\n forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires\n retryIntervalInMs: 3000, // Allow refresh attempts every 3s\n refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry\n};\n\n/**\n * Converts an an unreliable access token getter (which may resolve with null)\n * into an AccessTokenGetter by retrying the unreliable getter in a regular\n * interval.\n *\n * @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.\n * @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.\n * @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.\n * @returns - A promise that, if it resolves, will resolve with an access token.\n */\nasync function beginRefresh(\n getAccessToken: () => Promise<AccessToken | null>,\n retryIntervalInMs: number,\n refreshTimeout: number,\n): Promise<AccessToken> {\n // This wrapper handles exceptions gracefully as long as we haven't exceeded\n // the timeout.\n async function tryGetAccessToken(): Promise<AccessToken | null> {\n if (Date.now() < refreshTimeout) {\n try {\n return await getAccessToken();\n } catch {\n return null;\n }\n } else {\n const finalToken = await getAccessToken();\n\n // Timeout is up, so throw if it's still null\n if (finalToken === null) {\n throw new Error(\"Failed to refresh access token.\");\n }\n\n return finalToken;\n }\n }\n\n let token: AccessToken | null = await tryGetAccessToken();\n\n while (token === null) {\n await delay(retryIntervalInMs);\n\n token = await tryGetAccessToken();\n }\n\n return token;\n}\n\n/**\n * Creates a token cycler from a credential, scopes, and optional settings.\n *\n * A token cycler represents a way to reliably retrieve a valid access token\n * from a TokenCredential. It will handle initializing the token, refreshing it\n * when it nears expiration, and synchronizes refresh attempts to avoid\n * concurrency hazards.\n *\n * @param credential - the underlying TokenCredential that provides the access\n * token\n * @param tokenCyclerOptions - optionally override default settings for the cycler\n *\n * @returns - a function that reliably produces a valid access token\n */\nexport function createTokenCycler(\n credential: TokenCredential,\n tokenCyclerOptions?: Partial<TokenCyclerOptions>,\n): AccessTokenGetter {\n let refreshWorker: Promise<AccessToken> | null = null;\n let token: AccessToken | null = null;\n let tenantId: string | undefined;\n\n const options = {\n ...DEFAULT_CYCLER_OPTIONS,\n ...tokenCyclerOptions,\n };\n\n /**\n * This little holder defines several predicates that we use to construct\n * the rules of refreshing the token.\n */\n const cycler = {\n /**\n * Produces true if a refresh job is currently in progress.\n */\n get isRefreshing(): boolean {\n return refreshWorker !== null;\n },\n /**\n * Produces true if the cycler SHOULD refresh (we are within the refresh\n * window and not already refreshing)\n */\n get shouldRefresh(): boolean {\n if (cycler.isRefreshing) {\n return false;\n }\n if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {\n return true;\n }\n\n return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();\n },\n /**\n * Produces true if the cycler MUST refresh (null or nearly-expired\n * token).\n */\n get mustRefresh(): boolean {\n return (\n token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now()\n );\n },\n };\n\n /**\n * Starts a refresh job or returns the existing job if one is already\n * running.\n */\n function refresh(\n scopes: string | string[],\n getTokenOptions: GetTokenOptions,\n ): Promise<AccessToken> {\n if (!cycler.isRefreshing) {\n // We bind `scopes` here to avoid passing it around a lot\n const tryGetAccessToken = (): Promise<AccessToken | null> =>\n credential.getToken(scopes, getTokenOptions);\n\n // Take advantage of promise chaining to insert an assignment to `token`\n // before the refresh can be considered done.\n refreshWorker = beginRefresh(\n tryGetAccessToken,\n options.retryIntervalInMs,\n // If we don't have a token, then we should timeout immediately\n token?.expiresOnTimestamp ?? Date.now(),\n )\n .then((_token) => {\n refreshWorker = null;\n token = _token;\n tenantId = getTokenOptions.tenantId;\n return token;\n })\n .catch((reason) => {\n // We also should reset the refresher if we enter a failed state. All\n // existing awaiters will throw, but subsequent requests will start a\n // new retry chain.\n refreshWorker = null;\n token = null;\n tenantId = undefined;\n throw reason;\n });\n }\n\n return refreshWorker as Promise<AccessToken>;\n }\n\n return async (scopes: string | string[], tokenOptions: GetTokenOptions): Promise<AccessToken> => {\n //\n // Simple rules:\n // - If we MUST refresh, then return the refresh task, blocking\n // the pipeline until a token is available.\n // - If we SHOULD refresh, then run refresh but don't return it\n // (we can still use the cached token).\n // - Return the token, since it's fine if we didn't return in\n // step 1.\n //\n\n const hasClaimChallenge = Boolean(tokenOptions.claims);\n const tenantIdChanged = tenantId !== tokenOptions.tenantId;\n\n if (hasClaimChallenge) {\n // If we've received a claim, we know the existing token isn't valid\n // We want to clear it so that that refresh worker won't use the old expiration time as a timeout\n token = null;\n }\n\n // If the tenantId passed in token options is different to the one we have\n // Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to\n // refresh the token with the new tenantId or token.\n const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;\n\n if (mustRefresh) {\n return refresh(scopes, tokenOptions);\n }\n\n if (cycler.shouldRefresh) {\n refresh(scopes, tokenOptions);\n }\n\n return token as AccessToken;\n };\n}\n"]}
1
+ {"version":3,"file":"tokenCycler.js","sourceRoot":"","sources":["../../src/tokenCycler.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// This file is a direct copy of the tokenCycler implementation in core-rest-pipeline.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { delay } from \"@azure/core-util\";\n\n/**\n * A function that gets a promise of an access token and allows providing\n * options.\n *\n * @param options - the options to pass to the underlying token provider\n */\nexport type AccessTokenGetter = (\n scopes: string | string[],\n options: GetTokenOptions,\n) => Promise<AccessToken>;\n\nexport interface TokenCyclerOptions {\n /**\n * The window of time before token expiration during which the token will be\n * considered unusable due to risk of the token expiring before sending the\n * request.\n *\n * This will only become meaningful if the refresh fails for over\n * (refreshWindow - forcedRefreshWindow) milliseconds.\n */\n forcedRefreshWindowInMs: number;\n /**\n * Interval in milliseconds to retry failed token refreshes.\n */\n retryIntervalInMs: number;\n /**\n * The window of time before token expiration during which\n * we will attempt to refresh the token.\n */\n refreshWindowInMs: number;\n}\n\n// Default options for the cycler if none are provided\nexport const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions = {\n forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires\n retryIntervalInMs: 3000, // Allow refresh attempts every 3s\n refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry\n};\n\n/**\n * Converts an an unreliable access token getter (which may resolve with null)\n * into an AccessTokenGetter by retrying the unreliable getter in a regular\n * interval.\n *\n * @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.\n * @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.\n * @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.\n * @returns - A promise that, if it resolves, will resolve with an access token.\n */\nasync function beginRefresh(\n getAccessToken: () => Promise<AccessToken | null>,\n retryIntervalInMs: number,\n refreshTimeout: number,\n): Promise<AccessToken> {\n // This wrapper handles exceptions gracefully as long as we haven't exceeded\n // the timeout.\n async function tryGetAccessToken(): Promise<AccessToken | null> {\n if (Date.now() < refreshTimeout) {\n try {\n return await getAccessToken();\n } catch {\n return null;\n }\n } else {\n const finalToken = await getAccessToken();\n\n // Timeout is up, so throw if it's still null\n if (finalToken === null) {\n throw new Error(\"Failed to refresh access token.\");\n }\n\n return finalToken;\n }\n }\n\n let token: AccessToken | null = await tryGetAccessToken();\n\n while (token === null) {\n await delay(retryIntervalInMs);\n\n token = await tryGetAccessToken();\n }\n\n return token;\n}\n\n/**\n * Creates a token cycler from a credential, scopes, and optional settings.\n *\n * A token cycler represents a way to reliably retrieve a valid access token\n * from a TokenCredential. It will handle initializing the token, refreshing it\n * when it nears expiration, and synchronizes refresh attempts to avoid\n * concurrency hazards.\n *\n * @param credential - the underlying TokenCredential that provides the access\n * token\n * @param tokenCyclerOptions - optionally override default settings for the cycler\n *\n * @returns - a function that reliably produces a valid access token\n */\nexport function createTokenCycler(\n credential: TokenCredential,\n tokenCyclerOptions?: Partial<TokenCyclerOptions>,\n): AccessTokenGetter {\n let refreshWorker: Promise<AccessToken> | null = null;\n let token: AccessToken | null = null;\n let tenantId: string | undefined;\n\n const options = {\n ...DEFAULT_CYCLER_OPTIONS,\n ...tokenCyclerOptions,\n };\n\n /**\n * This little holder defines several predicates that we use to construct\n * the rules of refreshing the token.\n */\n const cycler = {\n /**\n * Produces true if a refresh job is currently in progress.\n */\n get isRefreshing(): boolean {\n return refreshWorker !== null;\n },\n /**\n * Produces true if the cycler SHOULD refresh (we are within the refresh\n * window and not already refreshing)\n */\n get shouldRefresh(): boolean {\n if (cycler.isRefreshing) {\n return false;\n }\n if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {\n return true;\n }\n\n return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();\n },\n /**\n * Produces true if the cycler MUST refresh (null or nearly-expired\n * token).\n */\n get mustRefresh(): boolean {\n return (\n token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now()\n );\n },\n };\n\n /**\n * Starts a refresh job or returns the existing job if one is already\n * running.\n */\n function refresh(\n scopes: string | string[],\n getTokenOptions: GetTokenOptions,\n ): Promise<AccessToken> {\n if (!cycler.isRefreshing) {\n // We bind `scopes` here to avoid passing it around a lot\n const tryGetAccessToken = (): Promise<AccessToken | null> =>\n credential.getToken(scopes, getTokenOptions);\n\n // Take advantage of promise chaining to insert an assignment to `token`\n // before the refresh can be considered done.\n refreshWorker = beginRefresh(\n tryGetAccessToken,\n options.retryIntervalInMs,\n // If we don't have a token, then we should timeout immediately\n token?.expiresOnTimestamp ?? Date.now(),\n )\n .then((_token) => {\n refreshWorker = null;\n token = _token;\n tenantId = getTokenOptions.tenantId;\n return token;\n })\n .catch((reason) => {\n // We also should reset the refresher if we enter a failed state. All\n // existing awaiters will throw, but subsequent requests will start a\n // new retry chain.\n refreshWorker = null;\n token = null;\n tenantId = undefined;\n throw reason;\n });\n }\n\n return refreshWorker as Promise<AccessToken>;\n }\n\n return async (scopes: string | string[], tokenOptions: GetTokenOptions): Promise<AccessToken> => {\n //\n // Simple rules:\n // - If we MUST refresh, then return the refresh task, blocking\n // the pipeline until a token is available.\n // - If we SHOULD refresh, then run refresh but don't return it\n // (we can still use the cached token).\n // - Return the token, since it's fine if we didn't return in\n // step 1.\n //\n\n const hasClaimChallenge = Boolean(tokenOptions.claims);\n const tenantIdChanged = tenantId !== tokenOptions.tenantId;\n\n if (hasClaimChallenge) {\n // If we've received a claim, we know the existing token isn't valid\n // We want to clear it so that that refresh worker won't use the old expiration time as a timeout\n token = null;\n }\n\n // If the tenantId passed in token options is different to the one we have\n // Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to\n // refresh the token with the new tenantId or token.\n const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;\n\n if (mustRefresh) {\n return refresh(scopes, tokenOptions);\n }\n\n if (cycler.shouldRefresh) {\n refresh(scopes, tokenOptions);\n }\n\n return token as AccessToken;\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure/keyvault-common",
3
- "version": "2.0.1-alpha.20250619.1",
3
+ "version": "2.0.1-alpha.20250730.1",
4
4
  "description": "Common internal functionality for all of the Azure Key Vault clients in the Azure SDK for JavaScript",
5
5
  "sdk-type": "client",
6
6
  "author": "Microsoft Corporation",
@@ -82,7 +82,7 @@
82
82
  "browser"
83
83
  ],
84
84
  "selfLink": false,
85
- "project": "./tsconfig.src.json"
85
+ "project": "../../../tsconfig.src.build.json"
86
86
  },
87
87
  "exports": {
88
88
  "./package.json": "./package.json",