wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
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"]}