@agent-native/core 0.77.8 → 0.77.11

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 (216) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +35 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/a2a/task-store.ts +25 -3
  5. package/corpus/core/src/agent/harness/store.ts +89 -55
  6. package/corpus/core/src/agent/observational-memory/store.ts +40 -14
  7. package/corpus/core/src/agent/run-store.ts +116 -59
  8. package/corpus/core/src/application-state/store.ts +36 -2
  9. package/corpus/core/src/audit/store.ts +37 -6
  10. package/corpus/core/src/browser-sessions/store.ts +31 -12
  11. package/corpus/core/src/chat-threads/store.ts +67 -3
  12. package/corpus/core/src/checkpoints/store.ts +14 -3
  13. package/corpus/core/src/cli/create.ts +9 -0
  14. package/corpus/core/src/collab/awareness.ts +12 -9
  15. package/corpus/core/src/collab/client.ts +4 -2
  16. package/corpus/core/src/collab/storage.ts +20 -2
  17. package/corpus/core/src/db/ddl-guard.ts +112 -0
  18. package/corpus/core/src/extensions/store.ts +76 -3
  19. package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
  20. package/corpus/core/src/integrations/config-store.ts +19 -18
  21. package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
  22. package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
  23. package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
  24. package/corpus/core/src/integrations/remote-push-store.ts +75 -35
  25. package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
  26. package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
  27. package/corpus/core/src/mcp/connect-store.ts +53 -16
  28. package/corpus/core/src/mcp/oauth-store.ts +32 -7
  29. package/corpus/core/src/notifications/store.ts +20 -4
  30. package/corpus/core/src/oauth-tokens/store.ts +45 -2
  31. package/corpus/core/src/observability/store.ts +154 -47
  32. package/corpus/core/src/progress/store.ts +36 -21
  33. package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
  34. package/corpus/core/src/provider-api/custom-registry.ts +15 -4
  35. package/corpus/core/src/provider-api/quota-governor.ts +15 -3
  36. package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
  37. package/corpus/core/src/resources/store.ts +66 -31
  38. package/corpus/core/src/secrets/storage.ts +18 -22
  39. package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
  40. package/corpus/core/src/server/auth.ts +22 -4
  41. package/corpus/core/src/server/better-auth-instance.ts +49 -15
  42. package/corpus/core/src/server/embed-session.ts +19 -4
  43. package/corpus/core/src/server/identity-sso-store.ts +40 -11
  44. package/corpus/core/src/server/recap-image-store.ts +28 -9
  45. package/corpus/core/src/settings/store.ts +14 -19
  46. package/corpus/core/src/usage/store.ts +51 -16
  47. package/corpus/core/src/workspace-connections/store.ts +200 -22
  48. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
  49. package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
  50. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  51. package/corpus/templates/content/actions/list-trashed-content-databases.ts +36 -2
  52. package/corpus/templates/content/actions/move-document.ts +79 -33
  53. package/corpus/templates/content/actions/restore-content-database.ts +40 -3
  54. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +84 -2
  55. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +177 -49
  56. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +393 -219
  57. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +632 -467
  58. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +114 -14
  59. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +56 -15
  60. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +6 -5
  61. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +12 -4
  62. package/corpus/templates/content/app/global.css +9 -0
  63. package/corpus/templates/content/app/hooks/use-create-page.ts +36 -7
  64. package/corpus/templates/content/app/i18n-data.ts +140 -10
  65. package/corpus/templates/content/changelog/2026-06-25-comment-cards-now-track-their-highlighted-text-while-scrolli.md +6 -0
  66. package/corpus/templates/content/changelog/2026-06-25-comments-now-scroll-with-the-document-and-stay-below-the-pag.md +6 -0
  67. package/corpus/templates/content/changelog/2026-06-25-creating-a-database-from-the-slash-menu-no-longer-leaves-sta.md +6 -0
  68. package/corpus/templates/content/changelog/2026-06-25-sidebar-hover-controls-no-longer-leave-a-lingering-fade-when.md +6 -0
  69. package/corpus/templates/content/changelog/2026-06-25-sidebar-page-actions-are-now-easier-to-see-when-hovering-ina.md +6 -0
  70. package/corpus/templates/content/changelog/2026-06-25-text-selections-in-the-editor-no-longer-show-white-gaps-when.md +6 -0
  71. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-has-a-one-click-page-link-copy-button.md +6 -0
  72. package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-shows-page-breadcrumbs-and-the-latest.md +6 -0
  73. package/corpus/templates/content/changelog/2026-06-25-the-page-command-now-leaves-a-notion-style-page-reference-an.md +6 -0
  74. package/corpus/templates/content/shared/nfm.ts +16 -0
  75. package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
  76. package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
  77. package/corpus/templates/plan/app/global.css +6 -11
  78. package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
  79. package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
  80. package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
  81. package/dist/a2a/task-store.d.ts.map +1 -1
  82. package/dist/a2a/task-store.js +19 -3
  83. package/dist/a2a/task-store.js.map +1 -1
  84. package/dist/agent/harness/store.d.ts.map +1 -1
  85. package/dist/agent/harness/store.js +63 -34
  86. package/dist/agent/harness/store.js.map +1 -1
  87. package/dist/agent/observational-memory/store.d.ts.map +1 -1
  88. package/dist/agent/observational-memory/store.js +30 -8
  89. package/dist/agent/observational-memory/store.js.map +1 -1
  90. package/dist/agent/run-store.d.ts.map +1 -1
  91. package/dist/agent/run-store.js +103 -46
  92. package/dist/agent/run-store.js.map +1 -1
  93. package/dist/application-state/store.d.ts.map +1 -1
  94. package/dist/application-state/store.js +28 -2
  95. package/dist/application-state/store.js.map +1 -1
  96. package/dist/audit/store.d.ts.map +1 -1
  97. package/dist/audit/store.js +18 -3
  98. package/dist/audit/store.js.map +1 -1
  99. package/dist/browser-sessions/store.d.ts.map +1 -1
  100. package/dist/browser-sessions/store.js +22 -7
  101. package/dist/browser-sessions/store.js.map +1 -1
  102. package/dist/chat-threads/store.d.ts.map +1 -1
  103. package/dist/chat-threads/store.js +51 -3
  104. package/dist/chat-threads/store.js.map +1 -1
  105. package/dist/checkpoints/store.d.ts.map +1 -1
  106. package/dist/checkpoints/store.js +12 -3
  107. package/dist/checkpoints/store.js.map +1 -1
  108. package/dist/cli/create.d.ts.map +1 -1
  109. package/dist/cli/create.js +9 -0
  110. package/dist/cli/create.js.map +1 -1
  111. package/dist/collab/awareness.d.ts +1 -1
  112. package/dist/collab/awareness.d.ts.map +1 -1
  113. package/dist/collab/awareness.js +12 -8
  114. package/dist/collab/awareness.js.map +1 -1
  115. package/dist/collab/client.d.ts.map +1 -1
  116. package/dist/collab/client.js +4 -3
  117. package/dist/collab/client.js.map +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/collab/storage.d.ts.map +1 -1
  120. package/dist/collab/storage.js +14 -2
  121. package/dist/collab/storage.js.map +1 -1
  122. package/dist/db/ddl-guard.d.ts +63 -0
  123. package/dist/db/ddl-guard.d.ts.map +1 -1
  124. package/dist/db/ddl-guard.js +85 -0
  125. package/dist/db/ddl-guard.js.map +1 -1
  126. package/dist/extensions/store.d.ts.map +1 -1
  127. package/dist/extensions/store.js +32 -3
  128. package/dist/extensions/store.js.map +1 -1
  129. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  130. package/dist/integrations/a2a-continuations-store.js +44 -24
  131. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  132. package/dist/integrations/config-store.d.ts.map +1 -1
  133. package/dist/integrations/config-store.js +17 -11
  134. package/dist/integrations/config-store.js.map +1 -1
  135. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  136. package/dist/integrations/pending-tasks-store.js +24 -16
  137. package/dist/integrations/pending-tasks-store.js.map +1 -1
  138. package/dist/integrations/remote-commands-store.d.ts.map +1 -1
  139. package/dist/integrations/remote-commands-store.js +29 -21
  140. package/dist/integrations/remote-commands-store.js.map +1 -1
  141. package/dist/integrations/remote-devices-store.d.ts.map +1 -1
  142. package/dist/integrations/remote-devices-store.js +33 -18
  143. package/dist/integrations/remote-devices-store.js.map +1 -1
  144. package/dist/integrations/remote-push-store.d.ts.map +1 -1
  145. package/dist/integrations/remote-push-store.js +53 -31
  146. package/dist/integrations/remote-push-store.js.map +1 -1
  147. package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
  148. package/dist/integrations/remote-run-events-store.js +26 -10
  149. package/dist/integrations/remote-run-events-store.js.map +1 -1
  150. package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
  151. package/dist/integrations/thread-mapping-store.js +10 -2
  152. package/dist/integrations/thread-mapping-store.js.map +1 -1
  153. package/dist/mcp/connect-store.d.ts.map +1 -1
  154. package/dist/mcp/connect-store.js +34 -16
  155. package/dist/mcp/connect-store.js.map +1 -1
  156. package/dist/mcp/oauth-store.d.ts.map +1 -1
  157. package/dist/mcp/oauth-store.js +22 -7
  158. package/dist/mcp/oauth-store.js.map +1 -1
  159. package/dist/notifications/store.d.ts.map +1 -1
  160. package/dist/notifications/store.js +15 -3
  161. package/dist/notifications/store.js.map +1 -1
  162. package/dist/oauth-tokens/store.d.ts.map +1 -1
  163. package/dist/oauth-tokens/store.js +33 -2
  164. package/dist/oauth-tokens/store.js.map +1 -1
  165. package/dist/observability/store.d.ts.map +1 -1
  166. package/dist/observability/store.js +72 -29
  167. package/dist/observability/store.js.map +1 -1
  168. package/dist/progress/store.d.ts.map +1 -1
  169. package/dist/progress/store.js +31 -16
  170. package/dist/progress/store.js.map +1 -1
  171. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
  172. package/dist/provider-api/corpus-jobs-store.js +16 -5
  173. package/dist/provider-api/corpus-jobs-store.js.map +1 -1
  174. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  175. package/dist/provider-api/custom-registry.js +14 -4
  176. package/dist/provider-api/custom-registry.js.map +1 -1
  177. package/dist/provider-api/quota-governor.d.ts.map +1 -1
  178. package/dist/provider-api/quota-governor.js +12 -3
  179. package/dist/provider-api/quota-governor.js.map +1 -1
  180. package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
  181. package/dist/provider-api/staged-datasets-store.js +16 -5
  182. package/dist/provider-api/staged-datasets-store.js.map +1 -1
  183. package/dist/resources/store.d.ts.map +1 -1
  184. package/dist/resources/store.js +56 -25
  185. package/dist/resources/store.js.map +1 -1
  186. package/dist/secrets/storage.d.ts.map +1 -1
  187. package/dist/secrets/storage.js +10 -14
  188. package/dist/secrets/storage.js.map +1 -1
  189. package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
  190. package/dist/server/agent-teams-run-queue.js +15 -4
  191. package/dist/server/agent-teams-run-queue.js.map +1 -1
  192. package/dist/server/auth.d.ts.map +1 -1
  193. package/dist/server/auth.js +16 -2
  194. package/dist/server/auth.js.map +1 -1
  195. package/dist/server/better-auth-instance.d.ts.map +1 -1
  196. package/dist/server/better-auth-instance.js +55 -21
  197. package/dist/server/better-auth-instance.js.map +1 -1
  198. package/dist/server/embed-session.d.ts.map +1 -1
  199. package/dist/server/embed-session.js +15 -4
  200. package/dist/server/embed-session.js.map +1 -1
  201. package/dist/server/identity-sso-store.d.ts.map +1 -1
  202. package/dist/server/identity-sso-store.js +30 -11
  203. package/dist/server/identity-sso-store.js.map +1 -1
  204. package/dist/server/recap-image-store.d.ts.map +1 -1
  205. package/dist/server/recap-image-store.js +21 -7
  206. package/dist/server/recap-image-store.js.map +1 -1
  207. package/dist/settings/store.d.ts.map +1 -1
  208. package/dist/settings/store.js +11 -13
  209. package/dist/settings/store.js.map +1 -1
  210. package/dist/usage/store.d.ts.map +1 -1
  211. package/dist/usage/store.js +36 -11
  212. package/dist/usage/store.js.map +1 -1
  213. package/dist/workspace-connections/store.d.ts.map +1 -1
  214. package/dist/workspace-connections/store.js +57 -7
  215. package/dist/workspace-connections/store.js.map +1 -1
  216. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"connect-store.js","sourceRoot":"","sources":["../../src/mcp/connect-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Framework-table store for the \"connect external agents\" feature.\n *\n * Two additive, dialect-agnostic tables back the browser **Connect** page and\n * the OAuth-style **device-code flow** a CLI drives:\n *\n * - `mcp_connect_tokens` — one row per minted MCP token. We never store the\n * token value (it's a signed JWT); only its `jti` so revocation is a\n * SQL lookup. Revoking sets `revoked_at`; the row is never deleted.\n * - `mcp_device_codes` — short-lived (10 min) device/user code pairs for\n * the OAuth 2.0 device-authorization-style CLI flow. Single-use\n * (`consumed_at`), rate-limited at creation.\n *\n * Mirrors `application-state/store.ts`: lazy `ensureTable()`, `getDbExec()`,\n * `isConnectionError()` swallow so a transient Neon WS drop never 500s.\n * `CREATE TABLE IF NOT EXISTS` only — strictly additive, never DROP / ALTER\n * (shared prod DB rule).\n */\n\nimport { randomBytes, randomUUID } from \"node:crypto\";\n\nimport { getDbExec, isConnectionError, intType } from \"../db/client.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Scope claim that marks a connect-minted token (vs. an ordinary A2A\n * delegation JWT). Only tokens carrying this scope go through the revoke\n * lookup in `verifyAuth` — defined here so both `connect-route.ts` and\n * `build-server.ts` import it from the leaf store without a cycle.\n */\nexport const MCP_CONNECT_SCOPE = \"mcp-connect\";\n\n/**\n * Client id used when connect/device flows have to mint a standard MCP OAuth\n * access token instead of an A2A JWT (for deployments without A2A_SECRET).\n */\nexport const MCP_CONNECT_OAUTH_CLIENT_ID = \"agent-native-connect\";\n\n/** Device codes are valid for 10 minutes. */\nexport const DEVICE_CODE_TTL_MS = 10 * 60_000;\n\n/** Default minted-token lifetime. Configurable per-request 1–365 days. */\nexport const DEFAULT_TOKEN_TTL_DAYS = 365;\nexport const MIN_TOKEN_TTL_DAYS = 1;\nexport const MAX_TOKEN_TTL_DAYS = 365;\n\n/**\n * Rate limit for `device/start`: at most this many device codes may be created\n * within `DEVICE_START_WINDOW_MS`. Unauthenticated endpoint — keep it tight so\n * a hostile client can't flood the table or brute-force user codes.\n */\nexport const DEVICE_START_MAX = 20;\nexport const DEVICE_START_WINDOW_MS = 60_000;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // Additive only. Never DROP / ALTER — this DB is shared across every\n // deploy context (preview/branch/prod) for hosted templates.\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_connect_tokens (\n id TEXT PRIMARY KEY,\n jti TEXT UNIQUE NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n label TEXT,\n kind TEXT NOT NULL DEFAULT 'personal',\n service_name TEXT,\n created_by TEXT,\n created_at ${intType()},\n last_used_at ${intType()},\n revoked_at ${intType()}\n )\n `);\n // Additive columns for org service tokens (deployments that created the\n // table before these columns existed; fresh DBs get them via the CREATE\n // TABLE above). kind='personal' (default) preserves the original\n // per-user token; kind='service' marks tokens minted for an org service\n // principal (e.g. CI) rather than a person. service_name is the\n // human-readable service label (e.g. \"ci\"); created_by records the\n // human who minted it, for audit.\n for (const [withIfNotExists, plain] of [\n [\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'personal'`,\n `ALTER TABLE mcp_connect_tokens ADD COLUMN kind TEXT NOT NULL DEFAULT 'personal'`,\n ],\n [\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS service_name TEXT`,\n `ALTER TABLE mcp_connect_tokens ADD COLUMN service_name TEXT`,\n ],\n [\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS created_by TEXT`,\n `ALTER TABLE mcp_connect_tokens ADD COLUMN created_by TEXT`,\n ],\n ]) {\n try {\n await client.execute(withIfNotExists);\n } catch {\n // SQLite doesn't support \"ADD COLUMN IF NOT EXISTS\" — retry the\n // plain form and swallow \"duplicate column\" when it already exists.\n try {\n await client.execute(plain);\n } catch {\n // Column already exists (or was created by CREATE TABLE above).\n }\n }\n }\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_device_codes (\n device_code TEXT PRIMARY KEY,\n user_code TEXT NOT NULL,\n owner_email TEXT,\n org_id TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n token_jti TEXT,\n created_at ${intType()},\n expires_at ${intType()},\n consumed_at ${intType()}\n )\n `);\n })().catch((err) => {\n // Don't cache a rejected init. A transient DB blip should let the next\n // connect/mint/revoke call retry rather than wedging the process.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// ---------------------------------------------------------------------------\n// Minted-token records\n// ---------------------------------------------------------------------------\n\nexport interface MintedTokenRow {\n id: string;\n jti: string;\n ownerEmail: string;\n orgId: string | null;\n label: string | null;\n createdAt: number | null;\n lastUsedAt: number | null;\n revokedAt: number | null;\n /** `'personal'` (default) or `'service'` for org service tokens. */\n kind: \"personal\" | \"service\";\n /** Human-readable service principal name, e.g. `\"ci\"`. Only set when `kind === 'service'`. */\n serviceName: string | null;\n /** Email of the human who minted a service token. Only set when `kind === 'service'`. */\n createdBy: string | null;\n}\n\n/**\n * Synthetic identity for an org service token: `svc-<name>@service.<orgId>`.\n * It is email-shaped so the entire existing identity plumbing (JWT `sub`,\n * `runWithRequestContext({ userEmail })`, ownable-row `owner_email` columns,\n * display surfaces that render an email) works unchanged, while remaining\n * clearly distinguishable from a human account. Ownable rows created under\n * this identity carry the org's `orgId`, so org members can see them.\n */\nexport function serviceIdentityEmail(\n serviceName: string,\n orgId: string,\n): string {\n return `svc-${normalizeServiceName(serviceName)}@service.${orgId}`;\n}\n\n/** True when an email is a synthetic org-service-token identity. */\nexport function isServiceIdentityEmail(email: string | undefined): boolean {\n return !!email && /^svc-[a-z0-9-]+@service\\./.test(email);\n}\n\n/**\n * Normalize a user-supplied service name to a DNS-label-ish slug so the\n * synthetic identity stays a valid email local part: lowercase, `a-z0-9-`,\n * max 48 chars. Throws on names that normalize to nothing.\n */\nexport function normalizeServiceName(raw: string): string {\n const slug = (raw ?? \"\")\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 48);\n if (!slug) {\n throw new Error(\"Service name must contain at least one letter or number.\");\n }\n return slug;\n}\n\n/**\n * Persist a record of a minted token. The token value itself (a signed JWT)\n * is NEVER stored — only its `jti`, so revocation is a cheap SQL lookup.\n */\nexport async function recordMintedToken(params: {\n jti: string;\n ownerEmail: string;\n orgId?: string | null;\n label?: string | null;\n /** Defaults to `'personal'`. Pass `'service'` for org service tokens. */\n kind?: \"personal\" | \"service\";\n /** Service principal name — required semantics when kind === 'service'. */\n serviceName?: string | null;\n /** The human who minted a service token (audit trail). */\n createdBy?: string | null;\n}): Promise<string> {\n await ensureTable();\n const client = getDbExec();\n const id = randomUUID();\n await client.execute({\n sql: `INSERT INTO mcp_connect_tokens (id, jti, owner_email, org_id, label, kind, service_name, created_by, created_at, last_used_at, revoked_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n params.jti,\n params.ownerEmail,\n params.orgId ?? null,\n params.label ?? null,\n params.kind ?? \"personal\",\n params.serviceName ?? null,\n params.createdBy ?? null,\n Date.now(),\n null,\n null,\n ],\n });\n return id;\n}\n\n/**\n * Returns true when the given `jti` corresponds to a token that has been\n * revoked. Fails OPEN on a store/DB error: a transient Neon WS drop must not\n * lock every connected agent out. Signature verification is unaffected — this\n * is only the post-verify revoke check (see `verifyAuth` in build-server.ts).\n */\nexport async function isJtiRevoked(jti: string): Promise<boolean> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT revoked_at FROM mcp_connect_tokens WHERE jti = ?`,\n args: [jti],\n });\n if (rows.length === 0) return false;\n const revokedAt = rows[0].revoked_at ?? rows[0].revokedAt;\n return revokedAt != null;\n } catch (err) {\n // Fail open: a DB blip must not turn every minted token into a 401.\n // (Signature checks already passed; this only gates explicit revokes.)\n if (isConnectionError(err)) return false;\n return false;\n }\n}\n\nfunction mapTokenRow(r: any): MintedTokenRow {\n return {\n id: r.id as string,\n jti: r.jti as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail) as string,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n label: (r.label ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n lastUsedAt: numOrNull(r.last_used_at ?? r.lastUsedAt),\n revokedAt: numOrNull(r.revoked_at ?? r.revokedAt),\n kind: r.kind === \"service\" ? \"service\" : \"personal\",\n serviceName: (r.service_name ?? r.serviceName ?? null) as string | null,\n createdBy: (r.created_by ?? r.createdBy ?? null) as string | null,\n };\n}\n\nexport async function listTokens(\n ownerEmail: string,\n): Promise<MintedTokenRow[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, jti, owner_email, org_id, label, kind, service_name, created_by, created_at, last_used_at, revoked_at FROM mcp_connect_tokens WHERE owner_email = ? ORDER BY created_at DESC`,\n args: [ownerEmail],\n });\n return rows.map(mapTokenRow);\n } catch (err) {\n if (isConnectionError(err)) return [];\n throw err;\n }\n}\n\n/**\n * List the org's service tokens (kind = 'service'), newest first. Scoped by\n * `org_id` — callers must already have established the caller is a member of\n * `orgId` (the actions in `mcp/actions/` gate on org role).\n */\nexport async function listOrgServiceTokens(\n orgId: string,\n): Promise<MintedTokenRow[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, jti, owner_email, org_id, label, kind, service_name, created_by, created_at, last_used_at, revoked_at FROM mcp_connect_tokens WHERE org_id = ? AND kind = 'service' ORDER BY created_at DESC`,\n args: [orgId],\n });\n return rows.map(mapTokenRow);\n } catch (err) {\n if (isConnectionError(err)) return [];\n throw err;\n }\n}\n\n/**\n * Revoke an org service token by id, scoped to `orgId` AND `kind = 'service'`\n * so a caller can never revoke another org's token (or someone's personal\n * token) through this path. Uses the same `revoked_at` gate `isJtiRevoked`\n * checks, so revocation takes effect on the next request like personal\n * tokens. Idempotent; returns true when a row actually transitioned.\n */\nexport async function revokeOrgServiceToken(\n orgId: string,\n id: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_connect_tokens SET revoked_at = ? WHERE id = ? AND org_id = ? AND kind = 'service' AND revoked_at IS NULL`,\n args: [Date.now(), id, orgId],\n });\n return result.rowsAffected > 0;\n}\n\n/**\n * Revoke a token, but ONLY if it is owned by `ownerEmail` (the caller). The\n * `owner_email = ?` predicate is the access scope — a caller can never revoke\n * another user's token. Idempotent: re-revoking keeps the first timestamp.\n * Returns true when a row was actually transitioned to revoked.\n */\nexport async function revokeToken(\n ownerEmail: string,\n id: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_connect_tokens SET revoked_at = ? WHERE id = ? AND owner_email = ? AND revoked_at IS NULL`,\n args: [Date.now(), id, ownerEmail],\n });\n return result.rowsAffected > 0;\n}\n\n/**\n * Best-effort: stamp `last_used_at` for a token. Swallows all errors — this is\n * pure telemetry and must never affect the auth path.\n */\nexport async function touchTokenUsed(jti: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_connect_tokens SET last_used_at = ? WHERE jti = ?`,\n args: [Date.now(), jti],\n });\n } catch {\n // last_used_at is informational only — never throw from the hot path.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Device-code flow (OAuth 2.0 device-authorization style)\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeRow {\n deviceCode: string;\n userCode: string;\n ownerEmail: string | null;\n orgId: string | null;\n status: \"pending\" | \"approved\" | \"minting\" | \"consumed\" | \"expired\";\n tokenJti: string | null;\n createdAt: number | null;\n expiresAt: number | null;\n consumedAt: number | null;\n}\n\nconst USER_CODE_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"; // Crockford-ish base32, no 0/1/O/I\n\n/** Crypto-random short human-typable code, formatted `XXXX-XXXX`. */\nfunction generateUserCode(): string {\n const bytes = randomBytes(8);\n let out = \"\";\n for (let i = 0; i < 8; i++) {\n out += USER_CODE_ALPHABET[bytes[i] % USER_CODE_ALPHABET.length];\n if (i === 3) out += \"-\";\n }\n return out;\n}\n\nfunction generateDeviceCode(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/**\n * Create a new device+user code pair. Rate-limited: at most\n * `DEVICE_START_MAX` codes within `DEVICE_START_WINDOW_MS`. The window count\n * is a coarse global cap (this endpoint is unauthenticated) — enough to stop\n * table flooding / user-code brute force without per-IP plumbing.\n *\n * Throws `RATE_LIMITED` when the cap is exceeded so the route can map it to a\n * 429.\n */\nexport async function createDeviceCode(): Promise<DeviceCodeRow> {\n await ensureTable();\n const client = getDbExec();\n\n const now = Date.now();\n try {\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) AS n FROM mcp_device_codes WHERE created_at > ?`,\n args: [now - DEVICE_START_WINDOW_MS],\n });\n const n = Number(rows[0]?.n ?? rows[0]?.[\"COUNT(*)\"] ?? 0);\n if (Number.isFinite(n) && n >= DEVICE_START_MAX) {\n throw new Error(\"RATE_LIMITED\");\n }\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") throw err;\n // A read failure here should not block legitimate device starts — the\n // single-use + short-TTL design is the primary protection. Continue.\n }\n\n const deviceCode = generateDeviceCode();\n const userCode = generateUserCode();\n const expiresAt = now + DEVICE_CODE_TTL_MS;\n await client.execute({\n sql: `INSERT INTO mcp_device_codes (device_code, user_code, owner_email, org_id, status, token_jti, created_at, expires_at, consumed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n deviceCode,\n userCode,\n null,\n null,\n \"pending\",\n null,\n now,\n expiresAt,\n null,\n ],\n });\n return {\n deviceCode,\n userCode,\n ownerEmail: null,\n orgId: null,\n status: \"pending\",\n tokenJti: null,\n createdAt: now,\n expiresAt,\n consumedAt: null,\n };\n}\n\nfunction mapDeviceRow(r: any): DeviceCodeRow {\n return {\n deviceCode: (r.device_code ?? r.deviceCode) as string,\n userCode: (r.user_code ?? r.userCode) as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail ?? null) as string | null,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n status: (r.status ?? \"pending\") as DeviceCodeRow[\"status\"],\n tokenJti: (r.token_jti ?? r.tokenJti ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n expiresAt: numOrNull(r.expires_at ?? r.expiresAt),\n consumedAt: numOrNull(r.consumed_at ?? r.consumedAt),\n };\n}\n\nexport async function getDeviceCode(\n deviceCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE device_code = ?`,\n args: [deviceCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\nasync function getDeviceCodeByUserCode(\n userCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE user_code = ?`,\n args: [userCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\n/**\n * Bind the logged-in user (email + org) to a pending device code, identified\n * by its human-typable `user_code`. Only transitions a non-expired, still\n * `pending` row. Returns the bound row, or a string error code:\n * - `not_found` — no such user_code\n * - `expired` — past its TTL\n * - `already` — already approved/consumed (not re-bindable)\n */\nexport async function approveDeviceCode(\n userCode: string,\n ownerEmail: string,\n orgId: string | null,\n): Promise<DeviceCodeRow | \"not_found\" | \"expired\" | \"already\"> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCodeByUserCode(userCode);\n if (!row) return \"not_found\";\n if ((row.expiresAt ?? 0) < Date.now()) return \"expired\";\n if (row.status !== \"pending\") return \"already\";\n\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', owner_email = ?, org_id = ? WHERE user_code = ? AND status = 'pending'`,\n args: [ownerEmail, orgId, userCode],\n });\n if (result.rowsAffected === 0) {\n // Lost a race with another approve — re-read to report the real state.\n const fresh = await getDeviceCodeByUserCode(userCode);\n return fresh && fresh.status !== \"pending\" ? \"already\" : \"not_found\";\n }\n return {\n ...row,\n status: \"approved\",\n ownerEmail,\n orgId,\n };\n}\n\n/**\n * Atomically transition an approved device code to consumed and stamp the\n * minted token's jti. Single-use: only succeeds when the row is currently\n * `approved` (not already consumed). Returns the pre-consume row on success,\n * or null when it could not be consumed (already consumed / not approved /\n * gone). The caller mints the token only after this returns a row.\n */\nexport async function consumeDeviceCode(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row) return null;\n if (row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null; // lost the single-use race\n return row;\n}\n\n/**\n * Claim an approved device code for token minting without making it terminal.\n * If signing or token recording fails, callers release this back to approved\n * so the CLI can retry the poll instead of being stuck at \"consumed\".\n */\nexport async function claimDeviceCodeForMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row || row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'minting', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null;\n return row;\n}\n\nexport async function finishDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed' WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n return result.rowsAffected > 0;\n}\n\nexport async function releaseDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', token_jti = NULL, consumed_at = NULL WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n } catch {\n // The next poll will keep returning pending for a minting row until a\n // later cleanup/retry path can observe or repair it. Do not throw here.\n }\n}\n\n/**\n * Best-effort: flip an expired, still-pending/approved row to `expired` so\n * the poll endpoint can report a clean terminal state. Swallows errors.\n */\nexport async function expireDeviceCode(deviceCode: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'expired' WHERE device_code = ? AND status IN ('pending','approved')`,\n args: [deviceCode],\n });\n } catch {\n // The poll handler already treats past-TTL rows as expired regardless of\n // whether this housekeeping write lands.\n }\n}\n\nfunction numOrNull(v: unknown): number | null {\n if (v == null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n"]}
1
+ {"version":3,"file":"connect-store.js","sourceRoot":"","sources":["../../src/mcp/connect-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Framework-table store for the \"connect external agents\" feature.\n *\n * Two additive, dialect-agnostic tables back the browser **Connect** page and\n * the OAuth-style **device-code flow** a CLI drives:\n *\n * - `mcp_connect_tokens` — one row per minted MCP token. We never store the\n * token value (it's a signed JWT); only its `jti` so revocation is a\n * SQL lookup. Revoking sets `revoked_at`; the row is never deleted.\n * - `mcp_device_codes` — short-lived (10 min) device/user code pairs for\n * the OAuth 2.0 device-authorization-style CLI flow. Single-use\n * (`consumed_at`), rate-limited at creation.\n *\n * Mirrors `application-state/store.ts`: lazy `ensureTable()`, `getDbExec()`,\n * `isConnectionError()` swallow so a transient Neon WS drop never 500s.\n * `CREATE TABLE IF NOT EXISTS` only — strictly additive, never DROP / ALTER\n * (shared prod DB rule).\n */\n\nimport { randomBytes, randomUUID } from \"node:crypto\";\n\nimport {\n getDbExec,\n isConnectionError,\n intType,\n isPostgres,\n} from \"../db/client.js\";\nimport { ensureTableExists, ensureColumnExists } from \"../db/ddl-guard.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Scope claim that marks a connect-minted token (vs. an ordinary A2A\n * delegation JWT). Only tokens carrying this scope go through the revoke\n * lookup in `verifyAuth` — defined here so both `connect-route.ts` and\n * `build-server.ts` import it from the leaf store without a cycle.\n */\nexport const MCP_CONNECT_SCOPE = \"mcp-connect\";\n\n/**\n * Client id used when connect/device flows have to mint a standard MCP OAuth\n * access token instead of an A2A JWT (for deployments without A2A_SECRET).\n */\nexport const MCP_CONNECT_OAUTH_CLIENT_ID = \"agent-native-connect\";\n\n/** Device codes are valid for 10 minutes. */\nexport const DEVICE_CODE_TTL_MS = 10 * 60_000;\n\n/** Default minted-token lifetime. Configurable per-request 1–365 days. */\nexport const DEFAULT_TOKEN_TTL_DAYS = 365;\nexport const MIN_TOKEN_TTL_DAYS = 1;\nexport const MAX_TOKEN_TTL_DAYS = 365;\n\n/**\n * Rate limit for `device/start`: at most this many device codes may be created\n * within `DEVICE_START_WINDOW_MS`. Unauthenticated endpoint — keep it tight so\n * a hostile client can't flood the table or brute-force user codes.\n */\nexport const DEVICE_START_MAX = 20;\nexport const DEVICE_START_WINDOW_MS = 60_000;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // Additive only. Never DROP / ALTER — this DB is shared across every\n // deploy context (preview/branch/prod) for hosted templates.\n const createTokensSql = `\n CREATE TABLE IF NOT EXISTS mcp_connect_tokens (\n id TEXT PRIMARY KEY,\n jti TEXT UNIQUE NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n label TEXT,\n kind TEXT NOT NULL DEFAULT 'personal',\n service_name TEXT,\n created_by TEXT,\n created_at ${intType()},\n last_used_at ${intType()},\n revoked_at ${intType()}\n )\n `;\n const createDeviceCodesSql = `\n CREATE TABLE IF NOT EXISTS mcp_device_codes (\n device_code TEXT PRIMARY KEY,\n user_code TEXT NOT NULL,\n owner_email TEXT,\n org_id TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n token_jti TEXT,\n created_at ${intType()},\n expires_at ${intType()},\n consumed_at ${intType()}\n )\n `;\n\n if (isPostgres()) {\n // PG-guard: probe information_schema first (no lock) and only issue\n // DDL when the table/column is actually missing, wrapped in a\n // transaction-scoped lock_timeout so a contended lock fails fast.\n await ensureTableExists(\"mcp_connect_tokens\", createTokensSql);\n // Additive columns for org service tokens — added after initial\n // deployment; ensureColumnExists probes before ALTERing.\n await ensureColumnExists(\n \"mcp_connect_tokens\",\n \"kind\",\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'personal'`,\n );\n await ensureColumnExists(\n \"mcp_connect_tokens\",\n \"service_name\",\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS service_name TEXT`,\n );\n await ensureColumnExists(\n \"mcp_connect_tokens\",\n \"created_by\",\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS created_by TEXT`,\n );\n await ensureTableExists(\"mcp_device_codes\", createDeviceCodesSql);\n return;\n }\n\n // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing\n // create-then-additive-alter behaviour.\n await client.execute(createTokensSql);\n // Additive columns for org service tokens (deployments that created the\n // table before these columns existed; fresh DBs get them via the CREATE\n // TABLE above). kind='personal' (default) preserves the original\n // per-user token; kind='service' marks tokens minted for an org service\n // principal (e.g. CI) rather than a person. service_name is the\n // human-readable service label (e.g. \"ci\"); created_by records the\n // human who minted it, for audit.\n for (const [withIfNotExists, plain] of [\n [\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'personal'`,\n `ALTER TABLE mcp_connect_tokens ADD COLUMN kind TEXT NOT NULL DEFAULT 'personal'`,\n ],\n [\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS service_name TEXT`,\n `ALTER TABLE mcp_connect_tokens ADD COLUMN service_name TEXT`,\n ],\n [\n `ALTER TABLE mcp_connect_tokens ADD COLUMN IF NOT EXISTS created_by TEXT`,\n `ALTER TABLE mcp_connect_tokens ADD COLUMN created_by TEXT`,\n ],\n ]) {\n try {\n await client.execute(withIfNotExists);\n } catch {\n // SQLite doesn't support \"ADD COLUMN IF NOT EXISTS\" — retry the\n // plain form and swallow \"duplicate column\" when it already exists.\n try {\n await client.execute(plain);\n } catch {\n // Column already exists (or was created by CREATE TABLE above).\n }\n }\n }\n await client.execute(createDeviceCodesSql);\n })().catch((err) => {\n // Don't cache a rejected init. A transient DB blip should let the next\n // connect/mint/revoke call retry rather than wedging the process.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// ---------------------------------------------------------------------------\n// Minted-token records\n// ---------------------------------------------------------------------------\n\nexport interface MintedTokenRow {\n id: string;\n jti: string;\n ownerEmail: string;\n orgId: string | null;\n label: string | null;\n createdAt: number | null;\n lastUsedAt: number | null;\n revokedAt: number | null;\n /** `'personal'` (default) or `'service'` for org service tokens. */\n kind: \"personal\" | \"service\";\n /** Human-readable service principal name, e.g. `\"ci\"`. Only set when `kind === 'service'`. */\n serviceName: string | null;\n /** Email of the human who minted a service token. Only set when `kind === 'service'`. */\n createdBy: string | null;\n}\n\n/**\n * Synthetic identity for an org service token: `svc-<name>@service.<orgId>`.\n * It is email-shaped so the entire existing identity plumbing (JWT `sub`,\n * `runWithRequestContext({ userEmail })`, ownable-row `owner_email` columns,\n * display surfaces that render an email) works unchanged, while remaining\n * clearly distinguishable from a human account. Ownable rows created under\n * this identity carry the org's `orgId`, so org members can see them.\n */\nexport function serviceIdentityEmail(\n serviceName: string,\n orgId: string,\n): string {\n return `svc-${normalizeServiceName(serviceName)}@service.${orgId}`;\n}\n\n/** True when an email is a synthetic org-service-token identity. */\nexport function isServiceIdentityEmail(email: string | undefined): boolean {\n return !!email && /^svc-[a-z0-9-]+@service\\./.test(email);\n}\n\n/**\n * Normalize a user-supplied service name to a DNS-label-ish slug so the\n * synthetic identity stays a valid email local part: lowercase, `a-z0-9-`,\n * max 48 chars. Throws on names that normalize to nothing.\n */\nexport function normalizeServiceName(raw: string): string {\n const slug = (raw ?? \"\")\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n .slice(0, 48);\n if (!slug) {\n throw new Error(\"Service name must contain at least one letter or number.\");\n }\n return slug;\n}\n\n/**\n * Persist a record of a minted token. The token value itself (a signed JWT)\n * is NEVER stored — only its `jti`, so revocation is a cheap SQL lookup.\n */\nexport async function recordMintedToken(params: {\n jti: string;\n ownerEmail: string;\n orgId?: string | null;\n label?: string | null;\n /** Defaults to `'personal'`. Pass `'service'` for org service tokens. */\n kind?: \"personal\" | \"service\";\n /** Service principal name — required semantics when kind === 'service'. */\n serviceName?: string | null;\n /** The human who minted a service token (audit trail). */\n createdBy?: string | null;\n}): Promise<string> {\n await ensureTable();\n const client = getDbExec();\n const id = randomUUID();\n await client.execute({\n sql: `INSERT INTO mcp_connect_tokens (id, jti, owner_email, org_id, label, kind, service_name, created_by, created_at, last_used_at, revoked_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n params.jti,\n params.ownerEmail,\n params.orgId ?? null,\n params.label ?? null,\n params.kind ?? \"personal\",\n params.serviceName ?? null,\n params.createdBy ?? null,\n Date.now(),\n null,\n null,\n ],\n });\n return id;\n}\n\n/**\n * Returns true when the given `jti` corresponds to a token that has been\n * revoked. Fails OPEN on a store/DB error: a transient Neon WS drop must not\n * lock every connected agent out. Signature verification is unaffected — this\n * is only the post-verify revoke check (see `verifyAuth` in build-server.ts).\n */\nexport async function isJtiRevoked(jti: string): Promise<boolean> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT revoked_at FROM mcp_connect_tokens WHERE jti = ?`,\n args: [jti],\n });\n if (rows.length === 0) return false;\n const revokedAt = rows[0].revoked_at ?? rows[0].revokedAt;\n return revokedAt != null;\n } catch (err) {\n // Fail open: a DB blip must not turn every minted token into a 401.\n // (Signature checks already passed; this only gates explicit revokes.)\n if (isConnectionError(err)) return false;\n return false;\n }\n}\n\nfunction mapTokenRow(r: any): MintedTokenRow {\n return {\n id: r.id as string,\n jti: r.jti as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail) as string,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n label: (r.label ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n lastUsedAt: numOrNull(r.last_used_at ?? r.lastUsedAt),\n revokedAt: numOrNull(r.revoked_at ?? r.revokedAt),\n kind: r.kind === \"service\" ? \"service\" : \"personal\",\n serviceName: (r.service_name ?? r.serviceName ?? null) as string | null,\n createdBy: (r.created_by ?? r.createdBy ?? null) as string | null,\n };\n}\n\nexport async function listTokens(\n ownerEmail: string,\n): Promise<MintedTokenRow[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, jti, owner_email, org_id, label, kind, service_name, created_by, created_at, last_used_at, revoked_at FROM mcp_connect_tokens WHERE owner_email = ? ORDER BY created_at DESC`,\n args: [ownerEmail],\n });\n return rows.map(mapTokenRow);\n } catch (err) {\n if (isConnectionError(err)) return [];\n throw err;\n }\n}\n\n/**\n * List the org's service tokens (kind = 'service'), newest first. Scoped by\n * `org_id` — callers must already have established the caller is a member of\n * `orgId` (the actions in `mcp/actions/` gate on org role).\n */\nexport async function listOrgServiceTokens(\n orgId: string,\n): Promise<MintedTokenRow[]> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, jti, owner_email, org_id, label, kind, service_name, created_by, created_at, last_used_at, revoked_at FROM mcp_connect_tokens WHERE org_id = ? AND kind = 'service' ORDER BY created_at DESC`,\n args: [orgId],\n });\n return rows.map(mapTokenRow);\n } catch (err) {\n if (isConnectionError(err)) return [];\n throw err;\n }\n}\n\n/**\n * Revoke an org service token by id, scoped to `orgId` AND `kind = 'service'`\n * so a caller can never revoke another org's token (or someone's personal\n * token) through this path. Uses the same `revoked_at` gate `isJtiRevoked`\n * checks, so revocation takes effect on the next request like personal\n * tokens. Idempotent; returns true when a row actually transitioned.\n */\nexport async function revokeOrgServiceToken(\n orgId: string,\n id: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_connect_tokens SET revoked_at = ? WHERE id = ? AND org_id = ? AND kind = 'service' AND revoked_at IS NULL`,\n args: [Date.now(), id, orgId],\n });\n return result.rowsAffected > 0;\n}\n\n/**\n * Revoke a token, but ONLY if it is owned by `ownerEmail` (the caller). The\n * `owner_email = ?` predicate is the access scope — a caller can never revoke\n * another user's token. Idempotent: re-revoking keeps the first timestamp.\n * Returns true when a row was actually transitioned to revoked.\n */\nexport async function revokeToken(\n ownerEmail: string,\n id: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_connect_tokens SET revoked_at = ? WHERE id = ? AND owner_email = ? AND revoked_at IS NULL`,\n args: [Date.now(), id, ownerEmail],\n });\n return result.rowsAffected > 0;\n}\n\n/**\n * Best-effort: stamp `last_used_at` for a token. Swallows all errors — this is\n * pure telemetry and must never affect the auth path.\n */\nexport async function touchTokenUsed(jti: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_connect_tokens SET last_used_at = ? WHERE jti = ?`,\n args: [Date.now(), jti],\n });\n } catch {\n // last_used_at is informational only — never throw from the hot path.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Device-code flow (OAuth 2.0 device-authorization style)\n// ---------------------------------------------------------------------------\n\nexport interface DeviceCodeRow {\n deviceCode: string;\n userCode: string;\n ownerEmail: string | null;\n orgId: string | null;\n status: \"pending\" | \"approved\" | \"minting\" | \"consumed\" | \"expired\";\n tokenJti: string | null;\n createdAt: number | null;\n expiresAt: number | null;\n consumedAt: number | null;\n}\n\nconst USER_CODE_ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"; // Crockford-ish base32, no 0/1/O/I\n\n/** Crypto-random short human-typable code, formatted `XXXX-XXXX`. */\nfunction generateUserCode(): string {\n const bytes = randomBytes(8);\n let out = \"\";\n for (let i = 0; i < 8; i++) {\n out += USER_CODE_ALPHABET[bytes[i] % USER_CODE_ALPHABET.length];\n if (i === 3) out += \"-\";\n }\n return out;\n}\n\nfunction generateDeviceCode(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/**\n * Create a new device+user code pair. Rate-limited: at most\n * `DEVICE_START_MAX` codes within `DEVICE_START_WINDOW_MS`. The window count\n * is a coarse global cap (this endpoint is unauthenticated) — enough to stop\n * table flooding / user-code brute force without per-IP plumbing.\n *\n * Throws `RATE_LIMITED` when the cap is exceeded so the route can map it to a\n * 429.\n */\nexport async function createDeviceCode(): Promise<DeviceCodeRow> {\n await ensureTable();\n const client = getDbExec();\n\n const now = Date.now();\n try {\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) AS n FROM mcp_device_codes WHERE created_at > ?`,\n args: [now - DEVICE_START_WINDOW_MS],\n });\n const n = Number(rows[0]?.n ?? rows[0]?.[\"COUNT(*)\"] ?? 0);\n if (Number.isFinite(n) && n >= DEVICE_START_MAX) {\n throw new Error(\"RATE_LIMITED\");\n }\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") throw err;\n // A read failure here should not block legitimate device starts — the\n // single-use + short-TTL design is the primary protection. Continue.\n }\n\n const deviceCode = generateDeviceCode();\n const userCode = generateUserCode();\n const expiresAt = now + DEVICE_CODE_TTL_MS;\n await client.execute({\n sql: `INSERT INTO mcp_device_codes (device_code, user_code, owner_email, org_id, status, token_jti, created_at, expires_at, consumed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n deviceCode,\n userCode,\n null,\n null,\n \"pending\",\n null,\n now,\n expiresAt,\n null,\n ],\n });\n return {\n deviceCode,\n userCode,\n ownerEmail: null,\n orgId: null,\n status: \"pending\",\n tokenJti: null,\n createdAt: now,\n expiresAt,\n consumedAt: null,\n };\n}\n\nfunction mapDeviceRow(r: any): DeviceCodeRow {\n return {\n deviceCode: (r.device_code ?? r.deviceCode) as string,\n userCode: (r.user_code ?? r.userCode) as string,\n ownerEmail: (r.owner_email ?? r.ownerEmail ?? null) as string | null,\n orgId: (r.org_id ?? r.orgId ?? null) as string | null,\n status: (r.status ?? \"pending\") as DeviceCodeRow[\"status\"],\n tokenJti: (r.token_jti ?? r.tokenJti ?? null) as string | null,\n createdAt: numOrNull(r.created_at ?? r.createdAt),\n expiresAt: numOrNull(r.expires_at ?? r.expiresAt),\n consumedAt: numOrNull(r.consumed_at ?? r.consumedAt),\n };\n}\n\nexport async function getDeviceCode(\n deviceCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE device_code = ?`,\n args: [deviceCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\nasync function getDeviceCodeByUserCode(\n userCode: string,\n): Promise<DeviceCodeRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_device_codes WHERE user_code = ?`,\n args: [userCode],\n });\n if (rows.length === 0) return null;\n return mapDeviceRow(rows[0]);\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\n/**\n * Bind the logged-in user (email + org) to a pending device code, identified\n * by its human-typable `user_code`. Only transitions a non-expired, still\n * `pending` row. Returns the bound row, or a string error code:\n * - `not_found` — no such user_code\n * - `expired` — past its TTL\n * - `already` — already approved/consumed (not re-bindable)\n */\nexport async function approveDeviceCode(\n userCode: string,\n ownerEmail: string,\n orgId: string | null,\n): Promise<DeviceCodeRow | \"not_found\" | \"expired\" | \"already\"> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCodeByUserCode(userCode);\n if (!row) return \"not_found\";\n if ((row.expiresAt ?? 0) < Date.now()) return \"expired\";\n if (row.status !== \"pending\") return \"already\";\n\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', owner_email = ?, org_id = ? WHERE user_code = ? AND status = 'pending'`,\n args: [ownerEmail, orgId, userCode],\n });\n if (result.rowsAffected === 0) {\n // Lost a race with another approve — re-read to report the real state.\n const fresh = await getDeviceCodeByUserCode(userCode);\n return fresh && fresh.status !== \"pending\" ? \"already\" : \"not_found\";\n }\n return {\n ...row,\n status: \"approved\",\n ownerEmail,\n orgId,\n };\n}\n\n/**\n * Atomically transition an approved device code to consumed and stamp the\n * minted token's jti. Single-use: only succeeds when the row is currently\n * `approved` (not already consumed). Returns the pre-consume row on success,\n * or null when it could not be consumed (already consumed / not approved /\n * gone). The caller mints the token only after this returns a row.\n */\nexport async function consumeDeviceCode(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row) return null;\n if (row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null; // lost the single-use race\n return row;\n}\n\n/**\n * Claim an approved device code for token minting without making it terminal.\n * If signing or token recording fails, callers release this back to approved\n * so the CLI can retry the poll instead of being stuck at \"consumed\".\n */\nexport async function claimDeviceCodeForMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<DeviceCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const row = await getDeviceCode(deviceCode);\n if (!row || row.status !== \"approved\") return null;\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'minting', token_jti = ?, consumed_at = ? WHERE device_code = ? AND status = 'approved'`,\n args: [tokenJti, Date.now(), deviceCode],\n });\n if (result.rowsAffected === 0) return null;\n return row;\n}\n\nexport async function finishDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'consumed' WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n return result.rowsAffected > 0;\n}\n\nexport async function releaseDeviceCodeMint(\n deviceCode: string,\n tokenJti: string,\n): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'approved', token_jti = NULL, consumed_at = NULL WHERE device_code = ? AND status = 'minting' AND token_jti = ?`,\n args: [deviceCode, tokenJti],\n });\n } catch {\n // The next poll will keep returning pending for a minting row until a\n // later cleanup/retry path can observe or repair it. Do not throw here.\n }\n}\n\n/**\n * Best-effort: flip an expired, still-pending/approved row to `expired` so\n * the poll endpoint can report a clean terminal state. Swallows errors.\n */\nexport async function expireDeviceCode(deviceCode: string): Promise<void> {\n try {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE mcp_device_codes SET status = 'expired' WHERE device_code = ? AND status IN ('pending','approved')`,\n args: [deviceCode],\n });\n } catch {\n // The poll handler already treats past-TTL rows as expired regardless of\n // whether this housekeeping write lands.\n }\n}\n\nfunction numOrNull(v: unknown): number | null {\n if (v == null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-store.d.ts","sourceRoot":"","sources":["../../src/mcp/oauth-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"oauth-store.d.ts","sourceRoot":"","sources":["../../src/mcp/oauth-store.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,8 @@
6
6
  * are signed JWTs and are never persisted.
7
7
  */
8
8
  import { randomBytes, randomUUID, createHash } from "node:crypto";
9
- import { getDbExec, isConnectionError, intType } from "../db/client.js";
9
+ import { getDbExec, isConnectionError, intType, isPostgres, } from "../db/client.js";
10
+ import { ensureTableExists } from "../db/ddl-guard.js";
10
11
  let _initPromise;
11
12
  export const MCP_OAUTH_CODE_TTL_MS = 10 * 60_000;
12
13
  /**
@@ -67,7 +68,7 @@ async function ensureTable() {
67
68
  if (!_initPromise) {
68
69
  _initPromise = (async () => {
69
70
  const client = getDbExec();
70
- await client.execute(`
71
+ const createClientsSql = `
71
72
  CREATE TABLE IF NOT EXISTS mcp_oauth_clients (
72
73
  client_id TEXT PRIMARY KEY,
73
74
  client_name TEXT,
@@ -77,8 +78,8 @@ async function ensureTable() {
77
78
  token_endpoint_auth_method TEXT,
78
79
  created_at ${intType()}
79
80
  )
80
- `);
81
- await client.execute(`
81
+ `;
82
+ const createCodesSql = `
82
83
  CREATE TABLE IF NOT EXISTS mcp_oauth_codes (
83
84
  code TEXT PRIMARY KEY,
84
85
  client_id TEXT NOT NULL,
@@ -94,8 +95,8 @@ async function ensureTable() {
94
95
  expires_at ${intType()},
95
96
  consumed_at ${intType()}
96
97
  )
97
- `);
98
- await client.execute(`
98
+ `;
99
+ const createRefreshTokensSql = `
99
100
  CREATE TABLE IF NOT EXISTS mcp_oauth_refresh_tokens (
100
101
  id TEXT PRIMARY KEY,
101
102
  token_hash TEXT UNIQUE NOT NULL,
@@ -111,7 +112,21 @@ async function ensureTable() {
111
112
  revoked_at ${intType()},
112
113
  replaced_by_hash TEXT
113
114
  )
114
- `);
115
+ `;
116
+ if (isPostgres()) {
117
+ // PG-guard: probe information_schema first (no lock) and only issue
118
+ // DDL when the table is actually missing, wrapped in a
119
+ // transaction-scoped lock_timeout so a contended lock fails fast.
120
+ await ensureTableExists("mcp_oauth_clients", createClientsSql);
121
+ await ensureTableExists("mcp_oauth_codes", createCodesSql);
122
+ await ensureTableExists("mcp_oauth_refresh_tokens", createRefreshTokensSql);
123
+ return;
124
+ }
125
+ // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
126
+ // create-then-execute behaviour.
127
+ await client.execute(createClientsSql);
128
+ await client.execute(createCodesSql);
129
+ await client.execute(createRefreshTokensSql);
115
130
  })().catch((err) => {
116
131
  _initPromise = undefined;
117
132
  throw err;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-store.js","sourceRoot":"","sources":["../../src/mcp/oauth-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL-backed storage for standard remote MCP OAuth.\n *\n * Additive framework tables only. We store OAuth client registrations,\n * short-lived authorization codes, and hashed refresh tokens. Access tokens\n * are signed JWTs and are never persisted.\n */\n\nimport { randomBytes, randomUUID, createHash } from \"node:crypto\";\n\nimport { getDbExec, isConnectionError, intType } from \"../db/client.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\nexport const MCP_OAUTH_CODE_TTL_MS = 10 * 60_000;\n\n/**\n * Parse a duration string like \"30d\", \"1h\", \"7d\" into seconds.\n * Returns `null` when the input is not a valid recognised pattern.\n */\nfunction parseDurationSeconds(raw: string): number | null {\n const trimmed = raw.trim();\n const match = trimmed.match(/^(\\d+(?:\\.\\d+)?)\\s*([smhd])$/i);\n if (!match) return null;\n const value = parseFloat(match[1]);\n if (!Number.isFinite(value) || value <= 0) return null;\n switch (match[2].toLowerCase()) {\n case \"s\":\n return value;\n case \"m\":\n return value * 60;\n case \"h\":\n return value * 3600;\n case \"d\":\n return value * 86400;\n }\n return null;\n}\n\nconst DEFAULT_ACCESS_TOKEN_TTL = \"30d\";\nconst DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 30 * 86400;\n\nfunction resolveAccessTokenTtl(): { str: string; seconds: number } {\n const env = process.env.MCP_OAUTH_ACCESS_TOKEN_TTL?.trim();\n if (env) {\n const secs = parseDurationSeconds(env);\n if (secs !== null) return { str: env, seconds: secs };\n // Garbage value — fall back to default rather than silently breaking.\n console.warn(\n `[mcp-oauth] Invalid MCP_OAUTH_ACCESS_TOKEN_TTL=\"${env}\", using default \"${DEFAULT_ACCESS_TOKEN_TTL}\"`,\n );\n }\n return {\n str: DEFAULT_ACCESS_TOKEN_TTL,\n seconds: DEFAULT_ACCESS_TOKEN_TTL_SECONDS,\n };\n}\n\nconst _accessTokenTtl = resolveAccessTokenTtl();\n\n/**\n * Access-token TTL as a jose-compatible duration string.\n * Defaults to \"30d\"; override with MCP_OAUTH_ACCESS_TOKEN_TTL env var.\n */\nexport const MCP_OAUTH_ACCESS_TOKEN_TTL: string = _accessTokenTtl.str;\n\n/**\n * Access-token TTL in seconds (derived from the same env var).\n * Used to populate the OAuth `expires_in` response field.\n */\nexport const MCP_OAUTH_ACCESS_TOKEN_TTL_SECONDS: number =\n _accessTokenTtl.seconds;\n\nexport const MCP_OAUTH_REFRESH_TOKEN_TTL_MS = 365 * 24 * 60 * 60_000;\nexport const MCP_OAUTH_REGISTER_MAX = 60;\nexport const MCP_OAUTH_REGISTER_WINDOW_MS = 60_000;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_oauth_clients (\n client_id TEXT PRIMARY KEY,\n client_name TEXT,\n redirect_uris TEXT NOT NULL,\n grant_types TEXT,\n response_types TEXT,\n token_endpoint_auth_method TEXT,\n created_at ${intType()}\n )\n `);\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_oauth_codes (\n code TEXT PRIMARY KEY,\n client_id TEXT NOT NULL,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n code_challenge_method TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n org_domain TEXT,\n scope TEXT NOT NULL,\n resource TEXT NOT NULL,\n created_at ${intType()},\n expires_at ${intType()},\n consumed_at ${intType()}\n )\n `);\n await client.execute(`\n CREATE TABLE IF NOT EXISTS mcp_oauth_refresh_tokens (\n id TEXT PRIMARY KEY,\n token_hash TEXT UNIQUE NOT NULL,\n client_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n org_domain TEXT,\n scope TEXT NOT NULL,\n resource TEXT NOT NULL,\n created_at ${intType()},\n expires_at ${intType()},\n last_used_at ${intType()},\n revoked_at ${intType()},\n replaced_by_hash TEXT\n )\n `);\n })().catch((err) => {\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport interface OAuthClientRow {\n clientId: string;\n clientName: string | null;\n redirectUris: string[];\n grantTypes: string[];\n responseTypes: string[];\n tokenEndpointAuthMethod: string;\n createdAt: number | null;\n}\n\nexport interface OAuthCodeRow {\n code: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n ownerEmail: string;\n orgId: string | null;\n orgDomain: string | null;\n scope: string;\n resource: string;\n createdAt: number | null;\n expiresAt: number | null;\n consumedAt: number | null;\n}\n\nexport interface OAuthRefreshTokenRow {\n id: string;\n tokenHash: string;\n clientId: string;\n ownerEmail: string;\n orgId: string | null;\n orgDomain: string | null;\n scope: string;\n resource: string;\n createdAt: number | null;\n expiresAt: number | null;\n lastUsedAt: number | null;\n revokedAt: number | null;\n replacedByHash: string | null;\n}\n\nexport function generateOpaqueToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport function hashOAuthToken(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"base64url\");\n}\n\nfunction parseJsonStringArray(\n value: unknown,\n fallback: string[] = [],\n): string[] {\n if (typeof value !== \"string\") return fallback;\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((item): item is string => typeof item === \"string\")\n : fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction numOrNull(v: unknown): number | null {\n if (v == null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction mapClientRow(row: any): OAuthClientRow {\n return {\n clientId: row.client_id ?? row.clientId,\n clientName: row.client_name ?? row.clientName ?? null,\n redirectUris: parseJsonStringArray(row.redirect_uris ?? row.redirectUris),\n grantTypes: parseJsonStringArray(row.grant_types ?? row.grantTypes, [\n \"authorization_code\",\n \"refresh_token\",\n ]),\n responseTypes: parseJsonStringArray(\n row.response_types ?? row.responseTypes,\n [\"code\"],\n ),\n tokenEndpointAuthMethod:\n row.token_endpoint_auth_method ?? row.tokenEndpointAuthMethod ?? \"none\",\n createdAt: numOrNull(row.created_at ?? row.createdAt),\n };\n}\n\nfunction mapCodeRow(row: any): OAuthCodeRow {\n return {\n code: row.code,\n clientId: row.client_id ?? row.clientId,\n redirectUri: row.redirect_uri ?? row.redirectUri,\n codeChallenge: row.code_challenge ?? row.codeChallenge,\n codeChallengeMethod: row.code_challenge_method ?? row.codeChallengeMethod,\n ownerEmail: row.owner_email ?? row.ownerEmail,\n orgId: row.org_id ?? row.orgId ?? null,\n orgDomain: row.org_domain ?? row.orgDomain ?? null,\n scope: row.scope,\n resource: row.resource,\n createdAt: numOrNull(row.created_at ?? row.createdAt),\n expiresAt: numOrNull(row.expires_at ?? row.expiresAt),\n consumedAt: numOrNull(row.consumed_at ?? row.consumedAt),\n };\n}\n\nfunction mapRefreshRow(row: any): OAuthRefreshTokenRow {\n return {\n id: row.id,\n tokenHash: row.token_hash ?? row.tokenHash,\n clientId: row.client_id ?? row.clientId,\n ownerEmail: row.owner_email ?? row.ownerEmail,\n orgId: row.org_id ?? row.orgId ?? null,\n orgDomain: row.org_domain ?? row.orgDomain ?? null,\n scope: row.scope,\n resource: row.resource,\n createdAt: numOrNull(row.created_at ?? row.createdAt),\n expiresAt: numOrNull(row.expires_at ?? row.expiresAt),\n lastUsedAt: numOrNull(row.last_used_at ?? row.lastUsedAt),\n revokedAt: numOrNull(row.revoked_at ?? row.revokedAt),\n replacedByHash: row.replaced_by_hash ?? row.replacedByHash ?? null,\n };\n}\n\nexport async function registerOAuthClient(params: {\n clientName?: string | null;\n redirectUris: string[];\n grantTypes?: string[];\n responseTypes?: string[];\n tokenEndpointAuthMethod?: string;\n}): Promise<OAuthClientRow> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n try {\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) AS n FROM mcp_oauth_clients WHERE created_at > ?`,\n args: [now - MCP_OAUTH_REGISTER_WINDOW_MS],\n });\n const n = Number(rows[0]?.n ?? rows[0]?.[\"COUNT(*)\"] ?? 0);\n if (Number.isFinite(n) && n >= MCP_OAUTH_REGISTER_MAX) {\n throw new Error(\"RATE_LIMITED\");\n }\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") throw err;\n // Registration stays possible through transient count-read failures; the\n // exact redirect URI allowlist remains the primary safety gate.\n }\n const clientId = `agent-native-oauth-client-${randomUUID()}`;\n const grantTypes = params.grantTypes?.length\n ? params.grantTypes\n : [\"authorization_code\", \"refresh_token\"];\n const responseTypes = params.responseTypes?.length\n ? params.responseTypes\n : [\"code\"];\n const tokenEndpointAuthMethod = params.tokenEndpointAuthMethod || \"none\";\n await client.execute({\n sql: `INSERT INTO mcp_oauth_clients (client_id, client_name, redirect_uris, grant_types, response_types, token_endpoint_auth_method, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n args: [\n clientId,\n params.clientName ?? null,\n JSON.stringify(params.redirectUris),\n JSON.stringify(grantTypes),\n JSON.stringify(responseTypes),\n tokenEndpointAuthMethod,\n now,\n ],\n });\n return {\n clientId,\n clientName: params.clientName ?? null,\n redirectUris: params.redirectUris,\n grantTypes,\n responseTypes,\n tokenEndpointAuthMethod,\n createdAt: now,\n };\n}\n\nexport async function getOAuthClient(\n clientId: string,\n): Promise<OAuthClientRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_clients WHERE client_id = ?`,\n args: [clientId],\n });\n return rows.length ? mapClientRow(rows[0]) : null;\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\nexport async function createOAuthCode(params: {\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n ownerEmail: string;\n orgId?: string | null;\n orgDomain?: string | null;\n scope: string;\n resource: string;\n}): Promise<OAuthCodeRow> {\n await ensureTable();\n const client = getDbExec();\n const code = generateOpaqueToken();\n const now = Date.now();\n const expiresAt = now + MCP_OAUTH_CODE_TTL_MS;\n await client.execute({\n sql: `INSERT INTO mcp_oauth_codes (code, client_id, redirect_uri, code_challenge, code_challenge_method, owner_email, org_id, org_domain, scope, resource, created_at, expires_at, consumed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n code,\n params.clientId,\n params.redirectUri,\n params.codeChallenge,\n params.codeChallengeMethod,\n params.ownerEmail,\n params.orgId ?? null,\n params.orgDomain ?? null,\n params.scope,\n params.resource,\n now,\n expiresAt,\n null,\n ],\n });\n return {\n code,\n clientId: params.clientId,\n redirectUri: params.redirectUri,\n codeChallenge: params.codeChallenge,\n codeChallengeMethod: params.codeChallengeMethod,\n ownerEmail: params.ownerEmail,\n orgId: params.orgId ?? null,\n orgDomain: params.orgDomain ?? null,\n scope: params.scope,\n resource: params.resource,\n createdAt: now,\n expiresAt,\n consumedAt: null,\n };\n}\n\nexport async function getOAuthCode(code: string): Promise<OAuthCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_codes WHERE code = ?`,\n args: [code],\n });\n if (rows.length === 0) return null;\n const row = mapCodeRow(rows[0]);\n if (row.consumedAt != null || (row.expiresAt ?? 0) < Date.now()) {\n return null;\n }\n return row;\n}\n\nexport async function consumeOAuthCode(\n code: string,\n): Promise<OAuthCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_codes WHERE code = ?`,\n args: [code],\n });\n if (rows.length === 0) return null;\n const row = mapCodeRow(rows[0]);\n if (row.consumedAt != null || (row.expiresAt ?? 0) < Date.now()) {\n return null;\n }\n const result = await client.execute({\n sql: `UPDATE mcp_oauth_codes SET consumed_at = ? WHERE code = ? AND consumed_at IS NULL`,\n args: [Date.now(), code],\n });\n return result.rowsAffected > 0 ? row : null;\n}\n\nexport async function createOAuthRefreshToken(params: {\n refreshToken: string;\n clientId: string;\n ownerEmail: string;\n orgId?: string | null;\n orgDomain?: string | null;\n scope: string;\n resource: string;\n}): Promise<OAuthRefreshTokenRow> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const row: OAuthRefreshTokenRow = {\n id: randomUUID(),\n tokenHash: hashOAuthToken(params.refreshToken),\n clientId: params.clientId,\n ownerEmail: params.ownerEmail,\n orgId: params.orgId ?? null,\n orgDomain: params.orgDomain ?? null,\n scope: params.scope,\n resource: params.resource,\n createdAt: now,\n expiresAt: now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS,\n lastUsedAt: null,\n revokedAt: null,\n replacedByHash: null,\n };\n await client.execute({\n sql: `INSERT INTO mcp_oauth_refresh_tokens (id, token_hash, client_id, owner_email, org_id, org_domain, scope, resource, created_at, expires_at, last_used_at, revoked_at, replaced_by_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n row.id,\n row.tokenHash,\n row.clientId,\n row.ownerEmail,\n row.orgId,\n row.orgDomain,\n row.scope,\n row.resource,\n row.createdAt,\n row.expiresAt,\n row.lastUsedAt,\n row.revokedAt,\n row.replacedByHash,\n ],\n });\n return row;\n}\n\nexport async function rotateOAuthRefreshToken(params: {\n oldRefreshToken: string;\n newRefreshToken: string;\n}): Promise<OAuthRefreshTokenRow | null> {\n await ensureTable();\n const client = getDbExec();\n const oldHash = hashOAuthToken(params.oldRefreshToken);\n const newHash = hashOAuthToken(params.newRefreshToken);\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_refresh_tokens WHERE token_hash = ?`,\n args: [oldHash],\n });\n if (rows.length === 0) return null;\n const old = mapRefreshRow(rows[0]);\n if (old.revokedAt != null || (old.expiresAt ?? 0) < Date.now()) return null;\n\n const now = Date.now();\n const update = await client.execute({\n sql: `UPDATE mcp_oauth_refresh_tokens SET revoked_at = ?, last_used_at = ?, replaced_by_hash = ? WHERE token_hash = ? AND revoked_at IS NULL`,\n args: [now, now, newHash, oldHash],\n });\n if (update.rowsAffected === 0) return null;\n\n const next: OAuthRefreshTokenRow = {\n ...old,\n id: randomUUID(),\n tokenHash: newHash,\n createdAt: now,\n expiresAt: now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS,\n lastUsedAt: null,\n revokedAt: null,\n replacedByHash: null,\n };\n await client.execute({\n sql: `INSERT INTO mcp_oauth_refresh_tokens (id, token_hash, client_id, owner_email, org_id, org_domain, scope, resource, created_at, expires_at, last_used_at, revoked_at, replaced_by_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n next.id,\n next.tokenHash,\n next.clientId,\n next.ownerEmail,\n next.orgId,\n next.orgDomain,\n next.scope,\n next.resource,\n next.createdAt,\n next.expiresAt,\n next.lastUsedAt,\n next.revokedAt,\n next.replacedByHash,\n ],\n });\n return next;\n}\n\nexport async function getOAuthRefreshToken(\n refreshToken: string,\n): Promise<OAuthRefreshTokenRow | null> {\n await ensureTable();\n const client = getDbExec();\n const tokenHash = hashOAuthToken(refreshToken);\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_refresh_tokens WHERE token_hash = ?`,\n args: [tokenHash],\n });\n if (rows.length === 0) return null;\n const row = mapRefreshRow(rows[0]);\n if (row.revokedAt != null || (row.expiresAt ?? 0) < Date.now()) {\n return null;\n }\n return row;\n}\n\n/**\n * Slide the refresh-token's expiry window on each successful use so that active\n * users never hit the TTL. Records `last_used_at` and extends `expires_at` to\n * `now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS`.\n */\nexport async function touchOAuthRefreshToken(\n refreshToken: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const tokenHash = hashOAuthToken(refreshToken);\n const now = Date.now();\n await client.execute({\n sql: `UPDATE mcp_oauth_refresh_tokens SET last_used_at = ?, expires_at = ? WHERE token_hash = ? AND revoked_at IS NULL`,\n args: [now, now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS, tokenHash],\n });\n}\n"]}
1
+ {"version":3,"file":"oauth-store.js","sourceRoot":"","sources":["../../src/mcp/oauth-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL-backed storage for standard remote MCP OAuth.\n *\n * Additive framework tables only. We store OAuth client registrations,\n * short-lived authorization codes, and hashed refresh tokens. Access tokens\n * are signed JWTs and are never persisted.\n */\n\nimport { randomBytes, randomUUID, createHash } from \"node:crypto\";\n\nimport {\n getDbExec,\n isConnectionError,\n intType,\n isPostgres,\n} from \"../db/client.js\";\nimport { ensureTableExists } from \"../db/ddl-guard.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\nexport const MCP_OAUTH_CODE_TTL_MS = 10 * 60_000;\n\n/**\n * Parse a duration string like \"30d\", \"1h\", \"7d\" into seconds.\n * Returns `null` when the input is not a valid recognised pattern.\n */\nfunction parseDurationSeconds(raw: string): number | null {\n const trimmed = raw.trim();\n const match = trimmed.match(/^(\\d+(?:\\.\\d+)?)\\s*([smhd])$/i);\n if (!match) return null;\n const value = parseFloat(match[1]);\n if (!Number.isFinite(value) || value <= 0) return null;\n switch (match[2].toLowerCase()) {\n case \"s\":\n return value;\n case \"m\":\n return value * 60;\n case \"h\":\n return value * 3600;\n case \"d\":\n return value * 86400;\n }\n return null;\n}\n\nconst DEFAULT_ACCESS_TOKEN_TTL = \"30d\";\nconst DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 30 * 86400;\n\nfunction resolveAccessTokenTtl(): { str: string; seconds: number } {\n const env = process.env.MCP_OAUTH_ACCESS_TOKEN_TTL?.trim();\n if (env) {\n const secs = parseDurationSeconds(env);\n if (secs !== null) return { str: env, seconds: secs };\n // Garbage value — fall back to default rather than silently breaking.\n console.warn(\n `[mcp-oauth] Invalid MCP_OAUTH_ACCESS_TOKEN_TTL=\"${env}\", using default \"${DEFAULT_ACCESS_TOKEN_TTL}\"`,\n );\n }\n return {\n str: DEFAULT_ACCESS_TOKEN_TTL,\n seconds: DEFAULT_ACCESS_TOKEN_TTL_SECONDS,\n };\n}\n\nconst _accessTokenTtl = resolveAccessTokenTtl();\n\n/**\n * Access-token TTL as a jose-compatible duration string.\n * Defaults to \"30d\"; override with MCP_OAUTH_ACCESS_TOKEN_TTL env var.\n */\nexport const MCP_OAUTH_ACCESS_TOKEN_TTL: string = _accessTokenTtl.str;\n\n/**\n * Access-token TTL in seconds (derived from the same env var).\n * Used to populate the OAuth `expires_in` response field.\n */\nexport const MCP_OAUTH_ACCESS_TOKEN_TTL_SECONDS: number =\n _accessTokenTtl.seconds;\n\nexport const MCP_OAUTH_REFRESH_TOKEN_TTL_MS = 365 * 24 * 60 * 60_000;\nexport const MCP_OAUTH_REGISTER_MAX = 60;\nexport const MCP_OAUTH_REGISTER_WINDOW_MS = 60_000;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createClientsSql = `\n CREATE TABLE IF NOT EXISTS mcp_oauth_clients (\n client_id TEXT PRIMARY KEY,\n client_name TEXT,\n redirect_uris TEXT NOT NULL,\n grant_types TEXT,\n response_types TEXT,\n token_endpoint_auth_method TEXT,\n created_at ${intType()}\n )\n `;\n const createCodesSql = `\n CREATE TABLE IF NOT EXISTS mcp_oauth_codes (\n code TEXT PRIMARY KEY,\n client_id TEXT NOT NULL,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n code_challenge_method TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n org_domain TEXT,\n scope TEXT NOT NULL,\n resource TEXT NOT NULL,\n created_at ${intType()},\n expires_at ${intType()},\n consumed_at ${intType()}\n )\n `;\n const createRefreshTokensSql = `\n CREATE TABLE IF NOT EXISTS mcp_oauth_refresh_tokens (\n id TEXT PRIMARY KEY,\n token_hash TEXT UNIQUE NOT NULL,\n client_id TEXT NOT NULL,\n owner_email TEXT NOT NULL,\n org_id TEXT,\n org_domain TEXT,\n scope TEXT NOT NULL,\n resource TEXT NOT NULL,\n created_at ${intType()},\n expires_at ${intType()},\n last_used_at ${intType()},\n revoked_at ${intType()},\n replaced_by_hash TEXT\n )\n `;\n\n if (isPostgres()) {\n // PG-guard: probe information_schema first (no lock) and only issue\n // DDL when the table is actually missing, wrapped in a\n // transaction-scoped lock_timeout so a contended lock fails fast.\n await ensureTableExists(\"mcp_oauth_clients\", createClientsSql);\n await ensureTableExists(\"mcp_oauth_codes\", createCodesSql);\n await ensureTableExists(\n \"mcp_oauth_refresh_tokens\",\n createRefreshTokensSql,\n );\n return;\n }\n\n // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing\n // create-then-execute behaviour.\n await client.execute(createClientsSql);\n await client.execute(createCodesSql);\n await client.execute(createRefreshTokensSql);\n })().catch((err) => {\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport interface OAuthClientRow {\n clientId: string;\n clientName: string | null;\n redirectUris: string[];\n grantTypes: string[];\n responseTypes: string[];\n tokenEndpointAuthMethod: string;\n createdAt: number | null;\n}\n\nexport interface OAuthCodeRow {\n code: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n ownerEmail: string;\n orgId: string | null;\n orgDomain: string | null;\n scope: string;\n resource: string;\n createdAt: number | null;\n expiresAt: number | null;\n consumedAt: number | null;\n}\n\nexport interface OAuthRefreshTokenRow {\n id: string;\n tokenHash: string;\n clientId: string;\n ownerEmail: string;\n orgId: string | null;\n orgDomain: string | null;\n scope: string;\n resource: string;\n createdAt: number | null;\n expiresAt: number | null;\n lastUsedAt: number | null;\n revokedAt: number | null;\n replacedByHash: string | null;\n}\n\nexport function generateOpaqueToken(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\nexport function hashOAuthToken(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"base64url\");\n}\n\nfunction parseJsonStringArray(\n value: unknown,\n fallback: string[] = [],\n): string[] {\n if (typeof value !== \"string\") return fallback;\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((item): item is string => typeof item === \"string\")\n : fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction numOrNull(v: unknown): number | null {\n if (v == null) return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction mapClientRow(row: any): OAuthClientRow {\n return {\n clientId: row.client_id ?? row.clientId,\n clientName: row.client_name ?? row.clientName ?? null,\n redirectUris: parseJsonStringArray(row.redirect_uris ?? row.redirectUris),\n grantTypes: parseJsonStringArray(row.grant_types ?? row.grantTypes, [\n \"authorization_code\",\n \"refresh_token\",\n ]),\n responseTypes: parseJsonStringArray(\n row.response_types ?? row.responseTypes,\n [\"code\"],\n ),\n tokenEndpointAuthMethod:\n row.token_endpoint_auth_method ?? row.tokenEndpointAuthMethod ?? \"none\",\n createdAt: numOrNull(row.created_at ?? row.createdAt),\n };\n}\n\nfunction mapCodeRow(row: any): OAuthCodeRow {\n return {\n code: row.code,\n clientId: row.client_id ?? row.clientId,\n redirectUri: row.redirect_uri ?? row.redirectUri,\n codeChallenge: row.code_challenge ?? row.codeChallenge,\n codeChallengeMethod: row.code_challenge_method ?? row.codeChallengeMethod,\n ownerEmail: row.owner_email ?? row.ownerEmail,\n orgId: row.org_id ?? row.orgId ?? null,\n orgDomain: row.org_domain ?? row.orgDomain ?? null,\n scope: row.scope,\n resource: row.resource,\n createdAt: numOrNull(row.created_at ?? row.createdAt),\n expiresAt: numOrNull(row.expires_at ?? row.expiresAt),\n consumedAt: numOrNull(row.consumed_at ?? row.consumedAt),\n };\n}\n\nfunction mapRefreshRow(row: any): OAuthRefreshTokenRow {\n return {\n id: row.id,\n tokenHash: row.token_hash ?? row.tokenHash,\n clientId: row.client_id ?? row.clientId,\n ownerEmail: row.owner_email ?? row.ownerEmail,\n orgId: row.org_id ?? row.orgId ?? null,\n orgDomain: row.org_domain ?? row.orgDomain ?? null,\n scope: row.scope,\n resource: row.resource,\n createdAt: numOrNull(row.created_at ?? row.createdAt),\n expiresAt: numOrNull(row.expires_at ?? row.expiresAt),\n lastUsedAt: numOrNull(row.last_used_at ?? row.lastUsedAt),\n revokedAt: numOrNull(row.revoked_at ?? row.revokedAt),\n replacedByHash: row.replaced_by_hash ?? row.replacedByHash ?? null,\n };\n}\n\nexport async function registerOAuthClient(params: {\n clientName?: string | null;\n redirectUris: string[];\n grantTypes?: string[];\n responseTypes?: string[];\n tokenEndpointAuthMethod?: string;\n}): Promise<OAuthClientRow> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n try {\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) AS n FROM mcp_oauth_clients WHERE created_at > ?`,\n args: [now - MCP_OAUTH_REGISTER_WINDOW_MS],\n });\n const n = Number(rows[0]?.n ?? rows[0]?.[\"COUNT(*)\"] ?? 0);\n if (Number.isFinite(n) && n >= MCP_OAUTH_REGISTER_MAX) {\n throw new Error(\"RATE_LIMITED\");\n }\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") throw err;\n // Registration stays possible through transient count-read failures; the\n // exact redirect URI allowlist remains the primary safety gate.\n }\n const clientId = `agent-native-oauth-client-${randomUUID()}`;\n const grantTypes = params.grantTypes?.length\n ? params.grantTypes\n : [\"authorization_code\", \"refresh_token\"];\n const responseTypes = params.responseTypes?.length\n ? params.responseTypes\n : [\"code\"];\n const tokenEndpointAuthMethod = params.tokenEndpointAuthMethod || \"none\";\n await client.execute({\n sql: `INSERT INTO mcp_oauth_clients (client_id, client_name, redirect_uris, grant_types, response_types, token_endpoint_auth_method, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n args: [\n clientId,\n params.clientName ?? null,\n JSON.stringify(params.redirectUris),\n JSON.stringify(grantTypes),\n JSON.stringify(responseTypes),\n tokenEndpointAuthMethod,\n now,\n ],\n });\n return {\n clientId,\n clientName: params.clientName ?? null,\n redirectUris: params.redirectUris,\n grantTypes,\n responseTypes,\n tokenEndpointAuthMethod,\n createdAt: now,\n };\n}\n\nexport async function getOAuthClient(\n clientId: string,\n): Promise<OAuthClientRow | null> {\n try {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_clients WHERE client_id = ?`,\n args: [clientId],\n });\n return rows.length ? mapClientRow(rows[0]) : null;\n } catch (err) {\n if (isConnectionError(err)) return null;\n throw err;\n }\n}\n\nexport async function createOAuthCode(params: {\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n codeChallengeMethod: string;\n ownerEmail: string;\n orgId?: string | null;\n orgDomain?: string | null;\n scope: string;\n resource: string;\n}): Promise<OAuthCodeRow> {\n await ensureTable();\n const client = getDbExec();\n const code = generateOpaqueToken();\n const now = Date.now();\n const expiresAt = now + MCP_OAUTH_CODE_TTL_MS;\n await client.execute({\n sql: `INSERT INTO mcp_oauth_codes (code, client_id, redirect_uri, code_challenge, code_challenge_method, owner_email, org_id, org_domain, scope, resource, created_at, expires_at, consumed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n code,\n params.clientId,\n params.redirectUri,\n params.codeChallenge,\n params.codeChallengeMethod,\n params.ownerEmail,\n params.orgId ?? null,\n params.orgDomain ?? null,\n params.scope,\n params.resource,\n now,\n expiresAt,\n null,\n ],\n });\n return {\n code,\n clientId: params.clientId,\n redirectUri: params.redirectUri,\n codeChallenge: params.codeChallenge,\n codeChallengeMethod: params.codeChallengeMethod,\n ownerEmail: params.ownerEmail,\n orgId: params.orgId ?? null,\n orgDomain: params.orgDomain ?? null,\n scope: params.scope,\n resource: params.resource,\n createdAt: now,\n expiresAt,\n consumedAt: null,\n };\n}\n\nexport async function getOAuthCode(code: string): Promise<OAuthCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_codes WHERE code = ?`,\n args: [code],\n });\n if (rows.length === 0) return null;\n const row = mapCodeRow(rows[0]);\n if (row.consumedAt != null || (row.expiresAt ?? 0) < Date.now()) {\n return null;\n }\n return row;\n}\n\nexport async function consumeOAuthCode(\n code: string,\n): Promise<OAuthCodeRow | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_codes WHERE code = ?`,\n args: [code],\n });\n if (rows.length === 0) return null;\n const row = mapCodeRow(rows[0]);\n if (row.consumedAt != null || (row.expiresAt ?? 0) < Date.now()) {\n return null;\n }\n const result = await client.execute({\n sql: `UPDATE mcp_oauth_codes SET consumed_at = ? WHERE code = ? AND consumed_at IS NULL`,\n args: [Date.now(), code],\n });\n return result.rowsAffected > 0 ? row : null;\n}\n\nexport async function createOAuthRefreshToken(params: {\n refreshToken: string;\n clientId: string;\n ownerEmail: string;\n orgId?: string | null;\n orgDomain?: string | null;\n scope: string;\n resource: string;\n}): Promise<OAuthRefreshTokenRow> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const row: OAuthRefreshTokenRow = {\n id: randomUUID(),\n tokenHash: hashOAuthToken(params.refreshToken),\n clientId: params.clientId,\n ownerEmail: params.ownerEmail,\n orgId: params.orgId ?? null,\n orgDomain: params.orgDomain ?? null,\n scope: params.scope,\n resource: params.resource,\n createdAt: now,\n expiresAt: now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS,\n lastUsedAt: null,\n revokedAt: null,\n replacedByHash: null,\n };\n await client.execute({\n sql: `INSERT INTO mcp_oauth_refresh_tokens (id, token_hash, client_id, owner_email, org_id, org_domain, scope, resource, created_at, expires_at, last_used_at, revoked_at, replaced_by_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n row.id,\n row.tokenHash,\n row.clientId,\n row.ownerEmail,\n row.orgId,\n row.orgDomain,\n row.scope,\n row.resource,\n row.createdAt,\n row.expiresAt,\n row.lastUsedAt,\n row.revokedAt,\n row.replacedByHash,\n ],\n });\n return row;\n}\n\nexport async function rotateOAuthRefreshToken(params: {\n oldRefreshToken: string;\n newRefreshToken: string;\n}): Promise<OAuthRefreshTokenRow | null> {\n await ensureTable();\n const client = getDbExec();\n const oldHash = hashOAuthToken(params.oldRefreshToken);\n const newHash = hashOAuthToken(params.newRefreshToken);\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_refresh_tokens WHERE token_hash = ?`,\n args: [oldHash],\n });\n if (rows.length === 0) return null;\n const old = mapRefreshRow(rows[0]);\n if (old.revokedAt != null || (old.expiresAt ?? 0) < Date.now()) return null;\n\n const now = Date.now();\n const update = await client.execute({\n sql: `UPDATE mcp_oauth_refresh_tokens SET revoked_at = ?, last_used_at = ?, replaced_by_hash = ? WHERE token_hash = ? AND revoked_at IS NULL`,\n args: [now, now, newHash, oldHash],\n });\n if (update.rowsAffected === 0) return null;\n\n const next: OAuthRefreshTokenRow = {\n ...old,\n id: randomUUID(),\n tokenHash: newHash,\n createdAt: now,\n expiresAt: now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS,\n lastUsedAt: null,\n revokedAt: null,\n replacedByHash: null,\n };\n await client.execute({\n sql: `INSERT INTO mcp_oauth_refresh_tokens (id, token_hash, client_id, owner_email, org_id, org_domain, scope, resource, created_at, expires_at, last_used_at, revoked_at, replaced_by_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n next.id,\n next.tokenHash,\n next.clientId,\n next.ownerEmail,\n next.orgId,\n next.orgDomain,\n next.scope,\n next.resource,\n next.createdAt,\n next.expiresAt,\n next.lastUsedAt,\n next.revokedAt,\n next.replacedByHash,\n ],\n });\n return next;\n}\n\nexport async function getOAuthRefreshToken(\n refreshToken: string,\n): Promise<OAuthRefreshTokenRow | null> {\n await ensureTable();\n const client = getDbExec();\n const tokenHash = hashOAuthToken(refreshToken);\n const { rows } = await client.execute({\n sql: `SELECT * FROM mcp_oauth_refresh_tokens WHERE token_hash = ?`,\n args: [tokenHash],\n });\n if (rows.length === 0) return null;\n const row = mapRefreshRow(rows[0]);\n if (row.revokedAt != null || (row.expiresAt ?? 0) < Date.now()) {\n return null;\n }\n return row;\n}\n\n/**\n * Slide the refresh-token's expiry window on each successful use so that active\n * users never hit the TTL. Records `last_used_at` and extends `expires_at` to\n * `now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS`.\n */\nexport async function touchOAuthRefreshToken(\n refreshToken: string,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const tokenHash = hashOAuthToken(refreshToken);\n const now = Date.now();\n await client.execute({\n sql: `UPDATE mcp_oauth_refresh_tokens SET last_used_at = ?, expires_at = ? WHERE token_hash = ? AND revoked_at IS NULL`,\n args: [now, now + MCP_OAUTH_REFRESH_TOKEN_TTL_MS, tokenHash],\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAoErE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,MAAM,WAAW,wBAAwB;IACvC,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,YAAY,EAAE,CAAC,CAiBzB;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhE;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW7E;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAWlB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAkFrE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,MAAM,WAAW,wBAAwB;IACvC,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,YAAY,EAAE,CAAC,CAiBzB;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhE;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW7E;AAED,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAWlB"}
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { getDbExec, intType, retryOnDdlRace, safeJsonParse, } from "../db/client.js";
2
+ import { getDbExec, intType, isPostgres, retryOnDdlRace, safeJsonParse, } from "../db/client.js";
3
+ import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
3
4
  import { recordChange } from "../server/poll.js";
4
5
  function bumpPoll(owner) {
5
6
  recordChange({ source: "notifications", type: "change", key: owner });
@@ -14,7 +15,7 @@ async function ensureTable() {
14
15
  if (!_initPromise) {
15
16
  _initPromise = (async () => {
16
17
  const client = getDbExec();
17
- await retryOnDdlRace(() => client.execute(`
18
+ const createSql = `
18
19
  CREATE TABLE IF NOT EXISTS notifications (
19
20
  id TEXT PRIMARY KEY,
20
21
  owner TEXT NOT NULL,
@@ -26,7 +27,18 @@ async function ensureTable() {
26
27
  created_at ${intType()} NOT NULL,
27
28
  read_at ${intType()}
28
29
  )
29
- `));
30
+ `;
31
+ if (isPostgres()) {
32
+ // PG-guard: probe information_schema / pg_indexes first (no lock) and
33
+ // only issue DDL when the table/index is actually missing, wrapped in
34
+ // a transaction-scoped lock_timeout so a contended lock fails fast.
35
+ await ensureTableExists("notifications", createSql);
36
+ await ensureIndexExists("idx_notifications_owner_unread", `CREATE INDEX IF NOT EXISTS idx_notifications_owner_unread ON notifications (owner, read_at)`);
37
+ return;
38
+ }
39
+ // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
40
+ // retryOnDdlRace behaviour.
41
+ await retryOnDdlRace(() => client.execute(createSql));
30
42
  await retryOnDdlRace(() => client.execute(`CREATE INDEX IF NOT EXISTS idx_notifications_owner_unread ON notifications (owner, read_at)`));
31
43
  })().catch((err) => {
32
44
  // Reset on failure so a transient DB outage doesn't poison the cached
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport {\n getDbExec,\n intType,\n retryOnDdlRace,\n safeJsonParse,\n} from \"../db/client.js\";\nimport { recordChange } from \"../server/poll.js\";\nimport type { Notification, NotificationSeverity } from \"./types.js\";\n\nfunction bumpPoll(owner: string): void {\n recordChange({ source: \"notifications\", type: \"change\", key: owner });\n}\n\nlet _initPromise: Promise<void> | undefined;\n\nfunction normalizeLimit(value: number | undefined, fallback = 50): number {\n if (!Number.isFinite(value) || value == null || value <= 0) return fallback;\n return Math.min(Math.floor(value), 200);\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n await retryOnDdlRace(() =>\n client.execute(`\n CREATE TABLE IF NOT EXISTS notifications (\n id TEXT PRIMARY KEY,\n owner TEXT NOT NULL,\n severity TEXT NOT NULL,\n title TEXT NOT NULL,\n body TEXT,\n metadata TEXT,\n delivered_channels TEXT NOT NULL DEFAULT '[]',\n created_at ${intType()} NOT NULL,\n read_at ${intType()}\n )\n `),\n );\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_notifications_owner_unread ON notifications (owner, read_at)`,\n ),\n );\n })().catch((err) => {\n // Reset on failure so a transient DB outage doesn't poison the cached\n // promise and reject every future insert/list call for the lifetime of\n // the process.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nfunction parseRow(row: Record<string, unknown>): Notification {\n return {\n id: String(row.id),\n owner: String(row.owner),\n severity: String(row.severity) as NotificationSeverity,\n title: String(row.title),\n body: row.body == null ? undefined : String(row.body),\n metadata: row.metadata\n ? safeJsonParse<Record<string, unknown> | undefined>(\n row.metadata,\n undefined,\n )\n : undefined,\n deliveredChannels: safeJsonParse<string[]>(row.delivered_channels, []),\n createdAt: new Date(Number(row.created_at)).toISOString(),\n readAt:\n row.read_at == null ? null : new Date(Number(row.read_at)).toISOString(),\n };\n}\n\nexport interface InsertNotificationInput {\n owner: string;\n severity: NotificationSeverity;\n title: string;\n body?: string;\n metadata?: Record<string, unknown>;\n deliveredChannels?: string[];\n}\n\nexport async function insertNotification(\n input: InsertNotificationInput,\n): Promise<Notification> {\n await ensureTable();\n const client = getDbExec();\n const id = randomUUID();\n const createdAt = Date.now();\n await client.execute({\n sql: `INSERT INTO notifications\n (id, owner, severity, title, body, metadata, delivered_channels, created_at, read_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n args: [\n id,\n input.owner,\n input.severity,\n input.title,\n input.body ?? null,\n input.metadata ? JSON.stringify(input.metadata) : null,\n JSON.stringify(input.deliveredChannels ?? []),\n createdAt,\n ],\n });\n bumpPoll(input.owner);\n return {\n id,\n owner: input.owner,\n severity: input.severity,\n title: input.title,\n body: input.body,\n metadata: input.metadata,\n deliveredChannels: input.deliveredChannels ?? [],\n createdAt: new Date(createdAt).toISOString(),\n readAt: null,\n };\n}\n\nexport async function updateDeliveredChannels(\n id: string,\n channels: string[],\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE notifications SET delivered_channels = ? WHERE id = ?`,\n args: [JSON.stringify(channels), id],\n });\n}\n\nexport interface ListNotificationsOptions {\n /** When true, only return unread (read_at IS NULL). */\n unreadOnly?: boolean;\n /** Max rows to return. Default 50. */\n limit?: number;\n /** ISO timestamp cursor — returns rows with created_at < cursor. */\n before?: string;\n}\n\nexport async function listNotifications(\n owner: string,\n options: ListNotificationsOptions = {},\n): Promise<Notification[]> {\n await ensureTable();\n const client = getDbExec();\n const limit = normalizeLimit(options.limit);\n const args: Array<string | number> = [owner];\n let where = `owner = ?`;\n if (options.unreadOnly) where += ` AND read_at IS NULL`;\n if (options.before) {\n where += ` AND created_at < ?`;\n args.push(new Date(options.before).getTime());\n }\n args.push(limit);\n const { rows } = await client.execute({\n sql: `SELECT * FROM notifications WHERE ${where} ORDER BY created_at DESC LIMIT ?`,\n args,\n });\n return rows.map((r) => parseRow(r as Record<string, unknown>));\n}\n\nexport async function countUnread(owner: string): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) as c FROM notifications WHERE owner = ? AND read_at IS NULL`,\n args: [owner],\n });\n return Number(rows[0]?.c ?? 0);\n}\n\nexport async function markNotificationRead(\n id: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const res = await client.execute({\n sql: `UPDATE notifications SET read_at = ? WHERE id = ? AND owner = ? AND read_at IS NULL`,\n args: [now, id, owner],\n });\n const updated =\n (res as unknown as { rowsAffected?: number }).rowsAffected !== 0;\n if (updated) bumpPoll(owner);\n return updated;\n}\n\nexport async function markAllNotificationsRead(owner: string): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const res = await client.execute({\n sql: `UPDATE notifications SET read_at = ? WHERE owner = ? AND read_at IS NULL`,\n args: [now, owner],\n });\n const count = (res as unknown as { rowsAffected?: number }).rowsAffected ?? 0;\n if (count > 0) bumpPoll(owner);\n return count;\n}\n\nexport async function deleteNotification(\n id: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const res = await client.execute({\n sql: `DELETE FROM notifications WHERE id = ? AND owner = ?`,\n args: [id, owner],\n });\n const deleted =\n (res as unknown as { rowsAffected?: number }).rowsAffected !== 0;\n if (deleted) bumpPoll(owner);\n return deleted;\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EACL,SAAS,EACT,OAAO,EACP,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,SAAS,QAAQ,CAAC,KAAa;IAC7B,YAAY,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,IAAI,YAAuC,CAAC;AAE5C,SAAS,cAAc,CAAC,KAAyB,EAAE,QAAQ,GAAG,EAAE;IAC9D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5E,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG;;;;;;;;;yBASC,OAAO,EAAE;sBACZ,OAAO,EAAE;;SAEtB,CAAC;YAEJ,IAAI,UAAU,EAAE,EAAE,CAAC;gBACjB,sEAAsE;gBACtE,sEAAsE;gBACtE,oEAAoE;gBACpE,MAAM,iBAAiB,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;gBACpD,MAAM,iBAAiB,CACrB,gCAAgC,EAChC,6FAA6F,CAC9F,CAAC;gBACF,OAAO;YACT,CAAC;YAED,uEAAuE;YACvE,4BAA4B;YAC5B,MAAM,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;YACtD,MAAM,cAAc,CAAC,GAAG,EAAE,CACxB,MAAM,CAAC,OAAO,CACZ,6FAA6F,CAC9F,CACF,CAAC;QACJ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACjB,sEAAsE;YACtE,uEAAuE;YACvE,eAAe;YACf,YAAY,GAAG,SAAS,CAAC;YACzB,MAAM,GAAG,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,QAAQ,CAAC,GAA4B;IAC5C,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAyB;QACtD,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACrD,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACpB,CAAC,CAAC,aAAa,CACX,GAAG,CAAC,QAAQ,EACZ,SAAS,CACV;YACH,CAAC,CAAC,SAAS;QACb,iBAAiB,EAAE,aAAa,CAAW,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC;QACtE,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE;QACzD,MAAM,EACJ,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE;KAC3E,CAAC;AACJ,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAA8B;IAE9B,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,MAAM,CAAC,OAAO,CAAC;QACnB,GAAG,EAAE;;4CAEmC;QACxC,IAAI,EAAE;YACJ,EAAE;YACF,KAAK,CAAC,KAAK;YACX,KAAK,CAAC,QAAQ;YACd,KAAK,CAAC,KAAK;YACX,KAAK,CAAC,IAAI,IAAI,IAAI;YAClB,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YACtD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC;YAC7C,SAAS;SACV;KACF,CAAC,CAAC;IACH,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACtB,OAAO;QACL,EAAE;QACF,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,EAAE;QAChD,SAAS,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE;QAC5C,MAAM,EAAE,IAAI;KACb,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,EAAU,EACV,QAAkB;IAElB,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,OAAO,CAAC;QACnB,GAAG,EAAE,8DAA8D;QACnE,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;KACrC,CAAC,CAAC;AACL,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAa,EACb,OAAO,GAA6B,EAAE;IAEtC,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,IAAI,GAA2B,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,KAAK,GAAG,WAAW,CAAC;IACxB,IAAI,OAAO,CAAC,UAAU;QAAE,KAAK,IAAI,sBAAsB,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,IAAI,qBAAqB,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACpC,GAAG,EAAE,qCAAqC,KAAK,mCAAmC;QAClF,IAAI;KACL,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAA4B,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAa;IAC7C,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACpC,GAAG,EAAE,6EAA6E;QAClF,IAAI,EAAE,CAAC,KAAK,CAAC;KACd,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,EAAU,EACV,KAAa;IAEb,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAC/B,GAAG,EAAE,qFAAqF;QAC1F,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,CAAC;KACvB,CAAC,CAAC;IACH,MAAM,OAAO,GACV,GAA4C,CAAC,YAAY,KAAK,CAAC,CAAC;IACnE,IAAI,OAAO;QAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,KAAa;IAC1D,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAC/B,GAAG,EAAE,0EAA0E;QAC/E,IAAI,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC;KACnB,CAAC,CAAC;IACH,MAAM,KAAK,GAAI,GAA4C,CAAC,YAAY,IAAI,CAAC,CAAC;IAC9E,IAAI,KAAK,GAAG,CAAC;QAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,EAAU,EACV,KAAa;IAEb,MAAM,WAAW,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAC/B,GAAG,EAAE,sDAAsD;QAC3D,IAAI,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC;KAClB,CAAC,CAAC;IACH,MAAM,OAAO,GACV,GAA4C,CAAC,YAAY,KAAK,CAAC,CAAC;IACnE,IAAI,OAAO;QAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport {\n getDbExec,\n intType,\n isPostgres,\n retryOnDdlRace,\n safeJsonParse,\n} from \"../db/client.js\";\nimport { ensureIndexExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport { recordChange } from \"../server/poll.js\";\nimport type { Notification, NotificationSeverity } from \"./types.js\";\n\nfunction bumpPoll(owner: string): void {\n recordChange({ source: \"notifications\", type: \"change\", key: owner });\n}\n\nlet _initPromise: Promise<void> | undefined;\n\nfunction normalizeLimit(value: number | undefined, fallback = 50): number {\n if (!Number.isFinite(value) || value == null || value <= 0) return fallback;\n return Math.min(Math.floor(value), 200);\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n const createSql = `\n CREATE TABLE IF NOT EXISTS notifications (\n id TEXT PRIMARY KEY,\n owner TEXT NOT NULL,\n severity TEXT NOT NULL,\n title TEXT NOT NULL,\n body TEXT,\n metadata TEXT,\n delivered_channels TEXT NOT NULL DEFAULT '[]',\n created_at ${intType()} NOT NULL,\n read_at ${intType()}\n )\n `;\n\n if (isPostgres()) {\n // PG-guard: probe information_schema / pg_indexes first (no lock) and\n // only issue DDL when the table/index is actually missing, wrapped in\n // a transaction-scoped lock_timeout so a contended lock fails fast.\n await ensureTableExists(\"notifications\", createSql);\n await ensureIndexExists(\n \"idx_notifications_owner_unread\",\n `CREATE INDEX IF NOT EXISTS idx_notifications_owner_unread ON notifications (owner, read_at)`,\n );\n return;\n }\n\n // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing\n // retryOnDdlRace behaviour.\n await retryOnDdlRace(() => client.execute(createSql));\n await retryOnDdlRace(() =>\n client.execute(\n `CREATE INDEX IF NOT EXISTS idx_notifications_owner_unread ON notifications (owner, read_at)`,\n ),\n );\n })().catch((err) => {\n // Reset on failure so a transient DB outage doesn't poison the cached\n // promise and reject every future insert/list call for the lifetime of\n // the process.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nfunction parseRow(row: Record<string, unknown>): Notification {\n return {\n id: String(row.id),\n owner: String(row.owner),\n severity: String(row.severity) as NotificationSeverity,\n title: String(row.title),\n body: row.body == null ? undefined : String(row.body),\n metadata: row.metadata\n ? safeJsonParse<Record<string, unknown> | undefined>(\n row.metadata,\n undefined,\n )\n : undefined,\n deliveredChannels: safeJsonParse<string[]>(row.delivered_channels, []),\n createdAt: new Date(Number(row.created_at)).toISOString(),\n readAt:\n row.read_at == null ? null : new Date(Number(row.read_at)).toISOString(),\n };\n}\n\nexport interface InsertNotificationInput {\n owner: string;\n severity: NotificationSeverity;\n title: string;\n body?: string;\n metadata?: Record<string, unknown>;\n deliveredChannels?: string[];\n}\n\nexport async function insertNotification(\n input: InsertNotificationInput,\n): Promise<Notification> {\n await ensureTable();\n const client = getDbExec();\n const id = randomUUID();\n const createdAt = Date.now();\n await client.execute({\n sql: `INSERT INTO notifications\n (id, owner, severity, title, body, metadata, delivered_channels, created_at, read_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n args: [\n id,\n input.owner,\n input.severity,\n input.title,\n input.body ?? null,\n input.metadata ? JSON.stringify(input.metadata) : null,\n JSON.stringify(input.deliveredChannels ?? []),\n createdAt,\n ],\n });\n bumpPoll(input.owner);\n return {\n id,\n owner: input.owner,\n severity: input.severity,\n title: input.title,\n body: input.body,\n metadata: input.metadata,\n deliveredChannels: input.deliveredChannels ?? [],\n createdAt: new Date(createdAt).toISOString(),\n readAt: null,\n };\n}\n\nexport async function updateDeliveredChannels(\n id: string,\n channels: string[],\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE notifications SET delivered_channels = ? WHERE id = ?`,\n args: [JSON.stringify(channels), id],\n });\n}\n\nexport interface ListNotificationsOptions {\n /** When true, only return unread (read_at IS NULL). */\n unreadOnly?: boolean;\n /** Max rows to return. Default 50. */\n limit?: number;\n /** ISO timestamp cursor — returns rows with created_at < cursor. */\n before?: string;\n}\n\nexport async function listNotifications(\n owner: string,\n options: ListNotificationsOptions = {},\n): Promise<Notification[]> {\n await ensureTable();\n const client = getDbExec();\n const limit = normalizeLimit(options.limit);\n const args: Array<string | number> = [owner];\n let where = `owner = ?`;\n if (options.unreadOnly) where += ` AND read_at IS NULL`;\n if (options.before) {\n where += ` AND created_at < ?`;\n args.push(new Date(options.before).getTime());\n }\n args.push(limit);\n const { rows } = await client.execute({\n sql: `SELECT * FROM notifications WHERE ${where} ORDER BY created_at DESC LIMIT ?`,\n args,\n });\n return rows.map((r) => parseRow(r as Record<string, unknown>));\n}\n\nexport async function countUnread(owner: string): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT COUNT(*) as c FROM notifications WHERE owner = ? AND read_at IS NULL`,\n args: [owner],\n });\n return Number(rows[0]?.c ?? 0);\n}\n\nexport async function markNotificationRead(\n id: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const res = await client.execute({\n sql: `UPDATE notifications SET read_at = ? WHERE id = ? AND owner = ? AND read_at IS NULL`,\n args: [now, id, owner],\n });\n const updated =\n (res as unknown as { rowsAffected?: number }).rowsAffected !== 0;\n if (updated) bumpPoll(owner);\n return updated;\n}\n\nexport async function markAllNotificationsRead(owner: string): Promise<number> {\n await ensureTable();\n const client = getDbExec();\n const now = Date.now();\n const res = await client.execute({\n sql: `UPDATE notifications SET read_at = ? WHERE owner = ? AND read_at IS NULL`,\n args: [now, owner],\n });\n const count = (res as unknown as { rowsAffected?: number }).rowsAffected ?? 0;\n if (count > 0) bumpPoll(owner);\n return count;\n}\n\nexport async function deleteNotification(\n id: string,\n owner: string,\n): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const res = await client.execute({\n sql: `DELETE FROM notifications WHERE id = ? AND owner = ?`,\n args: [id, owner],\n });\n const deleted =\n (res as unknown as { rowsAffected?: number }).rowsAffected !== 0;\n if (deleted) bumpPoll(owner);\n return deleted;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/oauth-tokens/store.ts"],"names":[],"mappings":"AAkGA,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAUzC;AAED;;;;;;;;GAQG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;IAC1D,QAAQ,CAAC,UAAU,OAAO;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,YAAY,IAAI,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;KACxB,EASA;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAChE,KAAK,CAAC;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC,CACH,CAaA;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CACR,KAAK,CAAC;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC,CACH,CAaA;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CASlB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/oauth-tokens/store.ts"],"names":[],"mappings":"AA6IA,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAUzC;AAED;;;;;;;;GAQG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;IAC1D,QAAQ,CAAC,UAAU,OAAO;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,YAAY,IAAI,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;KACxB,EASA;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAChE,KAAK,CAAC;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC,CACH,CAaA;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CACR,KAAK,CAAC;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC,CACH,CAaA;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CASlB"}
@@ -1,4 +1,5 @@
1
1
  import { getDbExec, isPostgres, intType } from "../db/client.js";
2
+ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
2
3
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
3
4
  import { encryptSecretValue, decryptSecretValue, isEncryptedSecretValue, } from "../secrets/crypto.js";
4
5
  let _initPromise;
@@ -46,7 +47,7 @@ async function ensureTable() {
46
47
  _initPromise = (async () => {
47
48
  const client = getDbExec();
48
49
  const table = oauthTokensTable();
49
- await client.execute(`
50
+ const createSql = `
50
51
  CREATE TABLE IF NOT EXISTS ${table} (
51
52
  provider TEXT NOT NULL,
52
53
  account_id TEXT NOT NULL,
@@ -55,7 +56,37 @@ async function ensureTable() {
55
56
  updated_at ${intType()} NOT NULL,
56
57
  PRIMARY KEY (provider, account_id)
57
58
  )
58
- `);
59
+ `;
60
+ if (isPostgres()) {
61
+ // Hot path: the `oauth_tokens` table and its additive columns are
62
+ // virtually always already present in production. Issuing `CREATE
63
+ // TABLE`/`ALTER TABLE` still takes an ACCESS EXCLUSIVE lock that, in a
64
+ // fresh background-worker process behind a concurrent connection on the
65
+ // shared Neon DB, can block ~indefinitely. The ensure* wrappers probe
66
+ // `information_schema` first (plain reads, no lock) and run DDL ONLY for
67
+ // what is actually missing, bounded by a transaction-scoped
68
+ // `lock_timeout`. If a swallowed lock-timeout leaves the schema still
69
+ // missing they RE-PROBE and THROW rather than letting init memoize
70
+ // success against absent schema. `oauthTokensTable()` is
71
+ // `public.oauth_tokens` on Postgres; the wrappers take the unqualified
72
+ // table name.
73
+ await ensureTableExists("oauth_tokens", createSql);
74
+ // Migration: add owner column to existing tables — guarded so the hot
75
+ // path (column already present) skips the ACCESS EXCLUSIVE ALTER.
76
+ await ensureColumnExists("oauth_tokens", "owner", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS owner TEXT`);
77
+ // Migration: add display_name column
78
+ await ensureColumnExists("oauth_tokens", "display_name", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS display_name TEXT`);
79
+ // Backfill: set owner = account_id for existing rows without an owner
80
+ await client.execute(`UPDATE ${table} SET owner = account_id WHERE owner IS NULL`);
81
+ // Older deployments have a 32-bit `updated_at`; on Postgres the
82
+ // `Date.now()` written on every token save overflows int4. Widen in place
83
+ // (no-op once done / on fresh DBs). Unqualified name — the helper scopes
84
+ // to the `public` schema.
85
+ await widenIntColumnsToBigInt("oauth_tokens", ["updated_at"]);
86
+ return;
87
+ }
88
+ // SQLite (local dev): no lock problem — keep the original behaviour.
89
+ await client.execute(createSql);
59
90
  // Migration: add owner column to existing tables
60
91
  try {
61
92
  await client.execute(`ALTER TABLE ${table} ADD COLUMN owner TEXT`);