import { createClerkClient, verifyToken } from "@clerk/backend";import { sql as drizzleSql } from "drizzle-orm";import * as Crypto from "effect/Crypto";import * as Context from "effect/Context";import * as DateTime from "effect/DateTime";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Record from "effect/Record";import * as Redacted from "effect/Redacted";import * as Schema from "effect/Schema";import * as Tracer from "effect/Tracer";import * as HttpEffect from "effect/unstable/http/HttpEffect";import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware";import * as HttpRouter from "effect/unstable/http/HttpRouter";import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import * as HttpTraceContext from "effect/unstable/http/HttpTraceContext";import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";import * as HttpApiError from "effect/unstable/httpapi/HttpApiError";import { encodeOAuthScope } from "@t3tools/shared/oauthScope";import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability"; import { RelayApi, RelayAgentActivityPublishProofExpiredError, RelayAgentActivityPublishProofInvalidError, RelayClientAuth, RelayClientPrincipal, RelayAccessTokenType, RelayDpopClientAuth, RelayEnvironmentConnectScope, RelayEnvironmentStatusScope, RelayMobileRegistrationScope, RelayAuthInvalidError, type RelayAuthInvalidReason, RelayEnvironmentAuth, RelayEnvironmentConnectNotAuthorizedError, RelayEnvironmentEndpointTimedOutError, RelayEnvironmentEndpointUnavailableError, RelayEnvironmentLinkFailedError, RelayEnvironmentLinkProofExpiredError, RelayEnvironmentLinkProofInvalidError, RelayEnvironmentLinkUnavailableError, RelayEnvironmentLinkLimitExceededError, RelayEnvironmentPrincipal, type RelayEnvironmentConnectRequest, type RelayDpopAccessTokenScope, RelayInternalError,} from "@t3tools/contracts/relay";import { normalizeRelayIssuer } from "@t3tools/shared/relayJwt"; import * as DeliveryAttempts from "../agentActivity/DeliveryAttempts.ts";import * as AgentActivityRows from "../agentActivity/AgentActivityRows.ts";import * as Devices from "../agentActivity/Devices.ts";import * as DpopProofs from "../auth/DpopProofs.ts";import * as RelayTokens from "../auth/RelayTokens.ts";import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts";import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts";import * as LiveActivities from "../agentActivity/LiveActivities.ts";import * as RelayConfiguration from "../Config.ts";import * as AgentActivityPublisher from "../agentActivity/AgentActivityPublisher.ts";import * as EnvironmentConnector from "../environments/EnvironmentConnector.ts";import * as EnvironmentLinker from "../environments/EnvironmentLinker.ts";import * as ManagedEndpointProvider from "../environments/ManagedEndpointProvider.ts";import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllocations.ts";import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts";import * as MobileRegistrations from "../agentActivity/MobileRegistrations.ts";import { withSpanAttributes } from "../observability.ts";import * as RelayDb from "../db.ts"; const relayCorsAllowedMethods = ["GET", "POST", "DELETE", "OPTIONS"] as const;const relayCorsAllowedHeaders = [ "authorization", "b3", "traceparent", "content-type", "dpop",] as const;const relayCorsExposedHeaders = ["traceparent", "www-authenticate"] as const; const relayCorsHeaders = { "access-control-allow-origin": "*", "access-control-expose-headers": relayCorsExposedHeaders.join(","),} as const; const relayCorsPreflightHeaders = { ...relayCorsHeaders, "access-control-allow-methods": relayCorsAllowedMethods.join(","), "access-control-allow-headers": relayCorsAllowedHeaders.join(","), "access-control-max-age": "86400",} as const; const appendRelayCredentialResponseHeaders = HttpEffect.appendPreResponseHandler( (_request, response) => Effect.succeed( HttpServerResponse.setHeaders(response, { "cache-control": "no-store", pragma: "no-cache", }), ),); const appendRelayDpopChallengeHeader = HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed( response.status === 401 ? HttpServerResponse.setHeader(response, "www-authenticate", "DPoP") : response, ),); const appendRelayTraceContextResponseHeader = Effect.gen(function* () { const span = yield* Effect.currentParentSpan; if (span._tag !== "Span") { return; } const traceparent = HttpTraceContext.toHeaders(span).traceparent; if (traceparent === undefined) { return; } yield* HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "traceparent", traceparent)), );}).pipe(Effect.ignore); export const relayCors = HttpRouter.middleware( Effect.fnUntraced(function* <E, R>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >, ) { const request = yield* HttpServerRequest.HttpServerRequest; if (request.method === "OPTIONS") { return HttpServerResponse.empty({ status: 204, headers: relayCorsPreflightHeaders, }); } const response = yield* httpEffect; return HttpServerResponse.setHeaders(response, relayCorsHeaders); }), { global: true },); export const relayNotFoundRoute = HttpRouter.add( "*", "/*", HttpServerResponse.empty({ status: 404 }),); export const relayDocsRedirectRoute = HttpRouter.add( "GET", "/", HttpServerResponse.redirect("/docs"),); // Shorter than the mobile client's 10s request timeout on purpose: when a// request hangs (e.g. a stuck upstream query), the client would otherwise// abort first, the invocation would die with the request span still open, and// the batched spans would never export — leaving no server-side trace at all.// Failing server-side first turns the hang into a completed 504 whose trace// contains the exact child span that stalled, and the response still carries// the traceparent back to the client.export const RELAY_REQUEST_DEADLINE_MS = 9_000; const relayRequestDeadline = <E, R>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >,) => httpEffect.pipe( Effect.timeoutOption(Duration.millis(RELAY_REQUEST_DEADLINE_MS)), Effect.flatMap( Option.match({ onNone: () => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; yield* Effect.logError("relay request exceeded deadline", { "http.method": request.method, "http.url": request.url, "relay.request.deadline_ms": RELAY_REQUEST_DEADLINE_MS, }); yield* Effect.annotateCurrentSpan({ "relay.request.deadline_exceeded": true, }); return HttpServerResponse.jsonUnsafe( { error: "relay_request_deadline_exceeded" }, { status: 504 }, ); }), onSome: Effect.succeed, }), ), ); export const traceRelayHttpRequest = <E, R>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >,) => // HttpMiddleware finalizes its span on the dispatcher; do not close a request-scoped exporter first. HttpMiddleware.tracer( appendRelayTraceContextResponseHeader.pipe(Effect.andThen(relayRequestDeadline(httpEffect))), ).pipe(Effect.ensuring(Effect.yieldNow)); export const traceRelayHttpRequestWith = <E, R, LayerError, LayerRequirements>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >, tracerLayer: Layer.Layer<never, LayerError, LayerRequirements>,) => traceRelayHttpRequest(httpEffect).pipe( Effect.provide(Layer.merge(tracerLayer, httpHeaderRedactionLayer)), ); export const withoutCapturedParentSpan = <A, E, R>( effect: Effect.Effect<A, E, R>,): Effect.Effect<A, E, R> => Effect.withFiber((fiber) => { const context = fiber.context; // HttpApiBuilder captures its build context for route handlers; an event parent would outlive export. fiber.setContext(Context.omit(Tracer.ParentSpan)(context)); return effect.pipe(Effect.ensuring(Effect.sync(() => fiber.setContext(context)))); }); export const relayClientAuthLayer = Layer.effect( RelayClientAuth, Effect.gen(function* () { const config = yield* RelayConfiguration.RelayConfiguration; return { clientBearer: Effect.fn("relay.auth.client.bearer")(function* (httpEffect, { credential }) { const token = readHttpAuthorizationCredential(credential); const verified = yield* verifyRelayClientBearerToken(config, token).pipe( Effect.tapError((error) => Effect.annotateCurrentSpan( "relay.auth.clerk_verification_failure", clerkVerificationFailureReason(error.cause), ), ), Effect.catch(() => relayAuthInvalidError("invalid_bearer")), ); if (!verified.sub) { yield* Effect.annotateCurrentSpan({ "relay.auth.clerk_verification_failure": "missing_subject", }); return yield* relayAuthInvalidError("invalid_bearer"); } yield* Effect.annotateCurrentSpan({ "relay.auth.mode": verified.mode, "relay.auth.subject": verified.sub, }); return yield* httpEffect.pipe( withSpanAttributes({ "user.id": verified.sub }), Effect.provideService(RelayClientPrincipal, { userId: verified.sub, token, }), ); }), }; }),); export const relayEnvironmentAuthLayer = Layer.effect( RelayEnvironmentAuth, Effect.gen(function* () { const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; return { environmentBearer: Effect.fn("relay.auth.environment.bearer")(function* ( httpEffect, { credential }, ) { const token = readHttpAuthorizationCredential(credential); const principal = yield* credentials.authenticate(token).pipe( Effect.catchTags({ EnvironmentCredentialAuthenticatePersistenceError: () => relayInternalErrorResponse("persistence_failed"), }), ); if (principal._tag === "None") { return yield* relayAuthInvalidError("not_authorized"); } yield* Effect.annotateCurrentSpan({ "relay.auth.mode": "environment_credential", }); return yield* httpEffect.pipe( withSpanAttributes({ "relay.environment_id": principal.value.environmentId, }), Effect.provideService(RelayEnvironmentPrincipal, principal.value), ); }), }; }),); export const relayDpopClientAuthLayer = Layer.effect( RelayDpopClientAuth, Effect.gen(function* () { const relayTokens = yield* RelayTokens.RelayTokens; return { relayDpop: Effect.fn("relay.auth.dpop_client")(function* (httpEffect, { credential }) { yield* appendRelayDpopChallengeHeader; const request = yield* HttpServerRequest.HttpServerRequest; if (!isDpopAuthorizationHeader(request.headers.authorization)) { return yield* relayAuthInvalidError("invalid_bearer"); } const token = readHttpAuthorizationCredential(credential); const now = yield* DateTime.now; const verified = yield* relayTokens.verifyDpopAccessToken({ token, nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), }); if (!verified) { return yield* relayAuthInvalidError("invalid_bearer"); } yield* Effect.annotateCurrentSpan({ "relay.auth.mode": "dpop", "relay.auth.subject": verified.sub, }); return yield* httpEffect.pipe( withSpanAttributes({ "user.id": verified.sub }), Effect.provideService(RelayClientPrincipal, { userId: verified.sub, token, proofKeyThumbprint: verified.cnf.jkt, dpopScopes: verified.scope, }), ); }), }; }),); function isDpopAuthorizationHeader(value: string | undefined): boolean { return /^DPoP +/iu.test(value ?? "");} function readHttpAuthorizationCredential(credential: Redacted.Redacted<string>): string { // Effect beta.73 leaves the scheme separator in decoded HTTP credentials. return Redacted.value(credential).trimStart();} export const metadataApi = HttpApiBuilder.group( RelayApi, "metadata", Effect.fnUntraced(function* (handlers) { const settings = yield* RelayConfiguration.RelayConfiguration; const issuer = normalizeRelayIssuer(settings.relayIssuer); const scopes = [ RelayEnvironmentConnectScope, RelayEnvironmentStatusScope, RelayMobileRegistrationScope, ] as const; return handlers .handle("authorizationServer", () => Effect.succeed({ issuer, token_endpoint: `${issuer}/v1/client/dpop-token`, grant_types_supported: ["urn:ietf:params:oauth:grant-type:token-exchange"], token_endpoint_auth_methods_supported: ["none"], dpop_signing_alg_values_supported: ["ES256"], scopes_supported: scopes, }), ) .handle("protectedResource", () => Effect.succeed({ resource: issuer, authorization_servers: [issuer], scopes_supported: scopes, dpop_bound_access_tokens_required: true, dpop_signing_alg_values_supported: ["ES256"], }), ); }),); export const healthApi = HttpApiBuilder.group( RelayApi, "health", Effect.fnUntraced(function* (handlers) { const db = yield* RelayDb.RelayDb; return handlers.handle( "health", Effect.fn("relay.api.health")( function* () { yield* db.execute(drizzleSql`SELECT 1`); return { ok: true, service: "relay" as const }; }, Effect.catch(() => relayInternalErrorResponse("database_unavailable")), ), ); }),); export const revokeEnvironmentLinkRecord = Effect.fn( "relay.api.client.revokeEnvironmentLinkRecord",)(function* (input: { readonly userId: string; readonly environmentId: string; readonly environmentPublicKey: string;}) { const transactions = yield* RelayDb.RelayTransactions; const links = yield* EnvironmentLinks.EnvironmentLinks; const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; return yield* transactions.withTransaction( Effect.gen(function* () { const revoked = yield* links.revokeForUser({ userId: input.userId, environmentId: input.environmentId, }); if (revoked) { yield* credentials.revokeForEnvironmentPublicKey({ environmentId: input.environmentId, environmentPublicKey: input.environmentPublicKey, }); } return revoked; }), );}); export const unlinkEnvironmentRecord = Effect.fn("relay.api.client.unlinkEnvironmentRecord")( function* (input: { readonly userId: string; readonly environmentId: string }) { const links = yield* EnvironmentLinks.EnvironmentLinks; const managedEndpointProvider = yield* ManagedEndpointProvider.ManagedEndpointProvider; const deprovisionTarget = yield* managedEndpointProvider.prepareDeprovision({ userId: input.userId, environmentId: input.environmentId, }); const link = yield* links.getForUser({ userId: input.userId, environmentId: input.environmentId, }); const unlinked = link === null ? false : yield* revokeEnvironmentLinkRecord({ userId: input.userId, environmentId: link.environmentId, environmentPublicKey: link.environmentPublicKey, }); // External teardown cannot share the SQL transaction. Run it only after // revocation commits so a database failure leaves a fully usable active // link. Still run teardown when the link is already revoked, allowing a // retry to finish cleanup after an earlier Cloudflare failure. yield* managedEndpointProvider.deprovision({ userId: input.userId, environmentId: input.environmentId, target: deprovisionTarget, }); return unlinked; },); export const mobileApi = HttpApiBuilder.group( RelayApi, "mobile", Effect.fnUntraced(function* (handlers) { const registrations = yield* MobileRegistrations.MobileRegistrations; const dpopProofs = yield* DpopProofs.DpopProofReplay; return handlers .handle( "registerDevice", Effect.fn("relay.api.mobile.registerDevice")(function* (args) { const { payload } = args; const { userId, token } = yield* RelayClientPrincipal; const proofKeyThumbprint = yield* requireDpopPrincipalScope("mobile:registration"); yield* requireDpopThumbprint(proofKeyThumbprint, { expectedAccessToken: token, }).pipe(Effect.provideService(DpopProofs.DpopProofReplay, dpopProofs)); return yield* registrations.registerDevice({ userId, payload }); }, mapRelayCommonApiErrors("invalid_dpop")), ) .handle( "registerLiveActivity", Effect.fn("relay.api.mobile.registerLiveActivity")(function* (args) { const { payload } = args; const { userId, token } = yield* RelayClientPrincipal; const proofKeyThumbprint = yield* requireDpopPrincipalScope("mobile:registration"); yield* requireDpopThumbprint(proofKeyThumbprint, { expectedAccessToken: token, }).pipe(Effect.provideService(DpopProofs.DpopProofReplay, dpopProofs)); return yield* registrations.registerLiveActivity({ userId, payload }); }, mapRelayCommonApiErrors("invalid_dpop")), ) .handle( "getAgentActivitySnapshot", Effect.fn("relay.api.mobile.getAgentActivitySnapshot")(function* () {Lines 1–500 of 1290. Load more
import { createClerkClient, verifyToken } from "@clerk/backend"; import { sql as drizzleSql } from "drizzle-orm"; import * as Crypto from "effect/Crypto"; import * as Context from "effect/Context"; import * as DateTime from "effect/DateTime"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Record from "effect/Record"; import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; import * as Tracer from "effect/Tracer"; import * as HttpEffect from "effect/unstable/http/HttpEffect"; import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware"; import * as HttpRouter from "effect/unstable/http/HttpRouter"; import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; import * as HttpTraceContext from "effect/unstable/http/HttpTraceContext"; import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; import * as HttpApiError from "effect/unstable/httpapi/HttpApiError"; import { encodeOAuthScope } from "@t3tools/shared/oauthScope"; import { httpHeaderRedactionLayer } from "@t3tools/shared/httpObservability"; import { RelayApi, RelayAgentActivityPublishProofExpiredError, RelayAgentActivityPublishProofInvalidError, RelayClientAuth, RelayClientPrincipal, RelayAccessTokenType, RelayDpopClientAuth, RelayEnvironmentConnectScope, RelayEnvironmentStatusScope, RelayMobileRegistrationScope, RelayAuthInvalidError, type RelayAuthInvalidReason, RelayEnvironmentAuth, RelayEnvironmentConnectNotAuthorizedError, RelayEnvironmentEndpointTimedOutError, RelayEnvironmentEndpointUnavailableError, RelayEnvironmentLinkFailedError, RelayEnvironmentLinkProofExpiredError, RelayEnvironmentLinkProofInvalidError, RelayEnvironmentLinkUnavailableError, RelayEnvironmentLinkLimitExceededError, RelayEnvironmentPrincipal, type RelayEnvironmentConnectRequest, type RelayDpopAccessTokenScope, RelayInternalError, } from "@t3tools/contracts/relay"; import { normalizeRelayIssuer } from "@t3tools/shared/relayJwt"; import * as DeliveryAttempts from "../agentActivity/DeliveryAttempts.ts"; import * as AgentActivityRows from "../agentActivity/AgentActivityRows.ts"; import * as Devices from "../agentActivity/Devices.ts"; import * as DpopProofs from "../auth/DpopProofs.ts"; import * as RelayTokens from "../auth/RelayTokens.ts"; import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts"; import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts"; import * as LiveActivities from "../agentActivity/LiveActivities.ts"; import * as RelayConfiguration from "../Config.ts"; import * as AgentActivityPublisher from "../agentActivity/AgentActivityPublisher.ts"; import * as EnvironmentConnector from "../environments/EnvironmentConnector.ts"; import * as EnvironmentLinker from "../environments/EnvironmentLinker.ts"; import * as ManagedEndpointProvider from "../environments/ManagedEndpointProvider.ts"; import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllocations.ts"; import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts"; import * as MobileRegistrations from "../agentActivity/MobileRegistrations.ts"; import { withSpanAttributes } from "../observability.ts"; import * as RelayDb from "../db.ts"; const relayCorsAllowedMethods = ["GET", "POST", "DELETE", "OPTIONS"] as const; const relayCorsAllowedHeaders = [ "authorization", "b3", "traceparent", "content-type", "dpop", ] as const; const relayCorsExposedHeaders = ["traceparent", "www-authenticate"] as const; const relayCorsHeaders = { "access-control-allow-origin": "*", "access-control-expose-headers": relayCorsExposedHeaders.join(","), } as const; const relayCorsPreflightHeaders = { ...relayCorsHeaders, "access-control-allow-methods": relayCorsAllowedMethods.join(","), "access-control-allow-headers": relayCorsAllowedHeaders.join(","), "access-control-max-age": "86400", } as const; const appendRelayCredentialResponseHeaders = HttpEffect.appendPreResponseHandler( (_request, response) => Effect.succeed( HttpServerResponse.setHeaders(response, { "cache-control": "no-store", pragma: "no-cache", }), ), ); const appendRelayDpopChallengeHeader = HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed( response.status === 401 ? HttpServerResponse.setHeader(response, "www-authenticate", "DPoP") : response, ), ); const appendRelayTraceContextResponseHeader = Effect.gen(function* () { const span = yield* Effect.currentParentSpan; if (span._tag !== "Span") { return; } const traceparent = HttpTraceContext.toHeaders(span).traceparent; if (traceparent === undefined) { return; } yield* HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "traceparent", traceparent)), ); }).pipe(Effect.ignore); export const relayCors = HttpRouter.middleware( Effect.fnUntraced(function* <E, R>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >, ) { const request = yield* HttpServerRequest.HttpServerRequest; if (request.method === "OPTIONS") { return HttpServerResponse.empty({ status: 204, headers: relayCorsPreflightHeaders, }); } const response = yield* httpEffect; return HttpServerResponse.setHeaders(response, relayCorsHeaders); }), { global: true }, ); export const relayNotFoundRoute = HttpRouter.add( "*", "/*", HttpServerResponse.empty({ status: 404 }), ); export const relayDocsRedirectRoute = HttpRouter.add( "GET", "/", HttpServerResponse.redirect("/docs"), ); // Shorter than the mobile client's 10s request timeout on purpose: when a // request hangs (e.g. a stuck upstream query), the client would otherwise // abort first, the invocation would die with the request span still open, and // the batched spans would never export — leaving no server-side trace at all. // Failing server-side first turns the hang into a completed 504 whose trace // contains the exact child span that stalled, and the response still carries // the traceparent back to the client. export const RELAY_REQUEST_DEADLINE_MS = 9_000; const relayRequestDeadline = <E, R>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >, ) => httpEffect.pipe( Effect.timeoutOption(Duration.millis(RELAY_REQUEST_DEADLINE_MS)), Effect.flatMap( Option.match({ onNone: () => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; yield* Effect.logError("relay request exceeded deadline", { "http.method": request.method, "http.url": request.url, "relay.request.deadline_ms": RELAY_REQUEST_DEADLINE_MS, }); yield* Effect.annotateCurrentSpan({ "relay.request.deadline_exceeded": true, }); return HttpServerResponse.jsonUnsafe( { error: "relay_request_deadline_exceeded" }, { status: 504 }, ); }), onSome: Effect.succeed, }), ), ); export const traceRelayHttpRequest = <E, R>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >, ) => // HttpMiddleware finalizes its span on the dispatcher; do not close a request-scoped exporter first. HttpMiddleware.tracer( appendRelayTraceContextResponseHeader.pipe(Effect.andThen(relayRequestDeadline(httpEffect))), ).pipe(Effect.ensuring(Effect.yieldNow)); export const traceRelayHttpRequestWith = <E, R, LayerError, LayerRequirements>( httpEffect: Effect.Effect< HttpServerResponse.HttpServerResponse, E, HttpServerRequest.HttpServerRequest | R >, tracerLayer: Layer.Layer<never, LayerError, LayerRequirements>, ) => traceRelayHttpRequest(httpEffect).pipe( Effect.provide(Layer.merge(tracerLayer, httpHeaderRedactionLayer)), ); export const withoutCapturedParentSpan = <A, E, R>( effect: Effect.Effect<A, E, R>, ): Effect.Effect<A, E, R> => Effect.withFiber((fiber) => { const context = fiber.context; // HttpApiBuilder captures its build context for route handlers; an event parent would outlive export. fiber.setContext(Context.omit(Tracer.ParentSpan)(context)); return effect.pipe(Effect.ensuring(Effect.sync(() => fiber.setContext(context)))); }); export const relayClientAuthLayer = Layer.effect( RelayClientAuth, Effect.gen(function* () { const config = yield* RelayConfiguration.RelayConfiguration; return { clientBearer: Effect.fn("relay.auth.client.bearer")(function* (httpEffect, { credential }) { const token = readHttpAuthorizationCredential(credential); const verified = yield* verifyRelayClientBearerToken(config, token).pipe( Effect.tapError((error) => Effect.annotateCurrentSpan( "relay.auth.clerk_verification_failure", clerkVerificationFailureReason(error.cause), ), ), Effect.catch(() => relayAuthInvalidError("invalid_bearer")), ); if (!verified.sub) { yield* Effect.annotateCurrentSpan({ "relay.auth.clerk_verification_failure": "missing_subject", }); return yield* relayAuthInvalidError("invalid_bearer"); } yield* Effect.annotateCurrentSpan({ "relay.auth.mode": verified.mode, "relay.auth.subject": verified.sub, }); return yield* httpEffect.pipe( withSpanAttributes({ "user.id": verified.sub }), Effect.provideService(RelayClientPrincipal, { userId: verified.sub, token, }), ); }), }; }), ); export const relayEnvironmentAuthLayer = Layer.effect( RelayEnvironmentAuth, Effect.gen(function* () { const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; return { environmentBearer: Effect.fn("relay.auth.environment.bearer")(function* ( httpEffect, { credential }, ) { const token = readHttpAuthorizationCredential(credential); const principal = yield* credentials.authenticate(token).pipe( Effect.catchTags({ EnvironmentCredentialAuthenticatePersistenceError: () => relayInternalErrorResponse("persistence_failed"), }), ); if (principal._tag === "None") { return yield* relayAuthInvalidError("not_authorized"); } yield* Effect.annotateCurrentSpan({ "relay.auth.mode": "environment_credential", }); return yield* httpEffect.pipe( withSpanAttributes({ "relay.environment_id": principal.value.environmentId, }), Effect.provideService(RelayEnvironmentPrincipal, principal.value), ); }), }; }), ); export const relayDpopClientAuthLayer = Layer.effect( RelayDpopClientAuth, Effect.gen(function* () { const relayTokens = yield* RelayTokens.RelayTokens; return { relayDpop: Effect.fn("relay.auth.dpop_client")(function* (httpEffect, { credential }) { yield* appendRelayDpopChallengeHeader; const request = yield* HttpServerRequest.HttpServerRequest; if (!isDpopAuthorizationHeader(request.headers.authorization)) { return yield* relayAuthInvalidError("invalid_bearer"); } const token = readHttpAuthorizationCredential(credential); const now = yield* DateTime.now; const verified = yield* relayTokens.verifyDpopAccessToken({ token, nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), }); if (!verified) { return yield* relayAuthInvalidError("invalid_bearer"); } yield* Effect.annotateCurrentSpan({ "relay.auth.mode": "dpop", "relay.auth.subject": verified.sub, }); return yield* httpEffect.pipe( withSpanAttributes({ "user.id": verified.sub }), Effect.provideService(RelayClientPrincipal, { userId: verified.sub, token, proofKeyThumbprint: verified.cnf.jkt, dpopScopes: verified.scope, }), ); }), }; }), ); function isDpopAuthorizationHeader(value: string | undefined): boolean { return /^DPoP +/iu.test(value ?? ""); } function readHttpAuthorizationCredential(credential: Redacted.Redacted<string>): string { // Effect beta.73 leaves the scheme separator in decoded HTTP credentials. return Redacted.value(credential).trimStart(); } export const metadataApi = HttpApiBuilder.group( RelayApi, "metadata", Effect.fnUntraced(function* (handlers) { const settings = yield* RelayConfiguration.RelayConfiguration; const issuer = normalizeRelayIssuer(settings.relayIssuer); const scopes = [ RelayEnvironmentConnectScope, RelayEnvironmentStatusScope, RelayMobileRegistrationScope, ] as const; return handlers .handle("authorizationServer", () => Effect.succeed({ issuer, token_endpoint: `${issuer}/v1/client/dpop-token`, grant_types_supported: ["urn:ietf:params:oauth:grant-type:token-exchange"], token_endpoint_auth_methods_supported: ["none"], dpop_signing_alg_values_supported: ["ES256"], scopes_supported: scopes, }), ) .handle("protectedResource", () => Effect.succeed({ resource: issuer, authorization_servers: [issuer], scopes_supported: scopes, dpop_bound_access_tokens_required: true, dpop_signing_alg_values_supported: ["ES256"], }), ); }), ); export const healthApi = HttpApiBuilder.group( RelayApi, "health", Effect.fnUntraced(function* (handlers) { const db = yield* RelayDb.RelayDb; return handlers.handle( "health", Effect.fn("relay.api.health")( function* () { yield* db.execute(drizzleSql`SELECT 1`); return { ok: true, service: "relay" as const }; }, Effect.catch(() => relayInternalErrorResponse("database_unavailable")), ), ); }), ); export const revokeEnvironmentLinkRecord = Effect.fn( "relay.api.client.revokeEnvironmentLinkRecord", )(function* (input: { readonly userId: string; readonly environmentId: string; readonly environmentPublicKey: string; }) { const transactions = yield* RelayDb.RelayTransactions; const links = yield* EnvironmentLinks.EnvironmentLinks; const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; return yield* transactions.withTransaction( Effect.gen(function* () { const revoked = yield* links.revokeForUser({ userId: input.userId, environmentId: input.environmentId, }); if (revoked) { yield* credentials.revokeForEnvironmentPublicKey({ environmentId: input.environmentId, environmentPublicKey: input.environmentPublicKey, }); } return revoked; }), ); }); export const unlinkEnvironmentRecord = Effect.fn("relay.api.client.unlinkEnvironmentRecord")( function* (input: { readonly userId: string; readonly environmentId: string }) { const links = yield* EnvironmentLinks.EnvironmentLinks; const managedEndpointProvider = yield* ManagedEndpointProvider.ManagedEndpointProvider; const deprovisionTarget = yield* managedEndpointProvider.prepareDeprovision({ userId: input.userId, environmentId: input.environmentId, }); const link = yield* links.getForUser({ userId: input.userId, environmentId: input.environmentId, }); const unlinked = link === null ? false : yield* revokeEnvironmentLinkRecord({ userId: input.userId, environmentId: link.environmentId, environmentPublicKey: link.environmentPublicKey, }); // External teardown cannot share the SQL transaction. Run it only after // revocation commits so a database failure leaves a fully usable active // link. Still run teardown when the link is already revoked, allowing a // retry to finish cleanup after an earlier Cloudflare failure. yield* managedEndpointProvider.deprovision({ userId: input.userId, environmentId: input.environmentId, target: deprovisionTarget, }); return unlinked; }, ); export const mobileApi = HttpApiBuilder.group( RelayApi, "mobile", Effect.fnUntraced(function* (handlers) { const registrations = yield* MobileRegistrations.MobileRegistrations; const dpopProofs = yield* DpopProofs.DpopProofReplay; return handlers .handle( "registerDevice", Effect.fn("relay.api.mobile.registerDevice")(function* (args) { const { payload } = args; const { userId, token } = yield* RelayClientPrincipal; const proofKeyThumbprint = yield* requireDpopPrincipalScope("mobile:registration"); yield* requireDpopThumbprint(proofKeyThumbprint, { expectedAccessToken: token, }).pipe(Effect.provideService(DpopProofs.DpopProofReplay, dpopProofs)); return yield* registrations.registerDevice({ userId, payload }); }, mapRelayCommonApiErrors("invalid_dpop")), ) .handle( "registerLiveActivity", Effect.fn("relay.api.mobile.registerLiveActivity")(function* (args) { const { payload } = args; const { userId, token } = yield* RelayClientPrincipal; const proofKeyThumbprint = yield* requireDpopPrincipalScope("mobile:registration"); yield* requireDpopThumbprint(proofKeyThumbprint, { expectedAccessToken: token, }).pipe(Effect.provideService(DpopProofs.DpopProofReplay, dpopProofs)); return yield* registrations.registerLiveActivity({ userId, payload }); }, mapRelayCommonApiErrors("invalid_dpop")), ) .handle( "getAgentActivitySnapshot", Effect.fn("relay.api.mobile.getAgentActivitySnapshot")(function* () {