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 }; });
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 };
});