284 lines · 9.9 KB · TypeScript
RawDownload
import type { AuthClientPresentationMetadata } from "@t3tools/contracts";import { withRelayClientTracing } from "@t3tools/shared/relayTracing";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import * as HttpClient from "effect/unstable/http/HttpClient"; import { appendClientConnectionParams } from "../authorization/remote.ts";import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";import * as ClientCapabilities from "../platform/capabilities.ts";import {  BearerConnectionCredential,  BearerConnectionProfile,  type ConnectionCatalogEntry,  SshConnectionProfile,} from "./catalog.ts";import * as ConnectionCredentialStore from "./credentialStore.ts";import {  credentialMissingError,  environmentMismatchError,  mapRemoteEnvironmentError,  profileMissingError,} from "./errors.ts";import type {  BearerConnectionTarget,  ConnectionTarget,  PreparedConnection,  PrimaryConnectionTarget,  RelayConnectionTarget,  SshConnectionTarget,} from "./model.ts";import { ConnectionBlockedError, type ConnectionAttemptError } from "./model.ts";import * as ConnectionProfileStore from "./profileStore.ts";import {  appendOrchestrationProtocol,  orchestrationProtocolCompatibilityError,} from "./compatibility.ts";import { fetchRemoteEnvironmentDescriptor } from "../environment/descriptor.ts"; export class ConnectionResolver extends Context.Service<  ConnectionResolver,  {    readonly prepare: (      entry: ConnectionCatalogEntry,    ) => Effect.Effect<PreparedConnection, ConnectionAttemptError>;  }>()("@t3tools/client-runtime/connection/resolver/ConnectionResolver") {} const isBearerProfile = Schema.is(BearerConnectionProfile);const isSshProfile = Schema.is(SshConnectionProfile);const isBearerCredential = Schema.is(BearerConnectionCredential); function primarySocketUrl(  target: PrimaryConnectionTarget,  clientMetadata: AuthClientPresentationMetadata | undefined,): string {  const url = new URL(target.wsBaseUrl);  if (url.pathname === "" || url.pathname === "/") {    url.pathname = "/ws";  }  appendClientConnectionParams(url, clientMetadata, "direct");  return url.toString();} const makePrimaryBroker = Effect.fn("clientRuntime.connection.broker.makePrimary")(function* () {  const auth = yield* ClientCapabilities.PrimaryEnvironmentAuth;  const presentation = yield* ClientCapabilities.ClientPresentation;  const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization;   return Effect.fn("clientRuntime.connection.broker.primary")(function* (    target: PrimaryConnectionTarget,  ) {    const bearerToken = yield* auth.bearerToken;    if (Option.isNone(bearerToken)) {      return {        environmentId: target.environmentId,        label: target.label,        httpBaseUrl: target.httpBaseUrl,        socketUrl: primarySocketUrl(target, presentation.metadata),        httpAuthorization: null,        target,      } satisfies PreparedConnection;    }     const authorized = yield* remote.authorizeBearer({      expectedEnvironmentId: target.environmentId,      httpBaseUrl: target.httpBaseUrl,      wsBaseUrl: target.wsBaseUrl,      bearerToken: bearerToken.value,      connectionMethod: "direct",    });    return {      ...authorized,      target,    } satisfies PreparedConnection;  });}); const makeBearerBroker = Effect.fn("clientRuntime.connection.broker.makeBearer")(function* () {  const credentials = yield* ConnectionCredentialStore.ConnectionCredentialStore;  const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization;   return Effect.fn("clientRuntime.connection.broker.bearer")(function* (    entry: ConnectionCatalogEntry & { readonly target: BearerConnectionTarget },  ) {    const target = entry.target;    const profile = yield* Option.match(entry.profile, {      onNone: () => Effect.fail(profileMissingError(target.connectionId)),      onSome: Effect.succeed,    });    if (!isBearerProfile(profile)) {      return yield* new ConnectionBlockedError({        reason: "configuration",        detail: `Connection profile ${target.connectionId} is not a bearer connection.`,      });    }    if (profile.environmentId !== target.environmentId) {      return yield* environmentMismatchError({        expected: target.environmentId,        actual: profile.environmentId,      });    }    const credential = yield* credentials.get(target.connectionId).pipe(      Effect.flatMap(        Option.match({          onNone: () => Effect.fail(credentialMissingError(target.connectionId)),          onSome: Effect.succeed,        }),      ),    );    if (!isBearerCredential(credential)) {      return yield* credentialMissingError(target.connectionId);    }    const authorized = yield* remote.authorizeBearer({      expectedEnvironmentId: target.environmentId,      httpBaseUrl: profile.httpBaseUrl,      wsBaseUrl: profile.wsBaseUrl,      bearerToken: credential.token,      connectionMethod: "direct",    });    return {      environmentId: authorized.environmentId,      label: authorized.label,      httpBaseUrl: authorized.httpBaseUrl,      socketUrl: authorized.socketUrl,      httpAuthorization: authorized.httpAuthorization,      target,    } satisfies PreparedConnection;  });}); const makeRelayBroker = Effect.fn("clientRuntime.connection.broker.makeRelay")(function* () {  const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization;   return Effect.fnUntraced(    function* (target: RelayConnectionTarget) {      const authorized = yield* remote.authorizeDpop({        expectedEnvironmentId: target.environmentId,      });      return {        environmentId: authorized.environmentId,        label: authorized.label,        httpBaseUrl: authorized.httpBaseUrl,        socketUrl: authorized.socketUrl,        httpAuthorization: authorized.httpAuthorization,        target,      } satisfies PreparedConnection;    },    Effect.withSpan("clientRuntime.connection.broker.relay"),    withRelayClientTracing,  );}); const makeSshBroker = Effect.fn("clientRuntime.connection.broker.makeSsh")(function* () {  const profiles = yield* ConnectionProfileStore.ConnectionProfileStore;  const ssh = yield* ClientCapabilities.SshEnvironmentGateway;  const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization;   return Effect.fn("clientRuntime.connection.broker.ssh")(function* (    entry: ConnectionCatalogEntry & { readonly target: SshConnectionTarget },  ) {    const target = entry.target;    const profile = yield* Option.match(entry.profile, {      onNone: () => Effect.fail(profileMissingError(target.connectionId)),      onSome: Effect.succeed,    });    if (!isSshProfile(profile)) {      return yield* new ConnectionBlockedError({        reason: "configuration",        detail: `Connection profile ${target.connectionId} is not an SSH connection.`,      });    }    if (profile.environmentId !== target.environmentId) {      return yield* environmentMismatchError({        expected: target.environmentId,        actual: profile.environmentId,      });    }    const prepared = yield* ssh.prepare({      connectionId: target.connectionId,      expectedEnvironmentId: target.environmentId,      target: profile.target,    });    yield* profiles.put(      new SshConnectionProfile({        connectionId: profile.connectionId,        environmentId: profile.environmentId,        label: profile.label,        target: prepared.bootstrap.target,      }),    );    const authorized = yield* remote.authorizeBearer({      expectedEnvironmentId: target.environmentId,      httpBaseUrl: prepared.bootstrap.httpBaseUrl,      wsBaseUrl: prepared.bootstrap.wsBaseUrl,      bearerToken: prepared.bearerToken,      connectionMethod: "ssh",    });    return {      environmentId: authorized.environmentId,      label: authorized.label,      httpBaseUrl: authorized.httpBaseUrl,      socketUrl: authorized.socketUrl,      httpAuthorization: authorized.httpAuthorization,      target,    } satisfies PreparedConnection;  });}); export const make = Effect.gen(function* () {  const primary = yield* makePrimaryBroker();  const bearer = yield* makeBearerBroker();  const relay = yield* makeRelayBroker();  const ssh = yield* makeSshBroker();  const httpClient = yield* HttpClient.HttpClient;   const prepare = Effect.fn("clientRuntime.connection.broker.prepare")(function* (    entry: ConnectionCatalogEntry,  ) {    const target: ConnectionTarget = entry.target;    yield* Effect.annotateCurrentSpan({      "connection.environment.id": target.environmentId,      "connection.target.kind": target._tag,    });    const prepared = yield* (() => {      switch (target._tag) {        case "PrimaryConnectionTarget":          return primary(target);        case "BearerConnectionTarget":          return bearer({ ...entry, target });        case "RelayConnectionTarget":          return relay(target);        case "SshConnectionTarget":          return ssh({ ...entry, target });      }    })();    const descriptor = yield* fetchRemoteEnvironmentDescriptor({      httpBaseUrl: prepared.httpBaseUrl,    }).pipe(      Effect.mapError(mapRemoteEnvironmentError),      Effect.provideService(HttpClient.HttpClient, httpClient),    );    if (descriptor.environmentId !== target.environmentId) {      return yield* environmentMismatchError({        expected: target.environmentId,        actual: descriptor.environmentId,      });    }    const compatibilityError = orchestrationProtocolCompatibilityError(descriptor);    if (compatibilityError !== null) {      return yield* compatibilityError;    }    return {      ...prepared,      socketUrl: appendOrchestrationProtocol(prepared.socketUrl),    };  });   return ConnectionResolver.of({ prepare });}); export const layer = Layer.effect(ConnectionResolver, make);