@tangle-network/agent-integrations 0.25.7 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +11 -2
  2. package/dist/bin/tangle-catalog-runtime.js +6 -2
  3. package/dist/bin/tangle-catalog-runtime.js.map +1 -1
  4. package/dist/catalog.d.ts +4 -1
  5. package/dist/catalog.js +6 -2
  6. package/dist/chunk-2TW2QKGZ.js +94 -0
  7. package/dist/chunk-2TW2QKGZ.js.map +1 -0
  8. package/dist/chunk-ATYHZXLL.js +457 -0
  9. package/dist/chunk-ATYHZXLL.js.map +1 -0
  10. package/dist/{chunk-A5I3EYU5.js → chunk-ICSBYCE2.js} +122 -1
  11. package/dist/chunk-ICSBYCE2.js.map +1 -0
  12. package/dist/{chunk-WC63AI4Q.js → chunk-JU25UDN2.js} +1252 -225
  13. package/dist/chunk-JU25UDN2.js.map +1 -0
  14. package/dist/chunk-P24T3MLM.js +106 -0
  15. package/dist/chunk-P24T3MLM.js.map +1 -0
  16. package/dist/chunk-SVQ4PHDZ.js +129 -0
  17. package/dist/chunk-SVQ4PHDZ.js.map +1 -0
  18. package/dist/connect/index.d.ts +112 -0
  19. package/dist/connect/index.js +14 -0
  20. package/dist/connect/index.js.map +1 -0
  21. package/dist/connectors/adapters/index.d.ts +593 -1
  22. package/dist/connectors/adapters/index.js +22 -1
  23. package/dist/connectors/index.d.ts +2 -1
  24. package/dist/connectors/index.js +32 -10
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.js +57 -11
  27. package/dist/middleware/index.d.ts +137 -0
  28. package/dist/middleware/index.js +14 -0
  29. package/dist/middleware/index.js.map +1 -0
  30. package/dist/registry.d.ts +165 -2
  31. package/dist/registry.js +6 -2
  32. package/dist/runtime.d.ts +4 -1
  33. package/dist/runtime.js +6 -2
  34. package/dist/specs.d.ts +4 -1
  35. package/dist/tangle-catalog-runtime.d.ts +4 -1
  36. package/dist/tangle-catalog-runtime.js +6 -2
  37. package/dist/tangle-id-CTU4kGId.d.ts +553 -0
  38. package/dist/webhooks/index.d.ts +193 -0
  39. package/dist/webhooks/index.js +285 -0
  40. package/dist/webhooks/index.js.map +1 -0
  41. package/examples/discover-capabilities.ts +46 -0
  42. package/examples/webhook-router.ts +56 -0
  43. package/package.json +25 -12
  44. package/dist/chunk-A5I3EYU5.js.map +0 -1
  45. package/dist/chunk-WC63AI4Q.js.map +0 -1
  46. package/dist/index-BQY5ry2s.d.ts +0 -808
