import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import { FetchHttpClient, type HttpMethod } from "effect/unstable/http"; import type { PreparedHttpAuthorization } from "../connection/model.ts"; import type { ManagedRelayDpopSigner } from "../relay/managedRelay.ts"; import { RemoteEnvironmentAuthFetchError } from "../rpc/http.ts"; export interface EnvironmentHttpAuthHeaders { readonly authorization?: string; readonly dpop?: string; } /** * Primary/local environments with no bearer or DPoP credential authenticate the * browser via a session cookie. A cross-origin `fetch` does not send cookies by * default, so those requests must opt into credentialed mode; bearer/DPoP * connections carry their credential in a header and need no cookies. Applied * per-request via `FetchHttpClient.RequestInit`, which the fetch client reads * from the fiber context at request time. */ export const withEnvironmentCredentials = ( authorization: PreparedHttpAuthorization | null, request: Effect.Effect, ): Effect.Effect => authorization === null ? request.pipe(Effect.provideService(FetchHttpClient.RequestInit, { credentials: "include" })) : request; /** * Build the authorization headers for an authenticated environment HTTP * request, matching the credential the connection was prepared with: * - primary/local connections carry no credential, * - bearer connections send a static `Bearer` token, * - relay connections send a `DPoP` access token with a freshly signed proof * bound to this request's method and URL. * * The DPoP signer is passed in (not resolved from context) and is only required * for relay/DPoP connections, so bearer/primary connections work even when no * signer is available. */ export const buildEnvironmentAuthHeaders = ( authorization: PreparedHttpAuthorization | null, method: HttpMethod.HttpMethod, url: string, signer: Option.Option, ): Effect.Effect => Effect.gen(function* () { if (authorization === null) { return {}; } if (authorization._tag === "Bearer") { return { authorization: `Bearer ${authorization.token}` }; } if (Option.isNone(signer)) { return yield* new RemoteEnvironmentAuthFetchError({ message: "No DPoP signer is available to authorize the environment request.", cause: authorization._tag, }); } const proof = yield* signer.value .createProof({ method, url, accessToken: authorization.accessToken }) .pipe( Effect.mapError( (cause) => new RemoteEnvironmentAuthFetchError({ message: "Could not create the environment request authorization proof.", cause, }), ), ); return { authorization: `DPoP ${authorization.accessToken}`, dpop: proof }; });