auramaxx 0.0.1

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 (418) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +77 -0
  3. package/apps/desktop-electron/main.js +428 -0
  4. package/bin/auramaxx.js +1063 -0
  5. package/docs/ADAPTERS.md +466 -0
  6. package/docs/AGENT_SETUP.md +159 -0
  7. package/docs/API.md +127 -0
  8. package/docs/APPS.md +199 -0
  9. package/docs/ARCHITECTURE.md +235 -0
  10. package/docs/AUTH.md +318 -0
  11. package/docs/BEST-PRACTICES.md +82 -0
  12. package/docs/CLI.md +141 -0
  13. package/docs/DESKTOP_ELECTRON.md +26 -0
  14. package/docs/DEVELOPING-APPS.md +453 -0
  15. package/docs/MCP.md +122 -0
  16. package/docs/PACKAGING_POLICY.md +19 -0
  17. package/docs/PERMISSION.md +137 -0
  18. package/docs/PROTOCOL.md +142 -0
  19. package/docs/README.md +50 -0
  20. package/docs/SKILLS.md +132 -0
  21. package/docs/TROUBLESHOOTING.md +376 -0
  22. package/docs/WORKSPACE.md +673 -0
  23. package/docs/agent-auth.md +14 -0
  24. package/docs/api/authentication.md +79 -0
  25. package/docs/api/secrets/api-keys.md +28 -0
  26. package/docs/api/secrets/credentials.md +80 -0
  27. package/docs/api/secrets/sharing.md +48 -0
  28. package/docs/api/system.md +41 -0
  29. package/docs/api/wallets/apps-strategies.md +66 -0
  30. package/docs/api/wallets/core.md +46 -0
  31. package/docs/api/wallets/data-portfolio.md +42 -0
  32. package/docs/aura-file.md +48 -0
  33. package/docs/core-concepts/FEATURES.md +114 -0
  34. package/docs/credentials.md +120 -0
  35. package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
  36. package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
  37. package/docs/external/getting-started.md +10 -0
  38. package/docs/external/overview.md +19 -0
  39. package/docs/external/persona-paths.md +7 -0
  40. package/docs/external/share-secret.md +76 -0
  41. package/docs/external/why-aura.md +7 -0
  42. package/docs/security.md +227 -0
  43. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  44. package/docs/wallet/AI.md +508 -0
  45. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  46. package/docs/wallet/README.md +47 -0
  47. package/docs/wallet/STRATEGY.md +89 -0
  48. package/next.config.ts +28 -0
  49. package/package.json +167 -0
  50. package/postcss.config.mjs +8 -0
  51. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  52. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  53. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  54. package/prisma/migrations/20260222090000_update_admin_ttl_default/migration.sql +10 -0
  55. package/prisma/migrations/migration_lock.toml +3 -0
  56. package/prisma/schema.prisma +447 -0
  57. package/public/logo.webp +0 -0
  58. package/scripts/add-app.js +245 -0
  59. package/server/abi/SwapHelper.json +438 -0
  60. package/server/cli/approval.ts +447 -0
  61. package/server/cli/commands/actions.ts +474 -0
  62. package/server/cli/commands/api.ts +220 -0
  63. package/server/cli/commands/apikey.ts +277 -0
  64. package/server/cli/commands/app.ts +204 -0
  65. package/server/cli/commands/auth.ts +464 -0
  66. package/server/cli/commands/cron.ts +24 -0
  67. package/server/cli/commands/diary.ts +274 -0
  68. package/server/cli/commands/doctor.ts +1247 -0
  69. package/server/cli/commands/env.ts +476 -0
  70. package/server/cli/commands/experimental.ts +69 -0
  71. package/server/cli/commands/init.ts +798 -0
  72. package/server/cli/commands/lock.ts +157 -0
  73. package/server/cli/commands/mcp.ts +285 -0
  74. package/server/cli/commands/quickhack.ts +86 -0
  75. package/server/cli/commands/release-check.ts +231 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/service.ts +320 -0
  78. package/server/cli/commands/shell-hook.ts +512 -0
  79. package/server/cli/commands/skill.ts +216 -0
  80. package/server/cli/commands/start.ts +139 -0
  81. package/server/cli/commands/status.ts +59 -0
  82. package/server/cli/commands/stop.ts +36 -0
  83. package/server/cli/commands/token.ts +180 -0
  84. package/server/cli/commands/unlock.ts +50 -0
  85. package/server/cli/commands/vault.ts +1323 -0
  86. package/server/cli/commands/wallet.ts +209 -0
  87. package/server/cli/index.ts +280 -0
  88. package/server/cli/lib/approval-poll.ts +94 -0
  89. package/server/cli/lib/aura-parser.ts +64 -0
  90. package/server/cli/lib/credential-create.ts +74 -0
  91. package/server/cli/lib/credential-resolve.ts +280 -0
  92. package/server/cli/lib/dotenv-migrate.ts +116 -0
  93. package/server/cli/lib/dotenv-parser.ts +146 -0
  94. package/server/cli/lib/escalation.ts +57 -0
  95. package/server/cli/lib/http.ts +91 -0
  96. package/server/cli/lib/init-steps.ts +76 -0
  97. package/server/cli/lib/local-agent-trust.ts +45 -0
  98. package/server/cli/lib/lock-unlock-helper.ts +71 -0
  99. package/server/cli/lib/process.ts +162 -0
  100. package/server/cli/lib/prompt.ts +294 -0
  101. package/server/cli/lib/theme.ts +240 -0
  102. package/server/cli/socket.ts +579 -0
  103. package/server/cli/transport-client.ts +50 -0
  104. package/server/cron/index.ts +137 -0
  105. package/server/cron/job.ts +31 -0
  106. package/server/cron/jobs/balance-sync.ts +436 -0
  107. package/server/cron/jobs/incoming-scan.ts +506 -0
  108. package/server/cron/jobs/native-price.ts +70 -0
  109. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  110. package/server/cron/jobs/strategy-runner.ts +175 -0
  111. package/server/cron/scheduler.ts +125 -0
  112. package/server/index.ts +420 -0
  113. package/server/lib/adapters/factory.ts +119 -0
  114. package/server/lib/adapters/index.ts +19 -0
  115. package/server/lib/adapters/router.ts +297 -0
  116. package/server/lib/adapters/telegram.ts +645 -0
  117. package/server/lib/adapters/types.ts +89 -0
  118. package/server/lib/adapters/webhook.ts +95 -0
  119. package/server/lib/address.ts +49 -0
  120. package/server/lib/agent-auth/contracts.ts +1194 -0
  121. package/server/lib/agent-profiles.ts +419 -0
  122. package/server/lib/ai.ts +285 -0
  123. package/server/lib/api-registry/contracts.ts +86 -0
  124. package/server/lib/api-registry/validation.ts +172 -0
  125. package/server/lib/apikey-migration.ts +258 -0
  126. package/server/lib/app-installer.ts +505 -0
  127. package/server/lib/app-tokens.ts +247 -0
  128. package/server/lib/approval-link.ts +27 -0
  129. package/server/lib/auth.ts +314 -0
  130. package/server/lib/auto-execute.ts +160 -0
  131. package/server/lib/batch.ts +242 -0
  132. package/server/lib/cold.ts +1048 -0
  133. package/server/lib/config.ts +408 -0
  134. package/server/lib/credential-access-audit.ts +85 -0
  135. package/server/lib/credential-access-policy.ts +111 -0
  136. package/server/lib/credential-health.ts +343 -0
  137. package/server/lib/credential-import.ts +608 -0
  138. package/server/lib/credential-scope.ts +102 -0
  139. package/server/lib/credential-shares.ts +190 -0
  140. package/server/lib/credential-transport.ts +533 -0
  141. package/server/lib/credential-vault.ts +77 -0
  142. package/server/lib/credentials.ts +422 -0
  143. package/server/lib/crypto.ts +8 -0
  144. package/server/lib/db.ts +58 -0
  145. package/server/lib/defaults.ts +386 -0
  146. package/server/lib/dex/index.ts +80 -0
  147. package/server/lib/dex/relay.ts +235 -0
  148. package/server/lib/dex/types.ts +59 -0
  149. package/server/lib/dex/uniswap.ts +370 -0
  150. package/server/lib/diary.ts +34 -0
  151. package/server/lib/dont-ask-again-policy.ts +41 -0
  152. package/server/lib/e2e-agent/artifacts.ts +36 -0
  153. package/server/lib/e2e-agent/contracts.ts +112 -0
  154. package/server/lib/e2e-agent/validation.ts +135 -0
  155. package/server/lib/encrypt.ts +114 -0
  156. package/server/lib/error.ts +20 -0
  157. package/server/lib/events.ts +217 -0
  158. package/server/lib/feature-flags.ts +93 -0
  159. package/server/lib/hot.ts +357 -0
  160. package/server/lib/human-action-summary.ts +80 -0
  161. package/server/lib/key-fingerprint.ts +28 -0
  162. package/server/lib/logger.ts +340 -0
  163. package/server/lib/network.ts +137 -0
  164. package/server/lib/notifications.ts +230 -0
  165. package/server/lib/oauth2-refresh.ts +241 -0
  166. package/server/lib/oursecret.ts +71 -0
  167. package/server/lib/passkey-credential.ts +360 -0
  168. package/server/lib/passkey.ts +68 -0
  169. package/server/lib/permissions.ts +299 -0
  170. package/server/lib/pino.ts +24 -0
  171. package/server/lib/policy-preview.ts +138 -0
  172. package/server/lib/price.ts +338 -0
  173. package/server/lib/prices.ts +34 -0
  174. package/server/lib/project-scope.ts +297 -0
  175. package/server/lib/resolve-action.ts +328 -0
  176. package/server/lib/resolve.ts +36 -0
  177. package/server/lib/secret-gist-share.ts +296 -0
  178. package/server/lib/sessions.ts +634 -0
  179. package/server/lib/socket-path.ts +56 -0
  180. package/server/lib/solana/connection.ts +26 -0
  181. package/server/lib/solana/jupiter.ts +128 -0
  182. package/server/lib/solana/transfer.ts +108 -0
  183. package/server/lib/solana/wallet.ts +136 -0
  184. package/server/lib/strategy/emits.ts +21 -0
  185. package/server/lib/strategy/engine.ts +1305 -0
  186. package/server/lib/strategy/executor.ts +115 -0
  187. package/server/lib/strategy/hook-context.ts +159 -0
  188. package/server/lib/strategy/hooks.ts +990 -0
  189. package/server/lib/strategy/index.ts +28 -0
  190. package/server/lib/strategy/installer.ts +305 -0
  191. package/server/lib/strategy/loader.ts +256 -0
  192. package/server/lib/strategy/message.ts +237 -0
  193. package/server/lib/strategy/repository.ts +218 -0
  194. package/server/lib/strategy/session-logger.ts +693 -0
  195. package/server/lib/strategy/sources.ts +288 -0
  196. package/server/lib/strategy/state.ts +189 -0
  197. package/server/lib/strategy/templates.ts +403 -0
  198. package/server/lib/strategy/tick.ts +404 -0
  199. package/server/lib/strategy/types.ts +230 -0
  200. package/server/lib/swap.ts +3 -0
  201. package/server/lib/temp.ts +86 -0
  202. package/server/lib/token-metadata.ts +86 -0
  203. package/server/lib/token-safety.ts +200 -0
  204. package/server/lib/token-search.ts +444 -0
  205. package/server/lib/totp.ts +194 -0
  206. package/server/lib/transactions.ts +123 -0
  207. package/server/lib/transport.ts +84 -0
  208. package/server/lib/txhistory/decoder.ts +262 -0
  209. package/server/lib/txhistory/enricher.ts +652 -0
  210. package/server/lib/txhistory/index.ts +391 -0
  211. package/server/lib/txhistory/signatures.ts +59 -0
  212. package/server/lib/update-check.ts +35 -0
  213. package/server/lib/verified-summary.ts +414 -0
  214. package/server/lib/view-registry.ts +80 -0
  215. package/server/mcp/profile-policy.ts +30 -0
  216. package/server/mcp/server.ts +1589 -0
  217. package/server/mcp/tools.ts +276 -0
  218. package/server/middleware/auth.ts +119 -0
  219. package/server/middleware/requestLogger.ts +84 -0
  220. package/server/routes/actions.ts +539 -0
  221. package/server/routes/adapters.ts +711 -0
  222. package/server/routes/addressbook.ts +113 -0
  223. package/server/routes/ai.ts +34 -0
  224. package/server/routes/apikeys.ts +343 -0
  225. package/server/routes/apps.ts +601 -0
  226. package/server/routes/auth.ts +406 -0
  227. package/server/routes/backup.ts +404 -0
  228. package/server/routes/batch.ts +270 -0
  229. package/server/routes/bookmarks.ts +162 -0
  230. package/server/routes/credential-shares.ts +380 -0
  231. package/server/routes/credential-vaults.ts +159 -0
  232. package/server/routes/credentials.ts +1782 -0
  233. package/server/routes/dashboard.ts +97 -0
  234. package/server/routes/defaults.ts +124 -0
  235. package/server/routes/flags.ts +11 -0
  236. package/server/routes/fund.ts +225 -0
  237. package/server/routes/heartbeat.ts +375 -0
  238. package/server/routes/import.ts +364 -0
  239. package/server/routes/launch.ts +665 -0
  240. package/server/routes/lock.ts +54 -0
  241. package/server/routes/logs.ts +68 -0
  242. package/server/routes/nuke.ts +111 -0
  243. package/server/routes/passkey-credentials.ts +99 -0
  244. package/server/routes/passkey.ts +366 -0
  245. package/server/routes/portfolio.ts +217 -0
  246. package/server/routes/price.ts +63 -0
  247. package/server/routes/resolve.ts +31 -0
  248. package/server/routes/security.ts +45 -0
  249. package/server/routes/send-evm.ts +241 -0
  250. package/server/routes/send-solana.ts +281 -0
  251. package/server/routes/send.ts +178 -0
  252. package/server/routes/setup.ts +210 -0
  253. package/server/routes/strategy.ts +894 -0
  254. package/server/routes/swap-evm.ts +352 -0
  255. package/server/routes/swap-solana.ts +176 -0
  256. package/server/routes/swap.ts +356 -0
  257. package/server/routes/token.ts +247 -0
  258. package/server/routes/unlock.ts +467 -0
  259. package/server/routes/views.ts +41 -0
  260. package/server/routes/wallet-assets.ts +361 -0
  261. package/server/routes/wallet-transactions.ts +515 -0
  262. package/server/routes/wallet.ts +709 -0
  263. package/server/types.ts +146 -0
  264. package/shared/credential-field-schema.ts +248 -0
  265. package/skills/auramaxx/HEARTBEAT.md +78 -0
  266. package/skills/auramaxx/SKILL.md +745 -0
  267. package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
  268. package/skills/auramaxx/docs/API.md +127 -0
  269. package/skills/auramaxx/docs/AUTH.md +318 -0
  270. package/skills/auramaxx/docs/CLI.md +130 -0
  271. package/skills/auramaxx/docs/MCP.md +122 -0
  272. package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
  273. package/skills/auramaxx/docs/WORKSPACE.md +673 -0
  274. package/skills/auramaxx/docs/security.md +227 -0
  275. package/skills/task-lifecycle/SKILL.md +378 -0
  276. package/src/app/api/[...doc]/page.tsx +36 -0
  277. package/src/app/api/agent-requests/route.ts +30 -0
  278. package/src/app/api/apps/install/route.ts +132 -0
  279. package/src/app/api/apps/manifests/route.ts +16 -0
  280. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  281. package/src/app/api/docs/plain/route.ts +74 -0
  282. package/src/app/api/events/route.ts +92 -0
  283. package/src/app/api/page.tsx +290 -0
  284. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  285. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  286. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  287. package/src/app/api/workspace/[id]/route.ts +168 -0
  288. package/src/app/api/workspace/auth.ts +40 -0
  289. package/src/app/api/workspace/config/route.ts +121 -0
  290. package/src/app/api/workspace/import/route.ts +127 -0
  291. package/src/app/api/workspace/route.ts +116 -0
  292. package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
  293. package/src/app/apple-icon.png +0 -0
  294. package/src/app/approve/[actionId]/page.tsx +409 -0
  295. package/src/app/docs/DocsPageContent.tsx +269 -0
  296. package/src/app/docs/[...doc]/page.tsx +41 -0
  297. package/src/app/docs/page.tsx +38 -0
  298. package/src/app/favicon.ico +0 -0
  299. package/src/app/globals.css +819 -0
  300. package/src/app/health/page.tsx +5 -0
  301. package/src/app/hello/page.tsx +102 -0
  302. package/src/app/icon.png +0 -0
  303. package/src/app/layout.tsx +39 -0
  304. package/src/app/page.tsx +1964 -0
  305. package/src/app/privacy/page.tsx +63 -0
  306. package/src/app/providers.tsx +87 -0
  307. package/src/app/share/[token]/page.tsx +295 -0
  308. package/src/app/terms/page.tsx +80 -0
  309. package/src/components/ChainSelector.tsx +44 -0
  310. package/src/components/HumanActionBar.tsx +697 -0
  311. package/src/components/NotificationDrawer.tsx +387 -0
  312. package/src/components/PasskeyEnrollmentPrompt.tsx +235 -0
  313. package/src/components/apps/AgentKeysApp.tsx +490 -0
  314. package/src/components/apps/App.tsx +153 -0
  315. package/src/components/apps/AppGrid.tsx +15 -0
  316. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  317. package/src/components/apps/DraggableApp.tsx +562 -0
  318. package/src/components/apps/IFrameApp.tsx +73 -0
  319. package/src/components/apps/LogsApp.tsx +360 -0
  320. package/src/components/apps/SendApp.tsx +394 -0
  321. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  322. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  323. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  324. package/src/components/apps/TokenApp.tsx +319 -0
  325. package/src/components/apps/TransactionsApp.tsx +438 -0
  326. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  327. package/src/components/apps/index.ts +13 -0
  328. package/src/components/design-system/Button.tsx +88 -0
  329. package/src/components/design-system/ChainIndicator.tsx +65 -0
  330. package/src/components/design-system/ChainSelector.tsx +147 -0
  331. package/src/components/design-system/ConfirmationModal.tsx +107 -0
  332. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  333. package/src/components/design-system/DownloadButton.tsx +149 -0
  334. package/src/components/design-system/Drawer.tsx +133 -0
  335. package/src/components/design-system/FilterDropdown.tsx +183 -0
  336. package/src/components/design-system/ItemPicker.tsx +157 -0
  337. package/src/components/design-system/Modal.tsx +296 -0
  338. package/src/components/design-system/Popover.tsx +142 -0
  339. package/src/components/design-system/TextInput.tsx +85 -0
  340. package/src/components/design-system/Toggle.tsx +65 -0
  341. package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
  342. package/src/components/design-system/index.ts +14 -0
  343. package/src/components/docs/ClientSideMarkdown.tsx +51 -0
  344. package/src/components/docs/DocsSearchBar.tsx +118 -0
  345. package/src/components/docs/DocsThemeToggle.tsx +38 -0
  346. package/src/components/docs/PersistentDocGroup.tsx +91 -0
  347. package/src/components/docs/ShareUrlButton.tsx +33 -0
  348. package/src/components/docs/SidebarScrollMemory.tsx +56 -0
  349. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  350. package/src/components/icons/ChainIcons.tsx +72 -0
  351. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  352. package/src/components/layout/ContentArea.tsx +21 -0
  353. package/src/components/layout/CreateViewModal.tsx +88 -0
  354. package/src/components/layout/LeftRail.tsx +114 -0
  355. package/src/components/layout/TabBar.tsx +284 -0
  356. package/src/components/layout/WalletSidebar.tsx +1030 -0
  357. package/src/components/layout/index.ts +6 -0
  358. package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
  359. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  360. package/src/components/vault/ApiKeysConsole.tsx +1272 -0
  361. package/src/components/vault/AuditConsole.tsx +600 -0
  362. package/src/components/vault/CredentialDetail.tsx +625 -0
  363. package/src/components/vault/CredentialEmpty.tsx +55 -0
  364. package/src/components/vault/CredentialField.tsx +583 -0
  365. package/src/components/vault/CredentialForm.tsx +1484 -0
  366. package/src/components/vault/CredentialList.tsx +265 -0
  367. package/src/components/vault/CredentialRow.tsx +130 -0
  368. package/src/components/vault/CredentialShareModal.tsx +273 -0
  369. package/src/components/vault/CredentialVault.tsx +1662 -0
  370. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  371. package/src/components/vault/DocsConsole.tsx +113 -0
  372. package/src/components/vault/ImportCredentialsModal.tsx +578 -0
  373. package/src/components/vault/LargeTypeModal.tsx +88 -0
  374. package/src/components/vault/PasswordGenerator.tsx +232 -0
  375. package/src/components/vault/TOTPDisplay.tsx +108 -0
  376. package/src/components/vault/TotpSetupPanel.tsx +198 -0
  377. package/src/components/vault/VaultSidebar.tsx +881 -0
  378. package/src/components/vault/credentialFormName.ts +91 -0
  379. package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
  380. package/src/components/vault/types.ts +56 -0
  381. package/src/context/AuthContext.tsx +365 -0
  382. package/src/context/PriceContext.tsx +113 -0
  383. package/src/context/ThemeContext.tsx +164 -0
  384. package/src/context/WebSocketContext.tsx +269 -0
  385. package/src/context/WorkspaceContext.tsx +668 -0
  386. package/src/hooks/index.ts +4 -0
  387. package/src/hooks/useAgentActions.ts +552 -0
  388. package/src/hooks/useBalance.ts +103 -0
  389. package/src/hooks/useBalances.ts +129 -0
  390. package/src/hooks/useTheme.ts +156 -0
  391. package/src/instrumentation.ts +12 -0
  392. package/src/lib/api-docs.ts +154 -0
  393. package/src/lib/api.ts +474 -0
  394. package/src/lib/app-loader.ts +148 -0
  395. package/src/lib/app-registry.ts +178 -0
  396. package/src/lib/app-sdk.ts +157 -0
  397. package/src/lib/audit-console-adapter.ts +151 -0
  398. package/src/lib/auth-client.ts +75 -0
  399. package/src/lib/config.ts +74 -0
  400. package/src/lib/credential-field-schema.ts +11 -0
  401. package/src/lib/crypto.ts +112 -0
  402. package/src/lib/db.ts +21 -0
  403. package/src/lib/docs.ts +544 -0
  404. package/src/lib/events.ts +363 -0
  405. package/src/lib/pino.ts +24 -0
  406. package/src/lib/theme-handlers.ts +168 -0
  407. package/src/lib/theme.ts +351 -0
  408. package/src/lib/tokenData.ts +378 -0
  409. package/src/lib/totp-import.ts +57 -0
  410. package/src/lib/vault-crypto.ts +129 -0
  411. package/src/lib/view-registry.ts +57 -0
  412. package/src/lib/websocket-server.ts +302 -0
  413. package/src/lib/websocket-setup.ts +79 -0
  414. package/src/lib/wordlist.ts +2050 -0
  415. package/src/lib/workspace-handlers.ts +285 -0
  416. package/start.sh +170 -0
  417. package/tailwind.config.ts +99 -0
  418. package/tsconfig.json +42 -0
