import { type DpopVerificationFailureCode as DpopVerificationFailureCodeType, verifyDpopProof, } from "@t3tools/shared/dpop"; import type { DpopFailureReason } from "@t3tools/contracts"; import * as Crypto from "effect/Crypto"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Encoding from "effect/Encoding"; import * as Option from "effect/Option"; import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; import { ServerAuthDpopReplayKeyCalculationError, ServerAuthDpopReplayStateRecordError, ServerAuthInvalidCredentialError, type ServerAuthInternalError, } from "./EnvironmentAuth.ts"; import * as ServerSecretStore from "./ServerSecretStore.ts"; export const mapDpopFailureReason = (code: DpopVerificationFailureCodeType): DpopFailureReason => { switch (code) { case "time_window": return "time_window"; case "key_mismatch": return "key_mismatch"; case "method_mismatch": case "url_mismatch": return "request_mismatch"; case "access_token_hash_mismatch": return "token_mismatch"; case "missing_proof": case "malformed_proof": case "invalid_signature": case "invalid_proof": return "invalid_proof"; } }; export const mapDpopReplayStoreError = ( error: ServerSecretStore.SecretStoreError, ): ServerAuthInvalidCredentialError | ServerAuthInternalError => ServerSecretStore.isSecretAlreadyExistsError(error) ? new ServerAuthInvalidCredentialError({ diagnostic: "DPoP proof replayed.", dpopFailureReason: "replay", cause: error, }) : new ServerAuthDpopReplayStateRecordError({ cause: error, }); export const verifyRequestDpopProof = (input: { readonly request: HttpServerRequest.HttpServerRequest; readonly expectedThumbprint?: string; readonly expectedAccessToken?: string; }) => Effect.gen(function* () { const proof = input.request.headers.dpop; const url = HttpServerRequest.toURL(input.request); if (Option.isNone(url)) { return yield* new ServerAuthInvalidCredentialError({ diagnostic: "Invalid DPoP request URL.", }); } const now = yield* DateTime.now; const result = verifyDpopProof({ proof, method: input.request.method, url: url.value.href, nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), ...(input.expectedThumbprint ? { expectedThumbprint: input.expectedThumbprint } : {}), ...(input.expectedAccessToken ? { expectedAccessToken: input.expectedAccessToken } : {}), }); if (!result.ok) { yield* Effect.annotateCurrentSpan({ "environment.dpop.failure_code": result.code, }); return yield* new ServerAuthInvalidCredentialError({ diagnostic: result.reason, dpopFailureReason: mapDpopFailureReason(result.code), }); } const secretStore = yield* ServerSecretStore.ServerSecretStore; const replayKey = yield* Crypto.Crypto.pipe( Effect.flatMap((crypto) => crypto.digest("SHA-256", new TextEncoder().encode(`${result.thumbprint}:${result.jti}`)), ), Effect.map(Encoding.encodeBase64Url), Effect.mapError( (cause) => new ServerAuthDpopReplayKeyCalculationError({ cause, }), ), ); yield* secretStore .create( `dpop-proof-${replayKey}`, new TextEncoder().encode( [ `thumbprint=${result.thumbprint}`, `jti=${result.jti}`, `iat=${result.iat}`, `consumedAt=${DateTime.formatIso(now)}`, ].join("\n"), ), ) .pipe( Effect.catchIf(ServerSecretStore.isSecretStoreError, (error) => Effect.gen(function* () { const mapped = mapDpopReplayStoreError(error); if (mapped._tag === "ServerAuthInvalidCredentialError") { yield* Effect.annotateCurrentSpan({ "environment.dpop.failure_code": mapped.dpopFailureReason, }); } return yield* Effect.fail(mapped); }), ), ); return result.thumbprint; });