{"version":3,"file":"challengeBasedAuthenticationPolicy.js","sourceRoot":"","sources":["../../../../keyvault-common/src/challengeBasedAuthenticationPolicy.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport {\n AuthorizeRequestOnChallengeOptions,\n AuthorizeRequestOptions,\n ChallengeCallbacks,\n PipelineRequest,\n RequestBodyType,\n} from \"@azure/core-rest-pipeline\";\nimport { WWWAuthenticate, parseWWWAuthenticateHeader } from \"./parseWWWAuthenticate\";\n\nimport { GetTokenOptions } from \"@azure/core-auth\";\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 };\n\n/**\n * Additional options for the challenge based authentication policy.\n */\nexport interface CreateChallengeCallbacksOptions {\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 * Creates challenge callback handlers to manage CAE lifecycle in Azure Key Vault.\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 createKeyVaultChallengeCallbacks(\n options: CreateChallengeCallbacksOptions = {},\n): ChallengeCallbacks {\n const { disableChallengeResourceVerification } = options;\n let challengeState: ChallengeState = { status: \"none\" };\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({\n request,\n getAccessToken,\n }: AuthorizeRequestOptions): Promise {\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, requestOptions);\n if (token) {\n request.headers.set(\"authorization\", `Bearer ${token.token}`);\n }\n break;\n }\n }\n return Promise.resolve();\n }\n\n async function authorizeRequestOnChallenge({\n request,\n response,\n getAccessToken,\n }: AuthorizeRequestOnChallengeOptions): Promise {\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 throw new Error(\"Missing challenge.\");\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 throw new Error(\"Missing scope.\");\n }\n\n if (!disableChallengeResourceVerification) {\n verifyChallengeResource(scope, request);\n }\n\n const accessToken = await getAccessToken([scope], {\n ...getTokenOptions,\n tenantId: parsedChallenge.tenantId,\n });\n\n if (!accessToken) {\n return false;\n }\n\n request.headers.set(\"Authorization\", `Bearer ${accessToken.token}`);\n\n challengeState = {\n status: \"complete\",\n scopes: [scope],\n };\n\n return true;\n }\n\n return {\n authorizeRequest,\n authorizeRequestOnChallenge,\n };\n}\n"]}