| ... | ... | @@ -3,7 +3,8 @@ import * as Crypto from "effect/Crypto"; |
| 3 | 3 | import * as DateTime from "effect/DateTime"; |
| 4 | 4 | import * as Effect from "effect/Effect"; |
| 5 | 5 | import * as Encoding from "effect/Encoding"; |
| 6 | | import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 6 | import * as Option from "effect/Option"; |
| 7 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 7 | 8 | |
| 8 | 9 | import { |
| 9 | 10 | ServerAuthDpopReplayKeyCalculationError, |
| ... | ... | @@ -13,22 +14,6 @@ import { |
| 13 | 14 | } from "./EnvironmentAuth.ts"; |
| 14 | 15 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 15 | 16 | |
| 16 | | function firstHeaderValue(value: string | undefined): string | undefined { |
| 17 | | const first = value?.split(",")[0]?.trim(); |
| 18 | | return first && first.length > 0 ? first : undefined; |
| 19 | | } |
| 20 | | |
| 21 | | export function requestAbsoluteUrl(request: HttpServerRequest.HttpServerRequest): string { |
| 22 | | try { |
| 23 | | return new URL(request.originalUrl).href; |
| 24 | | } catch { |
| 25 | | const host = firstHeaderValue(request.headers.host) ?? "127.0.0.1"; |
| 26 | | const forwardedProto = firstHeaderValue(request.headers["x-forwarded-proto"]); |
| 27 | | const proto = forwardedProto === "https" || forwardedProto === "http" ? forwardedProto : "http"; |
| 28 | | return new URL(request.originalUrl, `${proto}://${host}`).href; |
| 29 | | } |
| 30 | | } |
| 31 | | |
| 32 | 17 | export const mapDpopReplayStoreError = ( |
| 33 | 18 | error: ServerSecretStore.SecretStoreError, |
| 34 | 19 | ): ServerAuthInvalidCredentialError | ServerAuthInternalError => |
| ... | ... | @@ -48,11 +33,17 @@ export const verifyRequestDpopProof = (input: { |
| 48 | 33 | }) => |
| 49 | 34 | Effect.gen(function* () { |
| 50 | 35 | const proof = input.request.headers.dpop; |
| 36 | const url = HttpServerRequest.toURL(input.request); |
| 37 | if (Option.isNone(url)) { |
| 38 | return yield* new ServerAuthInvalidCredentialError({ |
| 39 | diagnostic: "Invalid DPoP request URL.", |
| 40 | }); |
| 41 | } |
| 51 | 42 | const now = yield* DateTime.now; |
| 52 | 43 | const result = verifyDpopProof({ |
| 53 | 44 | proof, |
| 54 | 45 | method: input.request.method, |
| 55 | | url: requestAbsoluteUrl(input.request), |
| 46 | url: url.value.href, |
| 56 | 47 | nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), |
| 57 | 48 | ...(input.expectedThumbprint ? { expectedThumbprint: input.expectedThumbprint } : {}), |
| 58 | 49 | ...(input.expectedAccessToken ? { expectedAccessToken: input.expectedAccessToken } : {}), |