1/**
2 * One-use pairing codes minted on an environment with the user's own session there: a code
3 * for linking, so a link card can be answered without fetching a code by hand, and a code
4 * that approves one app's sign-in at a set level. Each carries exactly what an agent
5 * sign-in with the chosen access needs.
6 */
7import {
8 type AuthCreatePairingCredentialInput,
9 type AuthMcpClientAccess,
10 type AuthPairingCredentialResult,
11 mcpClientScopes,
12} from "@t3tools/contracts";
13import * as Schema from "effect/Schema";
14import * as Effect from "effect/Effect";
15import type * as HttpClient from "effect/http/HttpClient";
16import * as Option from "effect/Option";
17import type { Atom } from "effect/reactivity";
18import * as SubscriptionRef from "effect/SubscriptionRef";
19
20import * as RemoteEnvironmentAuthorization from "../authorization/service.ts";
21import type { PreparedConnection } from "../connection/model.ts";
22import type { EnvironmentRegistry } from "../connection/registry.ts";
23import * as EnvironmentSupervisor from "../connection/supervisor.ts";
24import * as ManagedRelay from "../relay/managedRelay.ts";
25import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts";
26import { createEnvironmentCommand } from "./runtime.ts";
27
28const MINT_TIMEOUT_MS = 8_000;
29const PEER_PAIRING_CODE_LABEL = "T3 Code link";
30const MCP_APPROVAL_CODE_LABEL = "App sign-in";
31
32/** Minting failed; the user can still paste a code from the other environment. */
33export class PeerPairingCodeError extends Schema.TaggedError<PeerPairingCodeError>()(
34 "PeerPairingCodeError",
35 {
36 reason: Schema.Literals(["not_allowed", "unavailable"]),
37 cause: Schema.Defect(),
38 },
39) {
40 override get message(): string {
41 return this.reason === "not_allowed"
42 ? "Your session there cannot create pairing codes. Paste a code from there instead."
43 : "Could not get a pairing code from there. Paste a code from there instead.";
44 }
45}
46
47const mintPairingCredential = Effect.fn("clientRuntime.state.mintPairingCredential")(function* (
48 prepared: PreparedConnection,
49 payload: AuthCreatePairingCredentialInput,
50): Effect.fn.Return<AuthPairingCredentialResult, PeerPairingCodeError, HttpClient.HttpClient> {
51 const signer = yield* Effect.serviceOption(ManagedRelay.ManagedRelayDpopSigner);
52 const remoteAuthorization = yield* Effect.serviceOption(
53 RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization,
54 );
55 const minted = yield* executeAuthenticatedEnvironmentHttpRequest({
56 prepared,
57 signer,
58 remoteAuthorization,
59 group: "auth",
60 method: "POST",
61 url: (urls) => urls.pairingCredential(),
62 timeoutMs: MINT_TIMEOUT_MS,
63 request: ({ client, headers }) => client.pairingCredential({ headers, payload }),
64 }).pipe(
65 Effect.mapError(
66 (cause) =>
67 new PeerPairingCodeError({
68 reason:
69 cause._tag === "EnvironmentScopeRequiredError" ||
70 cause._tag === "EnvironmentOperationForbiddenError"
71 ? "not_allowed"
72 : "unavailable",
73 cause,
74 }),
75 ),
76 );
77 return minted;
78});
79
80// Untraced: mintPairingCredential owns the span.
81const mintOnEnvironment = Effect.fnUntraced(function* (payload: AuthCreatePairingCredentialInput) {
82 const supervisor = yield* EnvironmentSupervisor.EnvironmentSupervisor;
83 const prepared = yield* SubscriptionRef.get(supervisor.prepared);
84 if (Option.isNone(prepared)) {
85 return yield* new PeerPairingCodeError({
86 reason: "unavailable",
87 cause: "The environment is not connected.",
88 });
89 }
90 return yield* mintPairingCredential(prepared.value, payload);
91});
92
93/**
94 * Mints a code on the environment it runs in. Each run mints its own: a code
95 * is one-use, so two overlapping answers must never share one.
96 */
97export function createPeerPairingCodeCommand<R, E>(
98 runtime: Atom.AtomRuntime<EnvironmentRegistry | HttpClient.HttpClient | R, E>,
99) {
100 return createEnvironmentCommand(runtime, {
101 label: "environment-data:peer-pairing-code:mint",
102 execute: (access: AuthMcpClientAccess) =>
103 mintOnEnvironment({ label: PEER_PAIRING_CODE_LABEL, scopes: mcpClientScopes(access) }).pipe(
104 Effect.map((minted) => minted.credential),
105 ),
106 });
107}
108
109/**
110 * Mints a hidden code that approves one app's sign-in at `access`, and only that. Servers
111 * without `mcpApprovalCodes` ignore the level, so check the capability first.
112 */
113export function createMcpApprovalCodeCommand<R, E>(
114 runtime: Atom.AtomRuntime<EnvironmentRegistry | HttpClient.HttpClient | R, E>,
115) {
116 return createEnvironmentCommand(runtime, {
117 label: "environment-data:mcp-approval-code:mint",
118 execute: (access: AuthMcpClientAccess) =>
119 mintOnEnvironment({ label: MCP_APPROVAL_CODE_LABEL, access }),
120 });
121}