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