{"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"]}
|