1158 lines · 42.8 KB · TypeScript
RawDownload
import * as NodeCrypto from "node:crypto";import {  AuthRelayReadScope,  AuthRelayWriteScope,  AuthStandardClientScopes,  EnvironmentCloudEndpointUnavailableError,  EnvironmentCloudLinkStateResult,  EnvironmentCloudRelayConfigResult,  EnvironmentHttpApi,  EnvironmentHttpBadRequestError,  EnvironmentHttpConflictError,  type EnvironmentHttpInternalOperation,  EnvironmentHttpInternalServerError,  EnvironmentHttpRelayOperation,  EnvironmentHttpRelayPhase,  EnvironmentHttpUnauthorizedError,} from "@t3tools/contracts";import {  RelayCloudEnvironmentHealthProofPayload,  RelayCloudEnvironmentHealthRequest,  RelayCloudMintCredentialProofPayload,  RelayCloudMintCredentialRequest,  RelayEnvironmentHealthResponseProofPayload,  type RelayEnvironmentHealthResponse as RelayEnvironmentHealthResponseShape,  RelayEnvironmentConfigRequest,  RelayEnvironmentLinkChallengeResponse,  RelayEnvironmentLinkResponse,  RelayEnvironmentMintResponseProofPayload,  type RelayEnvironmentMintResponse as RelayEnvironmentMintResponseShape,  RelayEnvironmentLinkProof,  RelayEnvironmentLinkProofPayload,  RelayLinkProofRequest,  RelayManagedEndpointOrigin,} from "@t3tools/contracts/relay";import { withRelayClientTracing } from "@t3tools/shared/relayTracing";import {  normalizeRelayIssuer,  RELAY_HEALTH_REQUEST_TYP,  RELAY_HEALTH_RESPONSE_TYP,  RELAY_LINK_PROOF_TYP,  RELAY_MINT_REQUEST_TYP,  RELAY_MINT_RESPONSE_TYP,  signRelayJwt,  verifyRelayJwt,} from "@t3tools/shared/relayJwt";import { isSecureRelayUrl } from "@t3tools/shared/relayUrl";import { getUrlDiagnostics } from "@t3tools/shared/urlDiagnostics";import * as DateTime from "effect/DateTime";import * as Crypto from "effect/Crypto";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import * as HttpEffect from "effect/unstable/http/HttpEffect";import * as HttpBody from "effect/unstable/http/HttpBody";import * as HttpClient from "effect/unstable/http/HttpClient";import * as HttpClientError from "effect/unstable/http/HttpClientError";import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";import * as ServerSecretStore from "../auth/ServerSecretStore.ts";import { requireEnvironmentScope } from "../auth/http.ts";import * as ServerEnvironment from "../environment/ServerEnvironment.ts";import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts";import {  CLOUD_ENDPOINT_RUNTIME_CONFIG,  CLOUD_LINKED_USER_ID,  CLOUD_MINT_PUBLIC_KEY,  encodeEndpointRuntimeConfigJson,  PUBLISH_AGENT_ACTIVITY_SECRET,  RELAY_ENVIRONMENT_CREDENTIAL_SECRET,  RELAY_ISSUER_SECRET,  RELAY_URL_SECRET,} from "./config.ts";import { relayUrlConfig } from "./publicConfig.ts";import { setCliDesiredCloudLink } from "./CliState.ts";import * as CliTokenManager from "./CliTokenManager.ts";import { getOrCreateEnvironmentKeyPairFromSecretStore } from "./environmentKeys.ts";import { traceRelayRequest } from "./traceRelayRequest.ts"; const CLOUD_MINT_NONCE_PREFIX = "cloud-mint-nonce-";const CLOUD_MINT_JTI_PREFIX = "cloud-mint-jti-";const CLOUD_HEALTH_NONCE_PREFIX = "cloud-health-nonce-";const CLOUD_HEALTH_JTI_PREFIX = "cloud-health-jti-";const CLOUD_PROOF_MAX_LIFETIME_SECONDS = 5 * 60;const CLOUD_PROOF_CLOCK_SKEW_SECONDS = 60;const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]);const CLOUD_CREDENTIAL_RESPONSE_HEADERS = {  "cache-control": "no-store",  pragma: "no-cache",} as const; export class CloudRelayConfigurationError extends Schema.TaggedErrorClass<CloudRelayConfigurationError>()(  "CloudRelayConfigurationError",  {    configKey: Schema.Literal("T3CODE_RELAY_URL"),    cause: Schema.Defect(),  },) {  override get message(): string {    return `${this.configKey} must be configured as a secure absolute HTTPS origin.`;  }} export class CloudRelayRequestError extends Schema.TaggedErrorClass<CloudRelayRequestError>()(  "CloudRelayRequestError",  {    operation: EnvironmentHttpRelayOperation,    phase: EnvironmentHttpRelayPhase,    method: Schema.Literal("POST"),    requestUrlInputLength: Schema.Number,    requestUrlProtocol: Schema.optionalKey(Schema.String),    requestUrlHostname: Schema.optionalKey(Schema.String),    responseStatus: Schema.optional(Schema.Number),    cause: Schema.Defect(),  },) {  static fromClientFailure(input: {    readonly operation: CloudRelayRequestError["operation"];    readonly url: string;    readonly cause: HttpBody.HttpBodyError | HttpClientError.HttpClientError | Schema.SchemaError;    readonly responseStatus?: number;  }): CloudRelayRequestError {    const diagnostics = getUrlDiagnostics(input.url);    const context = {      operation: input.operation,      method: "POST" as const,      requestUrlInputLength: diagnostics.inputLength,      ...(diagnostics.protocol === undefined ? {} : { requestUrlProtocol: diagnostics.protocol }),      ...(diagnostics.hostname === undefined ? {} : { requestUrlHostname: diagnostics.hostname }),    };    if (input.cause._tag === "SchemaError") {      return new CloudRelayRequestError({        ...context,        phase: "decode-response",        ...(input.responseStatus === undefined ? {} : { responseStatus: input.responseStatus }),        cause: input.cause,      });    }     if (!HttpClientError.isHttpClientError(input.cause)) {      return new CloudRelayRequestError({        ...context,        phase: "encode-request",        cause: input.cause,      });    }     const phase: CloudRelayRequestError["phase"] = (() => {      switch (input.cause.reason._tag) {        case "EncodeError":          return "encode-request";        case "TransportError":        case "InvalidUrlError":          return "send-request";        case "StatusCodeError":          return "check-response-status";        case "DecodeError":        case "EmptyBodyError":          return "decode-response";      }    })();     return new CloudRelayRequestError({      ...context,      phase,      ...(input.cause.response === undefined        ? input.responseStatus === undefined          ? {}          : { responseStatus: input.responseStatus }        : { responseStatus: input.cause.response.status }),      cause: input.cause,    });  }   override get message(): string {    const responseStatus =      this.responseStatus === undefined ? "" : ` with response status ${this.responseStatus}`;    return `T3 Connect relay ${this.operation} failed during ${this.phase}${responseStatus}.`;  }} const appendCloudCredentialResponseHeaders = HttpEffect.appendPreResponseHandler(  (_request, response) =>    Effect.succeed(HttpServerResponse.setHeaders(response, CLOUD_CREDENTIAL_RESPONSE_HEADERS)),); function errorDiagnosticTag(cause: unknown): string {  if (    typeof cause === "object" &&    cause !== null &&    "_tag" in cause &&    typeof cause._tag === "string"  ) {    return cause._tag;  }  if (cause instanceof Error) return cause.name;  return typeof cause;} type EnvironmentCloudInternalErrorContext =  | Exclude<EnvironmentHttpInternalOperation, "relay_request">  | {      readonly operation: "relay_request";      readonly relayOperation: CloudRelayRequestError["operation"];      readonly relayPhase: CloudRelayRequestError["phase"];      readonly responseStatus?: number;    }; const failEnvironmentCloudInternalError =  (context: EnvironmentCloudInternalErrorContext) =>  (cause: unknown): Effect.Effect<never, EnvironmentHttpInternalServerError> => {    const error =      typeof context === "string"        ? new EnvironmentHttpInternalServerError({ operation: context, cause })        : new EnvironmentHttpInternalServerError({ ...context, cause });    const logContext =      typeof context === "string"        ? { operation: context, causeTag: errorDiagnosticTag(cause) }        : { ...context, causeTag: errorDiagnosticTag(cause) };    return Effect.logError(error.message, logContext).pipe(      Effect.flatMap(() => Effect.fail(error)),    );  }; const requireRelayUrl = relayUrlConfig.pipe(  Effect.mapError(    (cause) =>      new CloudRelayConfigurationError({        configKey: "T3CODE_RELAY_URL",        cause,      }),  ),); function bytesToString(bytes: Uint8Array): string {  return new TextDecoder().decode(bytes);} function stringToBytes(value: string): Uint8Array {  return new TextEncoder().encode(value);} export function consumeCloudReplayGuards(input: {  readonly secrets: ServerSecretStore.ServerSecretStore["Service"];  readonly names: ReadonlyArray<string>;  readonly value: Uint8Array;}) {  return Effect.all(    input.names.map((name) =>      input.secrets.create(name, input.value).pipe(        Effect.as(true),        Effect.catchTags({          SecretStorePersistError: (error) =>            ServerSecretStore.isSecretAlreadyExistsError(error)              ? Effect.succeed(false)              : Effect.fail(error),        }),      ),    ),    { concurrency: input.names.length },  ).pipe(Effect.map((created) => created.every(Boolean)));} function normalizePemForSignedPayload(value: string): string {  return value.trim();} function normalizeHostname(hostname: string): string {  return hostname    .trim()    .toLowerCase()    .replace(/^\[(.*)\]$/, "$1");} function validateCloudMintPublicKey(  publicKey: string,): Effect.Effect<void, EnvironmentHttpBadRequestError> {  return Effect.try({    try: () => NodeCrypto.createPublicKey(publicKey.replace(/\\n/g, "\n")),    catch: (cause) =>      new EnvironmentHttpBadRequestError({        reason: "invalid_cloud_mint_public_key",        cause,      }),  }).pipe(    Effect.flatMap((key) =>      key.asymmetricKeyType === "ed25519"        ? Effect.void        : Effect.fail(            new EnvironmentHttpBadRequestError({              reason: "invalid_cloud_mint_public_key",            }),          ),    ),  );} function validateRelayConfigPayload(  payload: RelayEnvironmentConfigRequest,): Effect.Effect<void, EnvironmentHttpBadRequestError> {  if (!isSecureRelayUrl(payload.relayUrl)) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        reason: "invalid_relay_url",      }),    );  }  if (payload.relayIssuer !== undefined && !isSecureRelayUrl(payload.relayIssuer)) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        reason: "invalid_relay_issuer",      }),    );  }  if (payload.environmentCredential.trim().length === 0) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        reason: "missing_relay_environment_credential",      }),    );  }  if (payload.cloudUserId.trim().length === 0) {    return Effect.fail(      new EnvironmentHttpBadRequestError({        reason: "missing_cloud_user_id",      }),    );  }  return Effect.void;} function validateLinkedCloudUser(input: {  readonly secrets: ServerSecretStore.ServerSecretStore["Service"];  readonly cloudUserId: string;}) {  return input.secrets.get(CLOUD_LINKED_USER_ID).pipe(    Effect.mapError(      (cause) =>        new EnvironmentAuth.ServerAuthLinkedCloudAccountVerificationError({          cause,        }),    ),    Effect.flatMap((existing) => {      if (Option.isNone(existing)) {        return Effect.void;      }      const existingCloudUserId = bytesToString(existing.value);      return existingCloudUserId === input.cloudUserId        ? Effect.void        : Effect.fail(            new EnvironmentHttpConflictError({              reason: "linked_to_different_cloud_account",            }),          );    }),  );} function readInstalledCloudUserId(secrets: ServerSecretStore.ServerSecretStore["Service"]) {  return secrets.get(CLOUD_LINKED_USER_ID).pipe(    Effect.mapError(      (cause) =>        new EnvironmentAuth.ServerAuthLinkedCloudAccountReadError({          cause,        }),    ),    Effect.flatMap((bytes) =>      Option.isSome(bytes)        ? Effect.succeed(bytesToString(bytes.value))        : Effect.fail(new EnvironmentAuth.ServerAuthLinkedCloudAccountMissingError({})),    ),  );} function isLoopbackHostname(hostname: string): boolean {  return LOOPBACK_HOSTNAMES.has(normalizeHostname(hostname));} function firstForwardedHeaderValue(value: string | undefined): string | undefined {  const first = value?.split(",")[0]?.trim();  return first && first.length > 0 ? first : undefined;} function requestAbsoluteUrl(request: HttpServerRequest.HttpServerRequest): string | null {  try {    return new URL(request.originalUrl).href;  } catch {    const host = firstForwardedHeaderValue(request.headers.host) ?? "127.0.0.1";    try {      return new URL(request.originalUrl, `http://${host}`).href;    } catch {      return null;    }  }} function hasForwardedAuthorityHeaders(request: HttpServerRequest.HttpServerRequest): boolean {  return (    firstForwardedHeaderValue(request.headers["x-forwarded-host"]) !== undefined ||    firstForwardedHeaderValue(request.headers["x-forwarded-proto"]) !== undefined  );} function endpointRequestPort(url: URL): number {  return Number(url.port || (url.protocol === "https:" ? 443 : 80));} function isAllowedEndpointOrigin(input: {  readonly origin: RelayManagedEndpointOrigin;  readonly requestUrl: string;}): boolean {  if (!isLoopbackHostname(input.origin.localHttpHost)) {    return false;  }   const url = new URL(input.requestUrl);  if (!isLoopbackHostname(url.hostname)) {    return false;  }   return input.origin.localHttpPort === endpointRequestPort(url);} function providerKindMatchesRequestedLinkScopes(request: RelayLinkProofRequest): boolean {  return request.endpoint.providerKind === "cloudflare_tunnel";} function hasExactScope(input: {  readonly scopes: ReadonlyArray<string>;  readonly expected: string;}): boolean {  return input.scopes.length === 1 && input.scopes[0] === input.expected;} function hasBoundedCloudProofLifetime(input: {  readonly iat: number;  readonly exp: number;  readonly nowSeconds: number;}): boolean {  return (    input.exp > input.iat &&    input.exp - input.iat <= CLOUD_PROOF_MAX_LIFETIME_SECONDS &&    input.iat <= input.nowSeconds + CLOUD_PROOF_CLOCK_SKEW_SECONDS  );} const decodeCloudHealthProof = Schema.decodeUnknownEffect(RelayCloudEnvironmentHealthProofPayload);const decodeCloudMintProof = Schema.decodeUnknownEffect(RelayCloudMintCredentialProofPayload); interface CloudHttpDependencies {  readonly secrets: ServerSecretStore.ServerSecretStore["Service"];  readonly environment: ServerEnvironment.ServerEnvironment["Service"];  readonly endpointRuntime: ManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"];  readonly environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"];  readonly cliTokenManager: CliTokenManager.CloudCliTokenManager["Service"];  readonly httpClient: HttpClient.HttpClient;} const cloudHttpDependencies = Effect.gen(function* () {  return {    secrets: yield* ServerSecretStore.ServerSecretStore,    environment: yield* ServerEnvironment.ServerEnvironment,    endpointRuntime: yield* ManagedEndpointRuntime.CloudManagedEndpointRuntime,    environmentAuth: yield* EnvironmentAuth.EnvironmentAuth,    cliTokenManager: yield* CliTokenManager.CloudCliTokenManager,    httpClient: yield* HttpClient.HttpClient,  } satisfies CloudHttpDependencies;}); const makeCloudLinkProof = Effect.fn("environment.cloud.makeLinkProof")(function* (  dependencies: CloudHttpDependencies,  request: RelayLinkProofRequest,  requestUrl: string,) {  const keyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(dependencies.secrets);  if (    !providerKindMatchesRequestedLinkScopes(request) ||    !isAllowedEndpointOrigin({      origin: request.origin,      requestUrl,    })  ) {    return yield* new EnvironmentHttpBadRequestError({      reason: "invalid_managed_endpoint_origin",    });  }  const now = yield* DateTime.now;  const expiresAt = DateTime.add(now, { minutes: 5 });  const nowSeconds = Math.floor(now.epochMilliseconds / 1_000);  const descriptor = yield* dependencies.environment.getDescriptor;  const payload = {    iss: `t3-env:${descriptor.environmentId}`,    aud: normalizeRelayIssuer(request.relayIssuer),    sub: descriptor.environmentId,    jti: yield* Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4)),

Lines 1–500 of 1158.