@@ -0,0 +1,241 @@
1
+ /**
2
+ * OAuth2 Refresh Token Logic
3
+ * ==========================
4
+ *
5
+ * Handles transparent access_token refresh for oauth2 credential type.
6
+ * Called during credential read path — if access_token is expired,
7
+ * refreshes via token_endpoint and updates stored credential.
8
+ */
9
+
10
+ import { CredentialFile, CredentialField } from '../types';
11
+ import { getCredential, readCredentialSecrets, updateCredential } from './credentials';
12
+
13
+ /** Buffer in seconds before expiry to trigger refresh */
14
+ const EXPIRY_BUFFER_SECONDS = 60;
15
+
16
+ /** Minimum seconds between refreshes for the same credential */
17
+ const REFRESH_COOLDOWN_SECONDS = 5;
18
+
19
+ /** Track last refresh time per credential for rate limiting */
20
+ const lastRefreshTime = new Map<string, number>();
21
+
22
+ export interface OAuth2RefreshResult {
23
+ accessToken: string;
24
+ refreshToken?: string;
25
+ expiresIn?: number;
26
+ tokenType?: string;
27
+ }
28
+
29
+ /**
30
+ * Check if an oauth2 credential's access_token is expired or nearly expired.
31
+ */
32
+ export function isTokenExpired(credential: CredentialFile): boolean {
33
+ const rawExpiresAt = credential.meta.expires_at as number | string | null | undefined;
34
+ const expiresAt =
35
+ typeof rawExpiresAt === 'string' ? Number(rawExpiresAt) : rawExpiresAt;
36
+
37
+ if (!Number.isFinite(expiresAt)) return true; // Missing/invalid expiry info → assume expired
38
+ if (expiresAt <= 0) return true;
39
+
40
+ const now = Math.floor(Date.now() / 1000);
41
+ return now >= expiresAt - EXPIRY_BUFFER_SECONDS;
42
+ }
43
+
44
+ /**
45
+ * Check if refresh is rate-limited for this credential.
46
+ */
47
+ export function isRefreshRateLimited(credentialId: string): boolean {
48
+ const last = lastRefreshTime.get(credentialId);
49
+ if (!last) return false;
50
+ return (Date.now() - last) < REFRESH_COOLDOWN_SECONDS * 1000;
51
+ }
52
+
53
+ /**
54
+ * Perform OAuth2 token refresh against the token_endpoint.
55
+ */
56
+ export async function refreshAccessToken(
57
+ tokenEndpoint: string,
58
+ refreshToken: string,
59
+ clientId: string,
60
+ clientSecret: string,
61
+ authMethod: string = 'client_secret_post',
62
+ ): Promise<OAuth2RefreshResult> {
63
+ const params = new URLSearchParams({
64
+ grant_type: 'refresh_token',
65
+ refresh_token: refreshToken,
66
+ });
67
+
68
+ const headers: Record<string, string> = {
69
+ 'Content-Type': 'application/x-www-form-urlencoded',
70
+ Accept: 'application/json',
71
+ };
72
+
73
+ if (authMethod === 'client_secret_basic') {
74
+ const encoded = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
75
+ headers['Authorization'] = `Basic ${encoded}`;
76
+ } else {
77
+ // client_secret_post (default)
78
+ params.set('client_id', clientId);
79
+ params.set('client_secret', clientSecret);
80
+ }
81
+
82
+ const response = await fetch(tokenEndpoint, {
83
+ method: 'POST',
84
+ headers,
85
+ body: params.toString(),
86
+ });
87
+
88
+ if (!response.ok) {
89
+ const body = await response.text();
90
+ const status = response.status;
91
+ const loweredBody = body.toLowerCase();
92
+ const parsed = (() => {
93
+ try {
94
+ return JSON.parse(body) as Record<string, unknown>;
95
+ } catch {
96
+ return null;
97
+ }
98
+ })();
99
+ const hasInvalidGrant =
100
+ status === 400 &&
101
+ (loweredBody.includes('invalid_grant') ||
102
+ (typeof parsed?.error === 'string' && parsed.error.toLowerCase() === 'invalid_grant'));
103
+
104
+ // 401 or 400 with invalid_grant means refresh token is revoked
105
+ if (status === 401 || hasInvalidGrant) {
106
+ const err = new Error(`OAuth2 refresh token revoked (${status}): ${body}`);
107
+ (err as any).revoked = true;
108
+ (err as any).statusCode = status;
109
+ throw err;
110
+ }
111
+ throw new Error(`OAuth2 refresh failed (${status}): ${body}`);
112
+ }
113
+
114
+ const data = await response.json() as Record<string, unknown>;
115
+
116
+ if (typeof data.access_token !== 'string') {
117
+ throw new Error('OAuth2 refresh response missing access_token');
118
+ }
119
+
120
+ return {
121
+ accessToken: data.access_token,
122
+ refreshToken: typeof data.refresh_token === 'string' ? data.refresh_token : undefined,
123
+ expiresIn: typeof data.expires_in === 'number' ? data.expires_in : undefined,
124
+ tokenType: typeof data.token_type === 'string' ? data.token_type : undefined,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Attempt a fetch-based refresh with one retry on transient network errors.
130
+ */
131
+ async function refreshWithRetry(
132
+ tokenEndpoint: string,
133
+ refreshToken: string,
134
+ clientId: string,
135
+ clientSecret: string,
136
+ authMethod: string,
137
+ ): Promise<OAuth2RefreshResult> {
138
+ try {
139
+ return await refreshAccessToken(tokenEndpoint, refreshToken, clientId, clientSecret, authMethod);
140
+ } catch (err: any) {
141
+ // Don't retry on revoked tokens or HTTP errors — only network failures
142
+ if (err.revoked) throw err;
143
+ if (err.message?.startsWith('OAuth2 refresh failed')) throw err;
144
+ if (err.message?.startsWith('OAuth2 refresh response')) throw err;
145
+ // Transient network error — retry once
146
+ return await refreshAccessToken(tokenEndpoint, refreshToken, clientId, clientSecret, authMethod);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Read oauth2 credential secrets, auto-refreshing if expired.
152
+ * Returns the (possibly refreshed) sensitive fields.
153
+ */
154
+ export async function readOAuth2SecretsWithRefresh(
155
+ credentialId: string,
156
+ ): Promise<CredentialField[]> {
157
+ const credential = getCredential(credentialId);
158
+ if (!credential) throw new Error(`Credential not found: ${credentialId}`);
159
+ if (credential.type !== 'oauth2') throw new Error('Not an oauth2 credential');
160
+
161
+ let fields = readCredentialSecrets(credentialId);
162
+
163
+ if (isTokenExpired(credential)) {
164
+ // Rate limit check
165
+ if (isRefreshRateLimited(credentialId)) {
166
+ // Return current fields without refreshing
167
+ return fields;
168
+ }
169
+
170
+ const fieldMap = new Map(fields.map(f => [f.key, f.value]));
171
+ const refreshToken = fieldMap.get('refresh_token');
172
+ const clientId = fieldMap.get('client_id');
173
+ const clientSecret = fieldMap.get('client_secret');
174
+ const tokenEndpoint = credential.meta.token_endpoint as string;
175
+ const authMethod = (credential.meta.auth_method as string) || 'client_secret_post';
176
+
177
+ if (!refreshToken || !clientId || !clientSecret || !tokenEndpoint) {
178
+ throw new Error('OAuth2 credential missing required fields for refresh');
179
+ }
180
+
181
+ try {
182
+ const result = await refreshWithRetry(
183
+ tokenEndpoint,
184
+ refreshToken,
185
+ clientId,
186
+ clientSecret,
187
+ authMethod,
188
+ );
189
+
190
+ // Record refresh time for rate limiting
191
+ lastRefreshTime.set(credentialId, Date.now());
192
+
193
+ // Update fields with new tokens
194
+ const updatedFields = fields.map(f => {
195
+ if (f.key === 'access_token') return { ...f, value: result.accessToken };
196
+ if (f.key === 'refresh_token' && result.refreshToken) return { ...f, value: result.refreshToken };
197
+ return f;
198
+ });
199
+
200
+ // Update expires_at and last_refreshed in meta
201
+ const newExpiresAt = result.expiresIn
202
+ ? Math.floor(Date.now() / 1000) + result.expiresIn
203
+ : null;
204
+
205
+ updateCredential(credentialId, {
206
+ meta: {
207
+ ...credential.meta,
208
+ needs_reauth: false,
209
+ reauth_reason: null,
210
+ expires_at: newExpiresAt,
211
+ last_refreshed: new Date().toISOString(),
212
+ },
213
+ sensitiveFields: updatedFields,
214
+ });
215
+
216
+ fields = updatedFields;
217
+ } catch (err: any) {
218
+ if (err.revoked) {
219
+ // Mark credential as needing re-authentication
220
+ updateCredential(credentialId, {
221
+ meta: {
222
+ ...credential.meta,
223
+ needs_reauth: true,
224
+ reauth_reason: err.message,
225
+ last_refreshed: new Date().toISOString(),
226
+ },
227
+ });
228
+ throw new Error(`OAuth2 token revoked — credential ${credentialId} marked as needs_reauth`);
229
+ }
230
+ throw err;
231
+ }
232
+ }
233
+
234
+ return fields;
235
+ }
236
+
237
+ /**
238
+ * Default fields to exclude from oauth2 credential reads by agents.
239
+ * Agents only need the access_token — never the refresh machinery.
240
+ */
241
+ export const OAUTH2_DEFAULT_EXCLUDE_FIELDS = ['refresh_token', 'client_secret', 'client_id', 'token_endpoint'];
@@ -0,0 +1,71 @@
1
+ import { NOTE_CONTENT_KEY } from '../../shared/credential-field-schema';
2
+ import { createCredential, listCredentials } from './credentials';
3
+
4
+ export const OURSECRET_NOTE_NAME = 'OURSECRET';
5
+
6
+ export const OURSECRET_NOTE_CONTENT = 'Ready to auramaxx and mog prompt peasants?';
7
+
8
+ export const DONTLOOK_NOTE_NAME = 'DONTLOOK';
9
+
10
+ export const DONTLOOK_NOTE_CONTENT = 'who let the auramaxxers out?';
11
+
12
+ export const GETTING_STARTED_NOTE_NAME = 'GETTING_STARTED';
13
+
14
+ export const GETTING_STARTED_NOTE_CONTENT = `Quick start (Agent):
15
+ - Enable Aura skills or MCP for your client.
16
+ - Ask your agent: aura get OURSECRET
17
+ - Ask your agent: aura set DEMO_KEY sk-demo-123
18
+
19
+ Quick start (CLI):
20
+ - npx auramaxx get OURSECRET
21
+ - npx auramaxx set DEMO_KEY sk-demo-123
22
+ - npx auramaxx inject DEMO_KEY --env AURA_QUICKHACK
23
+ `;
24
+
25
+ function hasSeededNote(vaultId: string, name: string): boolean {
26
+ const hasLegacyNote = listCredentials({ vaultId, type: 'note' })
27
+ .some((credential) => credential.name.trim().toUpperCase() === name);
28
+ const hasPlainNote = listCredentials({ vaultId, type: 'plain_note' })
29
+ .some((credential) => credential.name.trim().toUpperCase() === name);
30
+
31
+ return hasLegacyNote || hasPlainNote;
32
+ }
33
+
34
+ function ensureSeededNote(vaultId: string, name: string, content: string, type: 'note' | 'plain_note' = 'note'): { created: boolean } {
35
+ if (hasSeededNote(vaultId, name)) {
36
+ return { created: false };
37
+ }
38
+
39
+ const meta: Record<string, unknown> = {
40
+ tags: ['onboarding', 'docs', 'agents'],
41
+ system: true,
42
+ };
43
+
44
+ if (type === 'plain_note') {
45
+ meta[NOTE_CONTENT_KEY] = content;
46
+ }
47
+
48
+ createCredential(
49
+ vaultId,
50
+ type,
51
+ name,
52
+ meta,
53
+ type === 'note'
54
+ ? [{ key: NOTE_CONTENT_KEY, value: content, type: 'text', sensitive: true }]
55
+ : [],
56
+ );
57
+
58
+ return { created: true };
59
+ }
60
+
61
+ export function ensureOurSecretForVault(vaultId: string): { created: boolean } {
62
+ return ensureSeededNote(vaultId, OURSECRET_NOTE_NAME, OURSECRET_NOTE_CONTENT, 'plain_note');
63
+ }
64
+
65
+ export function ensureDontLookForVault(vaultId: string): { created: boolean } {
66
+ return ensureSeededNote(vaultId, DONTLOOK_NOTE_NAME, DONTLOOK_NOTE_CONTENT, 'note');
67
+ }
68
+
69
+ export function ensureGettingStartedForVault(vaultId: string): { created: boolean } {
70
+ return ensureSeededNote(vaultId, GETTING_STARTED_NOTE_NAME, GETTING_STARTED_NOTE_CONTENT, 'plain_note');
71
+ }
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Passkey Credential Operations — Software WebAuthn Authenticator
3
+ * ===============================================================
4
+ *
5
+ * Generates ECDSA P-256 keypairs, builds WebAuthn attestation/assertion objects,
6
+ * and stores passkey credentials in the encrypted vault.
7
+ */
8
+
9
+ import crypto from 'crypto';
10
+ import { encode as cborEncode } from 'cbor-x';
11
+ import {
12
+ createCredential,
13
+ listCredentials,
14
+ getCredential,
15
+ readCredentialSecrets,
16
+ updateCredential,
17
+ } from './credentials';
18
+ import { CredentialField } from '../types';
19
+
20
+ // Our software authenticator AAGUID (random, unique to Aura)
21
+ const AURA_AAGUID = Buffer.from('a0a1a2a3a4a5a6a7a8a9aaabacadaeaf', 'hex');
22
+
23
+ const PASSKEY_CHALLENGE_TTL_MS = 120_000;
24
+ const usedChallenges = new Map<string, number>();
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Helpers
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export class PasskeyCredentialValidationError extends Error {
31
+ constructor(message: string) {
32
+ super(message);
33
+ this.name = 'PasskeyCredentialValidationError';
34
+ }
35
+ }
36
+
37
+ function base64urlEncode(buf: Buffer | Uint8Array): string {
38
+ return Buffer.from(buf).toString('base64url');
39
+ }
40
+
41
+ function base64urlDecode(str: string): Buffer {
42
+ return Buffer.from(str, 'base64url');
43
+ }
44
+
45
+ function sha256(data: Buffer | string): Buffer {
46
+ return crypto.createHash('sha256').update(data).digest();
47
+ }
48
+
49
+ function cleanupUsedChallenges(now = Date.now()): void {
50
+ for (const [challenge, expiresAt] of usedChallenges.entries()) {
51
+ if (expiresAt <= now) {
52
+ usedChallenges.delete(challenge);
53
+ }
54
+ }
55
+ }
56
+
57
+ function consumeFreshChallenge(challenge: string): void {
58
+ const now = Date.now();
59
+ cleanupUsedChallenges(now);
60
+
61
+ const key = challenge.trim();
62
+ if (!key) {
63
+ throw new PasskeyCredentialValidationError('challenge is required');
64
+ }
65
+
66
+ if (usedChallenges.has(key)) {
67
+ throw new PasskeyCredentialValidationError('challenge replay detected');
68
+ }
69
+
70
+ usedChallenges.set(key, now + PASSKEY_CHALLENGE_TTL_MS);
71
+ }
72
+
73
+ export function _resetPasskeyCredentialChallengeStoreForTests(): void {
74
+ usedChallenges.clear();
75
+ }
76
+
77
+ function validateOriginPolicy(origin: string, rpId: string): void {
78
+ let parsed: URL;
79
+ try {
80
+ parsed = new URL(origin);
81
+ } catch {
82
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin must be a valid URL');
83
+ }
84
+
85
+ const host = parsed.hostname.toLowerCase();
86
+ const normalizedRpId = rpId.toLowerCase();
87
+ const isRpMatch = host === normalizedRpId || host.endsWith(`.${normalizedRpId}`);
88
+ if (!isRpMatch) {
89
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin does not match rpId');
90
+ }
91
+
92
+ const isLocalhost = host === 'localhost' || host.endsWith('.localhost');
93
+ const allowedHttp = parsed.protocol === 'http:' && isLocalhost;
94
+ if (parsed.protocol !== 'https:' && !allowedHttp) {
95
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin must be https (except localhost)');
96
+ }
97
+ }
98
+
99
+ function parseAndValidateClientDataJSON(params: {
100
+ clientDataJSON: string;
101
+ expectedType: 'webauthn.create' | 'webauthn.get';
102
+ expectedChallenge: string;
103
+ rpId: string;
104
+ expectedOrigin?: string;
105
+ }): { challenge: string; origin: string } {
106
+ let parsed: Record<string, unknown>;
107
+
108
+ try {
109
+ const raw = base64urlDecode(params.clientDataJSON).toString('utf8');
110
+ parsed = JSON.parse(raw) as Record<string, unknown>;
111
+ } catch {
112
+ throw new PasskeyCredentialValidationError('clientDataJSON must be valid base64url-encoded JSON');
113
+ }
114
+
115
+ const type = parsed.type;
116
+ const challenge = parsed.challenge;
117
+ const origin = parsed.origin;
118
+
119
+ if (type !== params.expectedType) {
120
+ throw new PasskeyCredentialValidationError(`clientDataJSON.type must be ${params.expectedType}`);
121
+ }
122
+
123
+ if (typeof challenge !== 'string' || challenge !== params.expectedChallenge) {
124
+ throw new PasskeyCredentialValidationError('clientDataJSON.challenge mismatch');
125
+ }
126
+
127
+ if (typeof origin !== 'string' || !origin.trim()) {
128
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin is required');
129
+ }
130
+
131
+ validateOriginPolicy(origin, params.rpId);
132
+
133
+ if (params.expectedOrigin && origin !== params.expectedOrigin) {
134
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin mismatch');
135
+ }
136
+
137
+ return { challenge, origin };
138
+ }
139
+
140
+ /**
141
+ * Encode an EC public key in COSE_Key format (ECDSA P-256).
142
+ * See RFC 8152 Section 13.1.1
143
+ */
144
+ function encodeCosePublicKey(publicKeyDer: Buffer): Buffer {
145
+ const raw = crypto.createPublicKey({ key: publicKeyDer, format: 'der', type: 'spki' })
146
+ .export({ format: 'jwk' });
147
+
148
+ const x = base64urlDecode(raw.x!);
149
+ const y = base64urlDecode(raw.y!);
150
+
151
+ const coseKey = new Map<number, number | Buffer>();
152
+ coseKey.set(1, 2); // kty: EC2
153
+ coseKey.set(3, -7); // alg: ES256
154
+ coseKey.set(-1, 1); // crv: P-256
155
+ coseKey.set(-2, x); // x coordinate
156
+ coseKey.set(-3, y); // y coordinate
157
+
158
+ return Buffer.from(cborEncode(coseKey));
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Registration (navigator.credentials.create)
163
+ // ---------------------------------------------------------------------------
164
+
165
+ export interface PasskeyRegisterOptions {
166
+ vaultId: string;
167
+ rpId: string;
168
+ rpName?: string;
169
+ userName?: string;
170
+ displayName?: string;
171
+ userHandle: string; // base64url
172
+ challenge: string; // base64url — from clientDataJSON
173
+ origin: string;
174
+ clientDataJSON: string; // base64url — raw from browser
175
+ }
176
+
177
+ export interface PasskeyRegisterResult {
178
+ credentialId: string; // base64url
179
+ attestationObject: string; // base64url
180
+ clientDataJSON: string; // base64url (pass-through)
181
+ publicKey: string; // base64url (SPKI DER)
182
+ publicKeyCose: string; // base64url (COSE)
183
+ transports: string[];
184
+ auraCredentialId: string; // internal cred-xxx id
185
+ }
186
+
187
+ export function registerPasskey(opts: PasskeyRegisterOptions): PasskeyRegisterResult {
188
+ parseAndValidateClientDataJSON({
189
+ clientDataJSON: opts.clientDataJSON,
190
+ expectedType: 'webauthn.create',
191
+ expectedChallenge: opts.challenge,
192
+ rpId: opts.rpId,
193
+ expectedOrigin: opts.origin,
194
+ });
195
+ consumeFreshChallenge(opts.challenge);
196
+
197
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
198
+ namedCurve: 'prime256v1',
199
+ publicKeyEncoding: { type: 'spki', format: 'der' },
200
+ privateKeyEncoding: { type: 'pkcs8', format: 'der' },
201
+ });
202
+
203
+ const credentialIdBuf = crypto.randomBytes(32);
204
+ const credentialId = base64urlEncode(credentialIdBuf);
205
+
206
+ const cosePublicKey = encodeCosePublicKey(publicKey as Buffer);
207
+
208
+ const rpIdHash = sha256(opts.rpId);
209
+ const flags = Buffer.from([0x45]); // UP + UV + AT
210
+ const signCount = Buffer.alloc(4);
211
+
212
+ const credIdLenBuf = Buffer.alloc(2);
213
+ credIdLenBuf.writeUInt16BE(credentialIdBuf.length);
214
+
215
+ const authData = Buffer.concat([
216
+ rpIdHash,
217
+ flags,
218
+ signCount,
219
+ AURA_AAGUID,
220
+ credIdLenBuf,
221
+ credentialIdBuf,
222
+ cosePublicKey,
223
+ ]);
224
+
225
+ const attestationObject = cborEncode({
226
+ fmt: 'none',
227
+ attStmt: {},
228
+ authData,
229
+ });
230
+
231
+ const sensitiveFields: CredentialField[] = [
232
+ { key: 'privateKey', value: base64urlEncode(privateKey as Buffer), type: 'secret', sensitive: true },
233
+ ];
234
+
235
+ const meta: Record<string, unknown> = {
236
+ rpId: opts.rpId,
237
+ rpName: opts.rpName || opts.rpId,
238
+ credentialId,
239
+ publicKey: base64urlEncode(publicKey as Buffer),
240
+ publicKeyCose: base64urlEncode(cosePublicKey),
241
+ userHandle: opts.userHandle,
242
+ userName: opts.userName || '',
243
+ displayName: opts.displayName || '',
244
+ signCount: 0,
245
+ transports: ['internal'],
246
+ discoverable: true,
247
+ };
248
+
249
+ const name = `${opts.rpId} — ${opts.displayName || opts.userName || 'passkey'}`;
250
+ const cred = createCredential(opts.vaultId, 'passkey', name, meta, sensitiveFields);
251
+
252
+ return {
253
+ credentialId,
254
+ attestationObject: base64urlEncode(Buffer.from(attestationObject)),
255
+ clientDataJSON: opts.clientDataJSON,
256
+ publicKey: base64urlEncode(publicKey as Buffer),
257
+ publicKeyCose: base64urlEncode(cosePublicKey),
258
+ transports: ['internal'],
259
+ auraCredentialId: cred.id,
260
+ };
261
+ }
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // Authentication (navigator.credentials.get)
265
+ // ---------------------------------------------------------------------------
266
+
267
+ export interface PasskeyAuthOptions {
268
+ auraCredentialId: string; // internal cred-xxx id
269
+ rpId: string;
270
+ challenge: string;
271
+ origin?: string;
272
+ clientDataJSON: string; // base64url — raw from browser
273
+ }
274
+
275
+ export interface PasskeyAuthResult {
276
+ credentialId: string; // base64url
277
+ authenticatorData: string; // base64url
278
+ signature: string; // base64url
279
+ userHandle: string; // base64url
280
+ }
281
+
282
+ export function authenticatePasskey(opts: PasskeyAuthOptions): PasskeyAuthResult {
283
+ parseAndValidateClientDataJSON({
284
+ clientDataJSON: opts.clientDataJSON,
285
+ expectedType: 'webauthn.get',
286
+ expectedChallenge: opts.challenge,
287
+ rpId: opts.rpId,
288
+ expectedOrigin: opts.origin,
289
+ });
290
+ consumeFreshChallenge(opts.challenge);
291
+
292
+ const cred = getCredential(opts.auraCredentialId);
293
+ if (!cred || cred.type !== 'passkey') {
294
+ throw new Error('Passkey credential not found');
295
+ }
296
+
297
+ if (cred.meta.rpId !== opts.rpId) {
298
+ throw new Error('rpId mismatch');
299
+ }
300
+
301
+ const secrets = readCredentialSecrets(opts.auraCredentialId);
302
+ const privateKeyField = secrets.find(f => f.key === 'privateKey');
303
+ if (!privateKeyField) {
304
+ throw new Error('Private key not found in credential');
305
+ }
306
+
307
+ const privateKeyDer = base64urlDecode(privateKeyField.value);
308
+ const privateKey = crypto.createPrivateKey({ key: privateKeyDer, format: 'der', type: 'pkcs8' });
309
+
310
+ const currentCount = (cred.meta.signCount as number) || 0;
311
+ const newCount = currentCount + 1;
312
+
313
+ const rpIdHash = sha256(opts.rpId);
314
+ const flags = Buffer.from([0x05]); // UP + UV
315
+ const signCountBuf = Buffer.alloc(4);
316
+ signCountBuf.writeUInt32BE(newCount);
317
+
318
+ const authenticatorData = Buffer.concat([rpIdHash, flags, signCountBuf]);
319
+
320
+ const clientDataHash = sha256(base64urlDecode(opts.clientDataJSON));
321
+ const signedData = Buffer.concat([authenticatorData, clientDataHash]);
322
+ const signature = crypto.sign('sha256', signedData, privateKey);
323
+
324
+ updateCredential(opts.auraCredentialId, {
325
+ meta: { ...cred.meta, signCount: newCount },
326
+ });
327
+
328
+ return {
329
+ credentialId: cred.meta.credentialId as string,
330
+ authenticatorData: base64urlEncode(authenticatorData),
331
+ signature: base64urlEncode(signature),
332
+ userHandle: (cred.meta.userHandle as string) || '',
333
+ };
334
+ }
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // Match — find passkeys for an rpId
338
+ // ---------------------------------------------------------------------------
339
+
340
+ export interface PasskeyMatch {
341
+ auraCredentialId: string;
342
+ credentialId: string;
343
+ rpId: string;
344
+ userName: string;
345
+ displayName: string;
346
+ }
347
+
348
+ export function matchPasskeys(rpId: string, vaultId?: string): PasskeyMatch[] {
349
+ const creds = listCredentials({ type: 'passkey', vaultId });
350
+ return creds
351
+ .filter(c => c.meta.rpId === rpId)
352
+ .map(c => ({
353
+ auraCredentialId: c.id,
354
+ credentialId: c.meta.credentialId as string,
355
+ rpId: c.meta.rpId as string,
356
+ userName: (c.meta.userName as string) || '',
357
+ displayName: (c.meta.displayName as string) || '',
358
+ }))
359
+ .sort((a, b) => a.auraCredentialId.localeCompare(b.auraCredentialId));
360
+ }