@@ -0,0 +1,106 @@
1
+ import {
2
+ DEFAULT_TANGLE_PLATFORM_URL,
3
+ TangleIdentityUnreachableError,
4
+ createTangleIdentityClient
5
+ } from "./chunk-ATYHZXLL.js";
6
+
7
+ // src/connect/index.ts
8
+ function startConnectFlow(opts, input) {
9
+ if (!input.appId) {
10
+ throw new TangleIdentityUnreachableError("connect/start: appId is required");
11
+ }
12
+ if (!input.state) {
13
+ throw new TangleIdentityUnreachableError(
14
+ "connect/start: state is required for CSRF protection (matches the platform contract)"
15
+ );
16
+ }
17
+ const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
18
+ const url = new URL(`${baseUrl}/cross-site/authorize`);
19
+ url.searchParams.set("app", input.appId);
20
+ url.searchParams.set("state", input.state);
21
+ if (input.redirectUri) url.searchParams.set("redirect", input.redirectUri);
22
+ return { authorizeUrl: url.toString() };
23
+ }
24
+ async function finishConnectFlow(opts, input) {
25
+ if (!input.code) {
26
+ throw new TangleIdentityUnreachableError("connect/finish: code is required");
27
+ }
28
+ if (!input.appId) {
29
+ throw new TangleIdentityUnreachableError("connect/finish: appId is required");
30
+ }
31
+ const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\/+$/, "");
32
+ const fetchImpl = opts.fetchImpl ?? fetch;
33
+ const timeoutMs = opts.timeoutMs ?? 5e3;
34
+ let res;
35
+ try {
36
+ res = await fetchImpl(`${baseUrl}/cross-site/exchange`, {
37
+ method: "POST",
38
+ headers: { "content-type": "application/json" },
39
+ body: JSON.stringify({ code: input.code, app: input.appId }),
40
+ signal: AbortSignal.timeout(timeoutMs)
41
+ });
42
+ } catch (err) {
43
+ throw new TangleIdentityUnreachableError("connect/finish: exchange request failed", { cause: err });
44
+ }
45
+ if (res.status === 401) {
46
+ throw new TangleIdentityUnreachableError(
47
+ "connect/finish: exchange code rejected \u2014 replay, expired, or wrong app",
48
+ { status: 401 }
49
+ );
50
+ }
51
+ if (!res.ok) {
52
+ const detail = await res.text().catch(() => "");
53
+ throw new TangleIdentityUnreachableError(
54
+ `connect/finish: /cross-site/exchange returned ${res.status}: ${detail.slice(0, 200)}`,
55
+ { status: res.status }
56
+ );
57
+ }
58
+ const body = await res.json().catch(() => null);
59
+ if (!body || typeof body.apiKey !== "string" || !body.user || typeof body.user.id !== "string") {
60
+ throw new TangleIdentityUnreachableError("connect/finish: exchange response had an invalid shape");
61
+ }
62
+ return {
63
+ apiKey: body.apiKey,
64
+ user: {
65
+ id: body.user.id,
66
+ ...typeof body.user.email === "string" ? { email: body.user.email } : {},
67
+ ...body.user.name !== void 0 ? { name: body.user.name } : {},
68
+ ...body.user.image !== void 0 ? { image: body.user.image } : {}
69
+ },
70
+ balance: typeof body.balance === "number" && Number.isFinite(body.balance) ? body.balance : 0
71
+ };
72
+ }
73
+ async function revokeConnectFlow(opts, input) {
74
+ if (!input.apiKey) {
75
+ throw new TangleIdentityUnreachableError("connect/revoke: apiKey is required");
76
+ }
77
+ const client = createTangleIdentityClient(opts);
78
+ await client.revokeSession(input.apiKey);
79
+ }
80
+ var InMemoryConnectStateStore = class {
81
+ entries = /* @__PURE__ */ new Map();
82
+ put(state, value) {
83
+ this.entries.set(state, {
84
+ appId: value.appId,
85
+ expiresAt: Date.now() + (value.ttlMs ?? 10 * 6e4)
86
+ });
87
+ }
88
+ consume(state) {
89
+ const entry = this.entries.get(state);
90
+ this.entries.delete(state);
91
+ if (!entry || entry.expiresAt <= Date.now()) return void 0;
92
+ return { appId: entry.appId };
93
+ }
94
+ /** Test-only — drop pending state between unit-test runs. */
95
+ clear() {
96
+ this.entries.clear();
97
+ }
98
+ };
99
+
100
+ export {
101
+ startConnectFlow,
102
+ finishConnectFlow,
103
+ revokeConnectFlow,
104
+ InMemoryConnectStateStore
105
+ };
106
+ //# sourceMappingURL=chunk-P24T3MLM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connect/index.ts"],"sourcesContent":["/**\n * @stable Cross-product connect flow.\n *\n * A product app (legal, tax, gtm, creative, agent-builder, sandbox, …) that\n * is already part of the Tangle trusted-app registry on id.tangle.tools\n * routes its users through this flow to obtain an `sk-tan-*` API key bound\n * to the calling user. The shape mirrors the platform's `/cross-site/*`\n * routes one-for-one so consumers can swap a bespoke fetch loop for these\n * helpers without changing the wire protocol.\n *\n * Three stages:\n *\n * 1. start({ appId, returnUrl, state }) → { authorizeUrl }\n * The product redirects the user to `authorizeUrl`. id.tangle.tools\n * checks the session cookie; if absent it punts to the login page\n * with a callback back to /cross-site/authorize.\n *\n * 2. callback({ code, app, state }) → { apiKey, user, workspaceId }\n * id.tangle.tools redirects back to the product's `returnUrl` with\n * `?code=…&app=…&state=…`. The product calls `finish()` with the\n * code; the helper POSTs /cross-site/exchange and returns the minted\n * key + identity. `state` is verified by the caller against its own\n * session (we never see it twice; CSRF is the caller's responsibility\n * per the platform contract — see `cross-site.ts` line 148).\n *\n * 3. revoke({ apiKey }) → void\n * Revoke the credential. Wraps `tangleIdentity().revokeSession`.\n *\n * Storage: this module is stateless. Persistence of the minted key (per\n * user, per workspace) is the caller's job — it goes in whatever\n * encrypted-credentials store the product already runs (sandbox uses Redis,\n * gtm uses Postgres, blueprints uses CF KV). The recipe is identical to\n * sandbox/api/src/lib/platform-client.ts — caller supplies a store, this\n * module hands back the raw key once and never persists it.\n *\n * Why not invent a new wire protocol: tcloud + sandbox already speak this\n * one against the live platform deployment. Diverging breaks the boundary\n * we maintain at the directive level (\"DO NOT invent the wire protocol —\n * use what tcloud already does\"). Every byte on the wire here matches a\n * test in `products/platform/api/tests/cross-site.test.ts`.\n */\n\nimport {\n createTangleIdentityClient,\n DEFAULT_TANGLE_PLATFORM_URL,\n TangleIdentityUnreachableError,\n type TangleIdentityOptions,\n type TangleUserSummary,\n} from '../connectors/adapters/tangle-id.js'\n\nexport interface ConnectFlowOptions extends TangleIdentityOptions {\n /** Base URL of id.tangle.tools (defaults to {@link DEFAULT_TANGLE_PLATFORM_URL}). */\n baseUrl?: string\n}\n\nexport interface StartConnectInput {\n /** Trusted app id (registered on id.tangle.tools — `evals`, `sandbox`,\n * `agent-builder`, `tax-agent`, `legal-agent`, …). */\n appId: string\n /** Caller-generated CSRF nonce. The caller stashes it in its own\n * session/cookie store; on the callback it MUST be compared against\n * the `state` returned in the redirect. */\n state: string\n /** Optional exact-match override of the registered callback URI. When\n * omitted, the platform falls back to the app's first registered\n * redirectUri. When provided, MUST equal one of the registered entries\n * (origin + pathname) — otherwise the platform refuses the flow. */\n redirectUri?: string\n}\n\nexport interface StartConnectOutput {\n /** The URL to redirect the user's browser to. */\n authorizeUrl: string\n}\n\nexport interface FinishConnectInput {\n /** Auth code returned by id.tangle.tools on the callback redirect. */\n code: string\n /** Same `appId` passed to `start()`. */\n appId: string\n}\n\nexport interface FinishConnectOutput {\n /** Newly-minted `sk-tan-*` API key bound to the calling user. Returned\n * ONCE — caller is responsible for stashing it in the product's\n * encrypted credentials store. */\n apiKey: string\n /** Identity hydrated from the exchange response. */\n user: TangleUserSummary\n /** Initial balance the platform returns alongside the key. */\n balance: number\n}\n\n/** Initiate a cross-product connect flow. Returns the URL the product\n * app should redirect the user's browser to. */\nexport function startConnectFlow(\n opts: ConnectFlowOptions,\n input: StartConnectInput,\n): StartConnectOutput {\n if (!input.appId) {\n throw new TangleIdentityUnreachableError('connect/start: appId is required')\n }\n if (!input.state) {\n throw new TangleIdentityUnreachableError(\n 'connect/start: state is required for CSRF protection (matches the platform contract)',\n )\n }\n const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\\/+$/, '')\n const url = new URL(`${baseUrl}/cross-site/authorize`)\n url.searchParams.set('app', input.appId)\n url.searchParams.set('state', input.state)\n if (input.redirectUri) url.searchParams.set('redirect', input.redirectUri)\n return { authorizeUrl: url.toString() }\n}\n\n/** Finish a cross-product connect flow. Calls /cross-site/exchange and\n * returns the minted API key + hydrated user identity. */\nexport async function finishConnectFlow(\n opts: ConnectFlowOptions,\n input: FinishConnectInput,\n): Promise<FinishConnectOutput> {\n if (!input.code) {\n throw new TangleIdentityUnreachableError('connect/finish: code is required')\n }\n if (!input.appId) {\n throw new TangleIdentityUnreachableError('connect/finish: appId is required')\n }\n const baseUrl = (opts.baseUrl ?? DEFAULT_TANGLE_PLATFORM_URL).replace(/\\/+$/, '')\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 5_000\n let res: Response\n try {\n res = await fetchImpl(`${baseUrl}/cross-site/exchange`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ code: input.code, app: input.appId }),\n signal: AbortSignal.timeout(timeoutMs),\n })\n } catch (err) {\n throw new TangleIdentityUnreachableError('connect/finish: exchange request failed', { cause: err })\n }\n if (res.status === 401) {\n throw new TangleIdentityUnreachableError(\n 'connect/finish: exchange code rejected — replay, expired, or wrong app',\n { status: 401 },\n )\n }\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new TangleIdentityUnreachableError(\n `connect/finish: /cross-site/exchange returned ${res.status}: ${detail.slice(0, 200)}`,\n { status: res.status },\n )\n }\n const body = (await res.json().catch(() => null)) as\n | {\n apiKey?: string\n user?: { id?: string; email?: string; name?: string | null; image?: string | null }\n balance?: number\n }\n | null\n if (!body || typeof body.apiKey !== 'string' || !body.user || typeof body.user.id !== 'string') {\n throw new TangleIdentityUnreachableError('connect/finish: exchange response had an invalid shape')\n }\n return {\n apiKey: body.apiKey,\n user: {\n id: body.user.id,\n ...(typeof body.user.email === 'string' ? { email: body.user.email } : {}),\n ...(body.user.name !== undefined ? { name: body.user.name } : {}),\n ...(body.user.image !== undefined ? { image: body.user.image } : {}),\n },\n balance: typeof body.balance === 'number' && Number.isFinite(body.balance) ? body.balance : 0,\n }\n}\n\n/** Revoke a minted API key. Idempotent — re-revoking a stale key is a no-op. */\nexport async function revokeConnectFlow(\n opts: ConnectFlowOptions,\n input: { apiKey: string },\n): Promise<void> {\n if (!input.apiKey) {\n throw new TangleIdentityUnreachableError('connect/revoke: apiKey is required')\n }\n const client = createTangleIdentityClient(opts)\n await client.revokeSession(input.apiKey)\n}\n\n/**\n * Convenience: build a tiny session manager keyed by `state` for products\n * that don't already have a CSRF store. NOT recommended for production —\n * use your existing session cookie / signed-state mechanism. Exposed for\n * tests and for quick prototyping. In-memory; not shared across workers.\n */\nexport class InMemoryConnectStateStore {\n private readonly entries = new Map<string, { appId: string; expiresAt: number }>()\n\n put(state: string, value: { appId: string; ttlMs?: number }): void {\n this.entries.set(state, {\n appId: value.appId,\n expiresAt: Date.now() + (value.ttlMs ?? 10 * 60_000),\n })\n }\n\n consume(state: string): { appId: string } | undefined {\n const entry = this.entries.get(state)\n this.entries.delete(state)\n if (!entry || entry.expiresAt <= Date.now()) return undefined\n return { appId: entry.appId }\n }\n\n /** Test-only — drop pending state between unit-test runs. */\n clear(): void {\n this.entries.clear()\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,129 @@
1
+ import {
2
+ TangleIdentityUnreachableError,
3
+ createTangleIdentityClient
4
+ } from "./chunk-ATYHZXLL.js";
5
+
6
+ // src/middleware/index.ts
7
+ async function requireTangleAuth(request, opts = {}) {
8
+ const client = opts.client ?? createTangleIdentityClient(opts);
9
+ const requireCredential = opts.requireCredential !== false;
10
+ const token = extractToken(request, opts.sessionCookieName);
11
+ if (!token) {
12
+ if (!requireCredential) {
13
+ return {
14
+ ok: true,
15
+ auth: {
16
+ userId: "",
17
+ workspaceId: "",
18
+ scopes: [],
19
+ kind: "session",
20
+ ownerType: "user"
21
+ }
22
+ };
23
+ }
24
+ return { ok: false, status: 401, reason: "missing_credential" };
25
+ }
26
+ let result;
27
+ try {
28
+ result = await client.verifyToken(token);
29
+ } catch (err) {
30
+ if (err instanceof TangleIdentityUnreachableError) {
31
+ return { ok: false, status: 503, reason: "platform_unreachable" };
32
+ }
33
+ throw err;
34
+ }
35
+ if (!result.valid) {
36
+ const status = result.reason === "service_token_refused" ? 403 : 401;
37
+ return { ok: false, status, reason: result.reason };
38
+ }
39
+ return {
40
+ ok: true,
41
+ auth: {
42
+ userId: result.userId,
43
+ workspaceId: result.workspaceId,
44
+ scopes: result.scopes,
45
+ kind: result.kind,
46
+ ownerType: result.ownerType,
47
+ ...result.expiresAt !== void 0 ? { expiresAt: result.expiresAt } : {},
48
+ ...result.credentialId ? { credentialId: result.credentialId } : {},
49
+ ...result.product ? { product: result.product } : {}
50
+ }
51
+ };
52
+ }
53
+ function extractToken(request, sessionCookieName = "better-auth.session_token") {
54
+ const headers = request.headers;
55
+ const authHeader = headerValue(headers, "authorization");
56
+ if (authHeader && authHeader.startsWith("Bearer ")) {
57
+ const candidate = authHeader.slice(7).trim();
58
+ if (!candidate) return void 0;
59
+ if (candidate.startsWith("svc_")) return void 0;
60
+ return candidate;
61
+ }
62
+ const cookieHeader = headerValue(headers, "cookie");
63
+ if (cookieHeader) {
64
+ const token = readCookie(cookieHeader, sessionCookieName);
65
+ if (token) return token;
66
+ }
67
+ return void 0;
68
+ }
69
+ function headerValue(headers, name) {
70
+ if (typeof headers.get === "function") {
71
+ return headers.get(name) ?? void 0;
72
+ }
73
+ const lookup = headers[name] ?? headers[name.toLowerCase()];
74
+ if (Array.isArray(lookup)) return typeof lookup[0] === "string" ? lookup[0] : void 0;
75
+ return typeof lookup === "string" ? lookup : void 0;
76
+ }
77
+ function readCookie(cookieHeader, name) {
78
+ const target = `${name}=`;
79
+ for (const piece of cookieHeader.split(";")) {
80
+ const trimmed = piece.trim();
81
+ if (!trimmed.startsWith(target)) continue;
82
+ return trimmed.slice(target.length);
83
+ }
84
+ return void 0;
85
+ }
86
+ function honoTangleAuthMiddleware(opts = {}) {
87
+ return async function tangleAuthHandler(c, next) {
88
+ const outcome = await requireTangleAuth(c.req.raw, opts);
89
+ if (!outcome.ok) {
90
+ return new Response(
91
+ JSON.stringify({
92
+ success: false,
93
+ error: { code: outcome.reason.toUpperCase(), message: outcome.reason }
94
+ }),
95
+ {
96
+ status: outcome.status,
97
+ headers: { "content-type": "application/json" }
98
+ }
99
+ );
100
+ }
101
+ c.set("tangleAuth", outcome.auth);
102
+ await next();
103
+ };
104
+ }
105
+ function expressTangleAuthMiddleware(opts = {}) {
106
+ return async function tangleAuthHandler(req, res, next) {
107
+ const headers = req.headers ?? {};
108
+ const outcome = await requireTangleAuth({ headers }, opts);
109
+ if (!outcome.ok) {
110
+ res.status(outcome.status);
111
+ res.setHeader?.("content-type", "application/json");
112
+ res.end(JSON.stringify({
113
+ success: false,
114
+ error: { code: outcome.reason.toUpperCase(), message: outcome.reason }
115
+ }));
116
+ return;
117
+ }
118
+ req.tangleAuth = outcome.auth;
119
+ next();
120
+ };
121
+ }
122
+
123
+ export {
124
+ requireTangleAuth,
125
+ extractToken,
126
+ honoTangleAuthMiddleware,
127
+ expressTangleAuthMiddleware
128
+ };
129
+ //# sourceMappingURL=chunk-SVQ4PHDZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/index.ts"],"sourcesContent":["/**\n * @stable Drop-in request middleware that verifies id.tangle.tools\n * credentials and attaches `{ userId, workspaceId, scopes, kind }` to the\n * request.\n *\n * The middleware is framework-agnostic. Instead of binding to express /\n * hono / itty-router specifically (each has its own request typings and\n * lifecycle), the helper accepts either a `Request` (web standard) or a\n * `{ headers }`-shaped object and returns a typed result the caller wires\n * into its own context. Concrete adapters for hono / express / fetch live\n * one call below in the same module so a product can pick the shape it\n * uses without dragging in framework types from the rest.\n *\n * Why this matters: legal-agent runs on Bun + Hono. tax-agent runs on\n * CF Workers + itty-router. gtm-agent runs on Node + Express. Wiring an\n * identical \"is this caller authed\" check across all three is what\n * unblocks shipping product apps in parallel.\n *\n * Token sources (checked in order):\n *\n * 1. `Authorization: Bearer <token>` — handles both sk-tan-* API keys\n * and Better Auth-issued session bearers.\n * 2. `Cookie: better-auth.session_token=<jwt>` — the canonical browser\n * flow. We forward the cookie value as a Bearer to the platform's\n * `/api/auth/get-session` endpoint.\n *\n * On success the middleware returns:\n *\n * { ok: true, auth: { userId, workspaceId, scopes, kind, expiresAt? } }\n *\n * On failure:\n *\n * { ok: false, status: 401|403, reason: '<stable-code>' }\n *\n * The caller decides whether to short-circuit the request (production) or\n * downgrade to anonymous (read-only public endpoints). The middleware\n * NEVER throws on bad-token; only true platform unreachability bubbles up\n * as a `TangleIdentityUnreachableError`.\n */\n\nimport {\n createTangleIdentityClient,\n TangleIdentityUnreachableError,\n type TangleIdentityClient,\n type TangleIdentityOptions,\n type TangleTokenVerifyFailure,\n type TangleTokenVerifyResult,\n} from '../connectors/adapters/tangle-id.js'\n\n/** Auth context the middleware attaches to the request on success. */\nexport interface TangleAuthContext {\n userId: string\n workspaceId: string\n scopes: string[]\n kind: 'api_key' | 'session'\n /** Wall-clock ms epoch when the credential expires, when known. */\n expiresAt?: number\n /** Stable credential id (key id for API keys, session id for sessions). */\n credentialId?: string\n /** Owner-shape on the platform side. */\n ownerType: 'user' | 'team'\n /** Product the credential is scoped to, when known. */\n product?: string\n}\n\nexport type TangleAuthOutcome =\n | { ok: true; auth: TangleAuthContext }\n | { ok: false; status: 401 | 403 | 503; reason: TangleAuthReason }\n\n/** Stable failure reasons surfaced to the caller. */\nexport type TangleAuthReason =\n | 'missing_credential'\n | 'malformed_credential'\n | 'service_token_refused'\n | TangleTokenVerifyFailure\n | 'platform_unreachable'\n\nexport interface RequireTangleAuthOptions extends TangleIdentityOptions {\n /** Pre-built client. When supplied, all `TangleIdentityOptions` fields\n * are ignored. Tests pass a stub here; production code typically\n * constructs the client once at boot and passes it in. */\n client?: TangleIdentityClient\n /** Override the cookie name where the session bearer lives. Defaults\n * to `better-auth.session_token` — matches the platform's Better Auth\n * configuration. */\n sessionCookieName?: string\n /** If true, missing-credential returns `ok: false, status: 401`\n * (default). If false, the middleware returns `ok: true` with a\n * synthetic anonymous context — useful for public endpoints that want\n * to opportunistically hydrate identity. */\n requireCredential?: boolean\n}\n\n/**\n * Verify the credential on `request` against id.tangle.tools and resolve\n * to a typed {@link TangleAuthContext}. Request type is the web-standard\n * `Request` shape — works in Bun, Workers, Deno, Node 20+, Hono context's\n * `c.req.raw`, and Express adapters that surface `req` via `webRequest()`.\n */\nexport async function requireTangleAuth(\n request: Pick<Request, 'headers'>,\n opts: RequireTangleAuthOptions = {},\n): Promise<TangleAuthOutcome> {\n const client = opts.client ?? createTangleIdentityClient(opts)\n const requireCredential = opts.requireCredential !== false\n\n const token = extractToken(request, opts.sessionCookieName)\n if (!token) {\n if (!requireCredential) {\n return {\n ok: true,\n auth: {\n userId: '',\n workspaceId: '',\n scopes: [],\n kind: 'session',\n ownerType: 'user',\n },\n }\n }\n return { ok: false, status: 401, reason: 'missing_credential' }\n }\n\n let result: TangleTokenVerifyResult\n try {\n result = await client.verifyToken(token)\n } catch (err) {\n if (err instanceof TangleIdentityUnreachableError) {\n return { ok: false, status: 503, reason: 'platform_unreachable' }\n }\n throw err\n }\n\n if (!result.valid) {\n const status = result.reason === 'service_token_refused' ? 403 : 401\n return { ok: false, status, reason: result.reason }\n }\n\n return {\n ok: true,\n auth: {\n userId: result.userId,\n workspaceId: result.workspaceId,\n scopes: result.scopes,\n kind: result.kind,\n ownerType: result.ownerType,\n ...(result.expiresAt !== undefined ? { expiresAt: result.expiresAt } : {}),\n ...(result.credentialId ? { credentialId: result.credentialId } : {}),\n ...(result.product ? { product: result.product } : {}),\n },\n }\n}\n\n/**\n * Extract the bearer credential from a request. Public so callers that\n * want to reuse the same token-discovery logic outside the middleware\n * (e.g. to attribute audit log entries) don't have to re-implement it.\n *\n * Order: Authorization header first (canonical), session cookie second.\n * Service tokens (`svc_*`) are explicitly dropped — the platform's\n * middleware refuses to map them to a user, so accepting them here\n * would invite the exact \"service-as-user\" privilege escalation the\n * platform's `resolveServiceIdentity` already guards against.\n */\nexport function extractToken(\n request: Pick<Request, 'headers'>,\n sessionCookieName = 'better-auth.session_token',\n): string | undefined {\n const headers = request.headers\n const authHeader = headerValue(headers, 'authorization')\n if (authHeader && authHeader.startsWith('Bearer ')) {\n const candidate = authHeader.slice(7).trim()\n if (!candidate) return undefined\n if (candidate.startsWith('svc_')) return undefined\n return candidate\n }\n const cookieHeader = headerValue(headers, 'cookie')\n if (cookieHeader) {\n const token = readCookie(cookieHeader, sessionCookieName)\n if (token) return token\n }\n return undefined\n}\n\nfunction headerValue(headers: Headers | Record<string, string | string[] | undefined>, name: string): string | undefined {\n if (typeof (headers as Headers).get === 'function') {\n return (headers as Headers).get(name) ?? undefined\n }\n const lookup = (headers as Record<string, unknown>)[name] ?? (headers as Record<string, unknown>)[name.toLowerCase()]\n if (Array.isArray(lookup)) return typeof lookup[0] === 'string' ? lookup[0] : undefined\n return typeof lookup === 'string' ? lookup : undefined\n}\n\nfunction readCookie(cookieHeader: string, name: string): string | undefined {\n // Cookies are semicolon-delimited; we DON'T URL-decode the value because\n // Better Auth's signed-cookie format includes `.` and `=` that survive\n // unencoded through the standard cookie parser. Matching by exact name=\n // prefix keeps us protocol-correct.\n const target = `${name}=`\n for (const piece of cookieHeader.split(';')) {\n const trimmed = piece.trim()\n if (!trimmed.startsWith(target)) continue\n return trimmed.slice(target.length)\n }\n return undefined\n}\n\n/**\n * Hono-flavored convenience wrapper. Returns a hono middleware factory\n * that calls {@link requireTangleAuth} and stashes the result on the\n * Hono context under `c.set('tangleAuth', auth)`. On failure short-\n * circuits with the canonical {success:false} envelope the platform uses.\n *\n * Kept typed against a structural `Context`-like shape so this module\n * does NOT take a hono peerDep. Consumers pass `c` directly.\n */\nexport function honoTangleAuthMiddleware(opts: RequireTangleAuthOptions = {}) {\n return async function tangleAuthHandler(\n c: HonoLikeContext,\n next: () => Promise<void>,\n ): Promise<Response | void> {\n const outcome = await requireTangleAuth(c.req.raw, opts)\n if (!outcome.ok) {\n return new Response(\n JSON.stringify({\n success: false,\n error: { code: outcome.reason.toUpperCase(), message: outcome.reason },\n }),\n {\n status: outcome.status,\n headers: { 'content-type': 'application/json' },\n },\n )\n }\n c.set('tangleAuth', outcome.auth)\n await next()\n }\n}\n\n/** Minimal Hono Context-shaped surface. Avoids the hono peerDep. */\nexport interface HonoLikeContext {\n req: { raw: Request }\n set(key: 'tangleAuth', value: TangleAuthContext): void\n}\n\n/**\n * Express-flavored convenience wrapper. Same outcome shape as the Hono\n * helper, expressed via the Node `req` / `res` / `next` triple. Consumers\n * pass the triple as positional args. Returns a function compatible with\n * any express-like `app.use(fn)`.\n */\nexport function expressTangleAuthMiddleware(opts: RequireTangleAuthOptions = {}) {\n return async function tangleAuthHandler(\n req: ExpressLikeRequest,\n res: ExpressLikeResponse,\n next: (err?: unknown) => void,\n ): Promise<void> {\n // Build a `Request`-compatible header view from express's\n // `IncomingMessage.headers` map — string | string[] | undefined.\n const headers: Record<string, string | string[] | undefined> = req.headers ?? {}\n const outcome = await requireTangleAuth({ headers: headers as never }, opts)\n if (!outcome.ok) {\n res.status(outcome.status)\n res.setHeader?.('content-type', 'application/json')\n res.end(JSON.stringify({\n success: false,\n error: { code: outcome.reason.toUpperCase(), message: outcome.reason },\n }))\n return\n }\n req.tangleAuth = outcome.auth\n next()\n }\n}\n\n/** Minimal Express-shaped surfaces. Avoids the express peerDep. */\nexport interface ExpressLikeRequest {\n headers: Record<string, string | string[] | undefined>\n tangleAuth?: TangleAuthContext\n}\nexport interface ExpressLikeResponse {\n status(code: number): unknown\n setHeader?(name: string, value: string): unknown\n end(body: string): unknown\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,112 @@
1
+ import { T as TangleIdentityOptions, a as TangleUserSummary } from '../tangle-id-CTU4kGId.js';
2
+
3
+ /**
4
+ * @stable Cross-product connect flow.
5
+ *
6
+ * A product app (legal, tax, gtm, creative, agent-builder, sandbox, …) that
7
+ * is already part of the Tangle trusted-app registry on id.tangle.tools
8
+ * routes its users through this flow to obtain an `sk-tan-*` API key bound
9
+ * to the calling user. The shape mirrors the platform's `/cross-site/*`
10
+ * routes one-for-one so consumers can swap a bespoke fetch loop for these
11
+ * helpers without changing the wire protocol.
12
+ *
13
+ * Three stages:
14
+ *
15
+ * 1. start({ appId, returnUrl, state }) → { authorizeUrl }
16
+ * The product redirects the user to `authorizeUrl`. id.tangle.tools
17
+ * checks the session cookie; if absent it punts to the login page
18
+ * with a callback back to /cross-site/authorize.
19
+ *
20
+ * 2. callback({ code, app, state }) → { apiKey, user, workspaceId }
21
+ * id.tangle.tools redirects back to the product's `returnUrl` with
22
+ * `?code=…&app=…&state=…`. The product calls `finish()` with the
23
+ * code; the helper POSTs /cross-site/exchange and returns the minted
24
+ * key + identity. `state` is verified by the caller against its own
25
+ * session (we never see it twice; CSRF is the caller's responsibility
26
+ * per the platform contract — see `cross-site.ts` line 148).
27
+ *
28
+ * 3. revoke({ apiKey }) → void
29
+ * Revoke the credential. Wraps `tangleIdentity().revokeSession`.
30
+ *
31
+ * Storage: this module is stateless. Persistence of the minted key (per
32
+ * user, per workspace) is the caller's job — it goes in whatever
33
+ * encrypted-credentials store the product already runs (sandbox uses Redis,
34
+ * gtm uses Postgres, blueprints uses CF KV). The recipe is identical to
35
+ * sandbox/api/src/lib/platform-client.ts — caller supplies a store, this
36
+ * module hands back the raw key once and never persists it.
37
+ *
38
+ * Why not invent a new wire protocol: tcloud + sandbox already speak this
39
+ * one against the live platform deployment. Diverging breaks the boundary
40
+ * we maintain at the directive level ("DO NOT invent the wire protocol —
41
+ * use what tcloud already does"). Every byte on the wire here matches a
42
+ * test in `products/platform/api/tests/cross-site.test.ts`.
43
+ */
44
+
45
+ interface ConnectFlowOptions extends TangleIdentityOptions {
46
+ /** Base URL of id.tangle.tools (defaults to {@link DEFAULT_TANGLE_PLATFORM_URL}). */
47
+ baseUrl?: string;
48
+ }
49
+ interface StartConnectInput {
50
+ /** Trusted app id (registered on id.tangle.tools — `evals`, `sandbox`,
51
+ * `agent-builder`, `tax-agent`, `legal-agent`, …). */
52
+ appId: string;
53
+ /** Caller-generated CSRF nonce. The caller stashes it in its own
54
+ * session/cookie store; on the callback it MUST be compared against
55
+ * the `state` returned in the redirect. */
56
+ state: string;
57
+ /** Optional exact-match override of the registered callback URI. When
58
+ * omitted, the platform falls back to the app's first registered
59
+ * redirectUri. When provided, MUST equal one of the registered entries
60
+ * (origin + pathname) — otherwise the platform refuses the flow. */
61
+ redirectUri?: string;
62
+ }
63
+ interface StartConnectOutput {
64
+ /** The URL to redirect the user's browser to. */
65
+ authorizeUrl: string;
66
+ }
67
+ interface FinishConnectInput {
68
+ /** Auth code returned by id.tangle.tools on the callback redirect. */
69
+ code: string;
70
+ /** Same `appId` passed to `start()`. */
71
+ appId: string;
72
+ }
73
+ interface FinishConnectOutput {
74
+ /** Newly-minted `sk-tan-*` API key bound to the calling user. Returned
75
+ * ONCE — caller is responsible for stashing it in the product's
76
+ * encrypted credentials store. */
77
+ apiKey: string;
78
+ /** Identity hydrated from the exchange response. */
79
+ user: TangleUserSummary;
80
+ /** Initial balance the platform returns alongside the key. */
81
+ balance: number;
82
+ }
83
+ /** Initiate a cross-product connect flow. Returns the URL the product
84
+ * app should redirect the user's browser to. */
85
+ declare function startConnectFlow(opts: ConnectFlowOptions, input: StartConnectInput): StartConnectOutput;
86
+ /** Finish a cross-product connect flow. Calls /cross-site/exchange and
87
+ * returns the minted API key + hydrated user identity. */
88
+ declare function finishConnectFlow(opts: ConnectFlowOptions, input: FinishConnectInput): Promise<FinishConnectOutput>;
89
+ /** Revoke a minted API key. Idempotent — re-revoking a stale key is a no-op. */
90
+ declare function revokeConnectFlow(opts: ConnectFlowOptions, input: {
91
+ apiKey: string;
92
+ }): Promise<void>;
93
+ /**
94
+ * Convenience: build a tiny session manager keyed by `state` for products
95
+ * that don't already have a CSRF store. NOT recommended for production —
96
+ * use your existing session cookie / signed-state mechanism. Exposed for
97
+ * tests and for quick prototyping. In-memory; not shared across workers.
98
+ */
99
+ declare class InMemoryConnectStateStore {
100
+ private readonly entries;
101
+ put(state: string, value: {
102
+ appId: string;
103
+ ttlMs?: number;
104
+ }): void;
105
+ consume(state: string): {
106
+ appId: string;
107
+ } | undefined;
108
+ /** Test-only — drop pending state between unit-test runs. */
109
+ clear(): void;
110
+ }
111
+
112
+ export { type ConnectFlowOptions, type FinishConnectInput, type FinishConnectOutput, InMemoryConnectStateStore, type StartConnectInput, type StartConnectOutput, finishConnectFlow, revokeConnectFlow, startConnectFlow };
@@ -0,0 +1,14 @@
1
+ import {
2
+ InMemoryConnectStateStore,
3
+ finishConnectFlow,
4
+ revokeConnectFlow,
5
+ startConnectFlow
6
+ } from "../chunk-P24T3MLM.js";
7
+ import "../chunk-ATYHZXLL.js";
8
+ export {
9
+ InMemoryConnectStateStore,
10
+ finishConnectFlow,
11
+ revokeConnectFlow,
12
+ startConnectFlow
13
+ };
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}