73 lines · 2.9 KB · TypeScript
RawDownload
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 = <A, E, R>(  authorization: PreparedHttpAuthorization | null,  request: Effect.Effect<A, E, R>,): Effect.Effect<A, E, R> =>  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<ManagedRelayDpopSigner["Service"]>,): Effect.Effect<EnvironmentHttpAuthHeaders, RemoteEnvironmentAuthFetchError> =>  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 };  });