auramaxx 1.0.0-alpha.4

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 (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,54 @@
1
+ import { Router, Request, Response } from 'express';
2
+ import { lock, isUnlocked, lockVault, isVaultUnlocked } from '../lib/cold';
3
+ import { requireWalletAuth } from '../middleware/auth';
4
+ import { requireAdmin } from '../lib/permissions';
5
+ import { logger } from '../lib/logger';
6
+ import { revokeAllTokens } from '../lib/sessions';
7
+ import { revokeAdminTokens } from '../lib/auth';
8
+
9
+ const router = Router();
10
+
11
+ // POST /lock - Lock all vaults (clear memory)
12
+ // Requires admin authentication
13
+ // Also revokes all active sessions so clients must re-authenticate.
14
+ router.post('/', requireWalletAuth, requireAdmin, async (_req: Request, res: Response) => {
15
+ const wasUnlocked = isUnlocked();
16
+
17
+ // Lock all vaults
18
+ lock();
19
+
20
+ // Revoke all active admin + agent tokens
21
+ revokeAdminTokens();
22
+ await revokeAllTokens();
23
+
24
+ // Log the lock event
25
+ if (wasUnlocked) {
26
+ logger.locked();
27
+ }
28
+
29
+ res.json({
30
+ success: true,
31
+ message: wasUnlocked
32
+ ? 'All vaults locked and active sessions revoked'
33
+ : 'Vaults were already locked; active sessions revoked'
34
+ });
35
+ });
36
+
37
+ // POST /lock/:vaultId - Lock a specific vault
38
+ router.post('/:vaultId', requireWalletAuth, requireAdmin, (req: Request<{ vaultId: string }>, res: Response) => {
39
+ const { vaultId } = req.params;
40
+ const wasUnlocked = isVaultUnlocked(vaultId);
41
+
42
+ lockVault(vaultId);
43
+
44
+ if (wasUnlocked) {
45
+ logger.locked();
46
+ }
47
+
48
+ res.json({
49
+ success: true,
50
+ message: wasUnlocked ? `Vault ${vaultId} locked` : `Vault ${vaultId} was already locked`
51
+ });
52
+ });
53
+
54
+ export default router;
@@ -0,0 +1,68 @@
1
+ import { Router, Request, Response } from 'express';
2
+ import { prisma } from '../lib/db';
3
+ import { getErrorMessage } from '../lib/error';
4
+
5
+ const router = Router();
6
+
7
+ // GET /logs - Event logs - fetch historical events from database
8
+ // Supports filtering by: type, category (prefix match), agentId, since/until (timestamps), path
9
+ router.get('/', async (req: Request, res: Response) => {
10
+ try {
11
+ const limit = Math.min(parseInt(req.query.limit as string) || 50, 250);
12
+ const offset = parseInt(req.query.offset as string) || 0;
13
+ const type = req.query.type as string | undefined;
14
+ const category = req.query.category as string | undefined;
15
+ const agentId = req.query.agentId as string | undefined;
16
+ const since = req.query.since as string | undefined;
17
+ const until = req.query.until as string | undefined;
18
+
19
+ const where: Record<string, unknown> = {};
20
+
21
+ if (type) {
22
+ where.type = type;
23
+ } else if (category) {
24
+ // Prefix match: category=auth matches auth:unlocked, auth:auth_failed, etc.
25
+ where.type = { startsWith: `${category}:` };
26
+ }
27
+
28
+ // Filter by agentId in the JSON data field
29
+ if (agentId) {
30
+ where.data = { contains: `"agentId":"${agentId}"` };
31
+ }
32
+
33
+ // Date range filtering
34
+ if (since || until) {
35
+ const timestampFilter: Record<string, Date> = {};
36
+ if (since) timestampFilter.gte = new Date(parseInt(since) || since);
37
+ if (until) timestampFilter.lte = new Date(parseInt(until) || until);
38
+ where.timestamp = timestampFilter;
39
+ }
40
+
41
+ const [logs, total] = await Promise.all([
42
+ prisma.event.findMany({
43
+ where,
44
+ orderBy: { timestamp: 'desc' },
45
+ take: limit,
46
+ skip: offset,
47
+ }),
48
+ prisma.event.count({ where }),
49
+ ]);
50
+
51
+ res.json({
52
+ success: true,
53
+ logs,
54
+ count: logs.length,
55
+ total,
56
+ pagination: {
57
+ limit,
58
+ offset,
59
+ hasMore: offset + logs.length < total,
60
+ },
61
+ });
62
+ } catch (error) {
63
+ const message = getErrorMessage(error);
64
+ res.status(500).json({ success: false, error: message });
65
+ }
66
+ });
67
+
68
+ export default router;
@@ -0,0 +1,111 @@
1
+ import { Router, Request, Response } from 'express';
2
+ import fs from 'fs';
3
+ import { deleteColdWallet, importColdWallet, listVaults, deleteVault } from '../lib/cold';
4
+ import { DATA_PATHS } from '../lib/config';
5
+ import { prisma } from '../lib/db';
6
+ import { lockAllCredentialVaults } from '../lib/credential-vault';
7
+ import { requireWalletAuth } from '../middleware/auth';
8
+ import { requireAdmin } from '../lib/permissions';
9
+ import { parseEncryptedPassword } from '../lib/transport';
10
+ import { logger } from '../lib/logger';
11
+ import { getErrorMessage, HttpError } from '../lib/error';
12
+
13
+ const router = Router();
14
+
15
+ // POST /nuke - Delete all wallet data (requires admin)
16
+ router.post('/', requireWalletAuth, requireAdmin, async (_req: Request, res: Response) => {
17
+ try {
18
+ // 1. Delete all vaults (files + memory)
19
+ try {
20
+ const vaults = listVaults();
21
+ for (const vault of vaults) {
22
+ deleteVault(vault.id);
23
+ }
24
+ deleteColdWallet(); // Also handles legacy cold.json
25
+ } catch (e) {
26
+ console.log('No cold wallet to delete:', e);
27
+ }
28
+
29
+ // 2. Delete all hot wallet files
30
+ const hotDir = DATA_PATHS.hotWallets;
31
+ if (fs.existsSync(hotDir)) {
32
+ fs.rmSync(hotDir, { recursive: true, force: true });
33
+ fs.mkdirSync(hotDir, { recursive: true });
34
+ }
35
+
36
+ // 3. Delete pending requests directory
37
+ const pendingDir = DATA_PATHS.pending;
38
+ if (fs.existsSync(pendingDir)) {
39
+ fs.rmSync(pendingDir, { recursive: true, force: true });
40
+ fs.mkdirSync(pendingDir, { recursive: true });
41
+ }
42
+
43
+ // 4. Delete credential files directory and clear in-memory credential sessions
44
+ const credentialsDir = DATA_PATHS.credentials;
45
+ if (fs.existsSync(credentialsDir)) {
46
+ fs.rmSync(credentialsDir, { recursive: true, force: true });
47
+ }
48
+ fs.mkdirSync(credentialsDir, { recursive: true });
49
+ lockAllCredentialVaults();
50
+
51
+ // 5. Clear database tables
52
+ try {
53
+ await prisma.humanAction.deleteMany({});
54
+ await prisma.agentToken.deleteMany({});
55
+ await prisma.hotWallet.deleteMany({});
56
+ } catch (e) {
57
+ console.log('Error clearing database:', e);
58
+ }
59
+
60
+ logger.nuke();
61
+
62
+ res.json({
63
+ success: true,
64
+ message: 'All data nuked. Ready for fresh setup.'
65
+ });
66
+ } catch (error) {
67
+ console.error('[NUKE ERROR]', error);
68
+ res.status(500).json({
69
+ success: false,
70
+ error: getErrorMessage(error)
71
+ });
72
+ }
73
+ });
74
+
75
+ // POST /nuke/import - Import wallet from seed phrase (requires admin)
76
+ router.post('/import', requireWalletAuth, requireAdmin, (req: Request, res: Response) => {
77
+ try {
78
+ const { mnemonic, password, encrypted } = req.body;
79
+
80
+ if (!mnemonic || typeof mnemonic !== 'string') {
81
+ res.status(400).json({ error: 'Seed phrase is required' });
82
+ return;
83
+ }
84
+
85
+ // Support encrypted password transport (dashboard) or plaintext (CLI)
86
+ let plainPassword: string;
87
+ if (encrypted && typeof encrypted === 'string') {
88
+ plainPassword = parseEncryptedPassword(encrypted);
89
+ } else if (password && typeof password === 'string') {
90
+ if (password.length < 8) {
91
+ throw new HttpError(400, 'Password must be at least 8 characters');
92
+ }
93
+ plainPassword = password;
94
+ } else {
95
+ throw new HttpError(400, 'Password is required (encrypted or plaintext)');
96
+ }
97
+
98
+ const result = importColdWallet(mnemonic, plainPassword);
99
+
100
+ res.json({
101
+ success: true,
102
+ address: result.address,
103
+ message: 'Wallet imported successfully'
104
+ });
105
+ } catch (error) {
106
+ if (error instanceof HttpError) { res.status(error.status).json({ error: error.message }); return; }
107
+ res.status(400).json({ error: getErrorMessage(error) });
108
+ }
109
+ });
110
+
111
+ export default router;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Passkey Credential Routes — WebAuthn software authenticator endpoints
3
+ * =====================================================================
4
+ *
5
+ * POST /credentials/passkey/register — Generate keypair, store, return attestation
6
+ * POST /credentials/passkey/authenticate — Sign challenge, return assertion
7
+ * GET /credentials/passkey/match — Find passkeys matching rpId
8
+ */
9
+
10
+ import { Router, Request, Response } from 'express';
11
+ import { requireWalletAuth } from '../middleware/auth';
12
+ import { registerPasskey, authenticatePasskey, matchPasskeys, PasskeyCredentialValidationError } from '../lib/passkey-credential';
13
+ import { getErrorMessage } from '../lib/error';
14
+ import { log } from '../lib/pino';
15
+
16
+ const router = Router();
17
+ router.use(requireWalletAuth);
18
+
19
+ // POST /credentials/passkey/register
20
+ router.post('/register', (req: Request, res: Response) => {
21
+ try {
22
+ const { vaultId, rpId, rpName, userName, displayName, userHandle, challenge, origin, clientDataJSON } = req.body;
23
+
24
+ if (!vaultId || !rpId || !userHandle || !clientDataJSON) {
25
+ res.status(400).json({ error: 'vaultId, rpId, userHandle, and clientDataJSON are required' });
26
+ return;
27
+ }
28
+
29
+ const result = registerPasskey({
30
+ vaultId,
31
+ rpId,
32
+ rpName,
33
+ userName,
34
+ displayName,
35
+ userHandle,
36
+ challenge,
37
+ origin,
38
+ clientDataJSON,
39
+ });
40
+
41
+ log.info('Passkey registered', { rpId, credentialId: result.credentialId });
42
+ res.json(result);
43
+ } catch (error) {
44
+ const message = getErrorMessage(error);
45
+ if (error instanceof PasskeyCredentialValidationError) {
46
+ res.status(400).json({ error: message });
47
+ return;
48
+ }
49
+
50
+ log.error('Passkey register error', { error: message });
51
+ res.status(500).json({ error: message });
52
+ }
53
+ });
54
+
55
+ // POST /credentials/passkey/authenticate
56
+ router.post('/authenticate', (req: Request, res: Response) => {
57
+ try {
58
+ const { auraCredentialId, rpId, challenge, origin, clientDataJSON } = req.body;
59
+
60
+ if (!auraCredentialId || !rpId || !challenge || !clientDataJSON) {
61
+ res.status(400).json({ error: 'auraCredentialId, rpId, challenge, and clientDataJSON are required' });
62
+ return;
63
+ }
64
+
65
+ const result = authenticatePasskey({ auraCredentialId, rpId, challenge, origin, clientDataJSON });
66
+
67
+ log.info('Passkey authenticated', { rpId, credentialId: result.credentialId });
68
+ res.json(result);
69
+ } catch (error) {
70
+ const message = getErrorMessage(error);
71
+ if (error instanceof PasskeyCredentialValidationError) {
72
+ res.status(400).json({ error: message });
73
+ return;
74
+ }
75
+
76
+ log.error('Passkey authenticate error', { error: message });
77
+ res.status(500).json({ error: message });
78
+ }
79
+ });
80
+
81
+ // GET /credentials/passkey/match?rpId=xxx
82
+ router.get('/match', (req: Request, res: Response) => {
83
+ try {
84
+ const rpId = req.query.rpId as string;
85
+ const vaultId = req.query.vaultId as string | undefined;
86
+
87
+ if (!rpId) {
88
+ res.status(400).json({ error: 'rpId query parameter is required' });
89
+ return;
90
+ }
91
+
92
+ const matches = matchPasskeys(rpId, vaultId);
93
+ res.json({ matches });
94
+ } catch (error) {
95
+ res.status(500).json({ error: getErrorMessage(error) });
96
+ }
97
+ });
98
+
99
+ export default router;
@@ -0,0 +1,346 @@
1
+ import { Router, Request, Response } from 'express';
2
+ import {
3
+ generateRegistrationOptions,
4
+ verifyRegistrationResponse,
5
+ generateAuthenticationOptions,
6
+ verifyAuthenticationResponse,
7
+ } from '@simplewebauthn/server';
8
+ import { prisma } from '../lib/db';
9
+ import { requireWalletAuth } from '../middleware/auth';
10
+ import { requireAdmin } from '../lib/permissions';
11
+ import { isUnlocked } from '../lib/cold';
12
+ import { createAdminToken } from '../lib/auth';
13
+ import { storeChallenge, consumeChallenge, uint8ArrayToBase64url, base64urlToUint8Array } from '../lib/passkey';
14
+ import { isValidAgentPubkey, normalizeAgentPubkey } from '../lib/credential-transport';
15
+ import { log } from '../lib/pino';
16
+ import { getErrorMessage } from '../lib/error';
17
+
18
+ const router = Router();
19
+
20
+ // Helper: extract rpId from request
21
+ function getRpId(req: Request, bodyRpId?: string): string {
22
+ return bodyRpId || req.hostname || 'localhost';
23
+ }
24
+
25
+ // ─── Status ─────────────────────────────────────────────────────────────────
26
+
27
+ // GET /auth/passkey/status — public, returns whether passkeys registered for origin
28
+ router.get('/status', async (req: Request, res: Response) => {
29
+ try {
30
+ const rpId = getRpId(req, req.query.rpId as string | undefined);
31
+ const passkeys = await prisma.passkey.findMany({
32
+ where: { rpId },
33
+ select: { credentialId: true, createdAt: true },
34
+ });
35
+ res.json({
36
+ registered: passkeys.length > 0,
37
+ count: passkeys.length,
38
+ rpId,
39
+ credentials: passkeys.map(p => ({ id: p.credentialId, createdAt: p.createdAt })),
40
+ });
41
+ } catch (error) {
42
+ res.status(500).json({ error: getErrorMessage(error) });
43
+ }
44
+ });
45
+
46
+ // ─── Registration ───────────────────────────────────────────────────────────
47
+
48
+ // POST /auth/passkey/register/options — requires admin token
49
+ router.post('/register/options', requireWalletAuth, requireAdmin, async (req: Request, res: Response) => {
50
+ try {
51
+ if (!isUnlocked()) {
52
+ res.status(400).json({ error: 'Vault must be unlocked to register passkey' });
53
+ return;
54
+ }
55
+
56
+ const rpId = getRpId(req, req.body?.rpId);
57
+
58
+ // Get existing credentials for this rpId to exclude
59
+ const existing = await prisma.passkey.findMany({
60
+ where: { rpId },
61
+ select: { credentialId: true, transports: true },
62
+ });
63
+
64
+ const excludeCredentials = existing.map((p) => ({
65
+ id: p.credentialId,
66
+ transports: JSON.parse(p.transports || '[]'),
67
+ }));
68
+
69
+ const options = await generateRegistrationOptions({
70
+ rpName: 'AuraWallet',
71
+ rpID: rpId,
72
+ userName: 'owner',
73
+ userDisplayName: 'Vault Owner',
74
+ attestationType: 'none',
75
+ authenticatorSelection: {
76
+ authenticatorAttachment: 'platform',
77
+ userVerification: 'required',
78
+ residentKey: 'preferred',
79
+ },
80
+ excludeCredentials,
81
+ timeout: 60000,
82
+ });
83
+
84
+ // Store challenge
85
+ storeChallenge(options.challenge, 'register');
86
+
87
+ res.json(options);
88
+ } catch (error) {
89
+ res.status(500).json({ error: getErrorMessage(error) });
90
+ }
91
+ });
92
+
93
+ // POST /auth/passkey/register/verify — requires admin token
94
+ router.post('/register/verify', requireWalletAuth, requireAdmin, async (req: Request, res: Response) => {
95
+ try {
96
+ if (!isUnlocked()) {
97
+ res.status(400).json({ error: 'Vault must be unlocked to register passkey' });
98
+ return;
99
+ }
100
+
101
+ const { credential } = req.body;
102
+ if (!credential) {
103
+ res.status(400).json({ error: 'credential is required' });
104
+ return;
105
+ }
106
+
107
+ const rpId = getRpId(req, req.body?.rpId);
108
+ const origin = `${req.protocol}://${req.get('host')}`;
109
+
110
+ // Consume challenge
111
+ const challenge = credential.response?.clientDataJSON
112
+ ? JSON.parse(Buffer.from(credential.response.clientDataJSON, 'base64url').toString()).challenge
113
+ : undefined;
114
+
115
+ if (!challenge || !consumeChallenge(challenge, 'register')) {
116
+ res.status(400).json({ error: 'Invalid or expired challenge' });
117
+ return;
118
+ }
119
+
120
+ const verification = await verifyRegistrationResponse({
121
+ response: credential,
122
+ expectedChallenge: challenge,
123
+ expectedOrigin: origin,
124
+ expectedRPID: rpId,
125
+ requireUserVerification: true,
126
+ });
127
+
128
+ if (!verification.verified || !verification.registrationInfo) {
129
+ res.status(400).json({ error: 'Registration verification failed' });
130
+ return;
131
+ }
132
+
133
+ const { credential: cred, credentialBackedUp, aaguid } = verification.registrationInfo;
134
+
135
+ // Store in DB
136
+ await prisma.passkey.create({
137
+ data: {
138
+ credentialId: cred.id,
139
+ publicKey: Buffer.from(cred.publicKey),
140
+ counter: cred.counter,
141
+ transports: JSON.stringify(credential.response.transports || []),
142
+ rpId,
143
+ aaguid: aaguid || '',
144
+ },
145
+ });
146
+
147
+ res.json({
148
+ success: true,
149
+ credentialId: cred.id,
150
+ });
151
+ } catch (error) {
152
+ res.status(500).json({ error: getErrorMessage(error) });
153
+ }
154
+ });
155
+
156
+ // ─── Authentication ─────────────────────────────────────────────────────────
157
+
158
+ // POST /auth/passkey/authenticate/options — public (vault must be unlocked server-side)
159
+ router.post('/authenticate/options', async (req: Request, res: Response) => {
160
+ try {
161
+ // Specific vault_locked error per audit
162
+ if (!isUnlocked()) {
163
+ res.status(400).json({
164
+ error: 'vault_locked',
165
+ message: 'Password required after server restart',
166
+ });
167
+ return;
168
+ }
169
+
170
+ const rpId = getRpId(req, req.body?.rpId);
171
+
172
+ // Get credentials for this rpId — reject if none exist (audit item)
173
+ const passkeys = await prisma.passkey.findMany({
174
+ where: { rpId },
175
+ select: { credentialId: true, transports: true },
176
+ });
177
+
178
+ if (passkeys.length === 0) {
179
+ res.status(400).json({ error: 'No passkeys registered for this origin' });
180
+ return;
181
+ }
182
+
183
+ const allowCredentials = passkeys.map((p) => ({
184
+ id: p.credentialId,
185
+ transports: JSON.parse(p.transports || '[]'),
186
+ }));
187
+
188
+ const options = await generateAuthenticationOptions({
189
+ rpID: rpId,
190
+ allowCredentials,
191
+ userVerification: 'required',
192
+ timeout: 60000,
193
+ });
194
+
195
+ storeChallenge(options.challenge, 'authenticate');
196
+
197
+ res.json(options);
198
+ } catch (error) {
199
+ res.status(500).json({ error: getErrorMessage(error) });
200
+ }
201
+ });
202
+
203
+ // POST /auth/passkey/authenticate/verify — public (assertion IS the auth)
204
+ router.post('/authenticate/verify', async (req: Request, res: Response) => {
205
+ try {
206
+ // Specific vault_locked error per audit
207
+ if (!isUnlocked()) {
208
+ res.status(400).json({
209
+ error: 'vault_locked',
210
+ message: 'Password required after server restart',
211
+ });
212
+ return;
213
+ }
214
+
215
+ const { credential, pubkey } = req.body;
216
+ if (!credential) {
217
+ res.status(400).json({ error: 'credential is required' });
218
+ return;
219
+ }
220
+ if (!pubkey || typeof pubkey !== 'string') {
221
+ res.status(400).json({ error: 'pubkey is required' });
222
+ return;
223
+ }
224
+ if (!isValidAgentPubkey(pubkey)) {
225
+ res.status(400).json({ error: 'pubkey must be a valid RSA public key' });
226
+ return;
227
+ }
228
+
229
+ const rpId = getRpId(req, req.body?.rpId);
230
+ const origin = `${req.protocol}://${req.get('host')}`;
231
+
232
+ // Extract and consume challenge
233
+ const challenge = credential.response?.clientDataJSON
234
+ ? JSON.parse(Buffer.from(credential.response.clientDataJSON, 'base64url').toString()).challenge
235
+ : undefined;
236
+
237
+ if (!challenge || !consumeChallenge(challenge, 'authenticate')) {
238
+ log.warn('Passkey auth: invalid or expired challenge', { ip: req.ip, origin });
239
+ res.status(401).json({ error: 'Invalid or expired challenge' });
240
+ return;
241
+ }
242
+
243
+ // Look up the credential
244
+ const credentialId = credential.id || credential.rawId;
245
+ const passkey = await prisma.passkey.findUnique({
246
+ where: { credentialId },
247
+ });
248
+
249
+ if (!passkey) {
250
+ log.warn('Passkey auth: unknown credential', { credentialId, ip: req.ip, origin });
251
+ res.status(401).json({ error: 'Unknown credential' });
252
+ return;
253
+ }
254
+
255
+ let verification;
256
+ try {
257
+ verification = await verifyAuthenticationResponse({
258
+ response: credential,
259
+ expectedChallenge: challenge,
260
+ expectedOrigin: origin,
261
+ expectedRPID: rpId,
262
+ credential: {
263
+ id: passkey.credentialId,
264
+ publicKey: new Uint8Array(passkey.publicKey),
265
+ counter: passkey.counter,
266
+ transports: JSON.parse(passkey.transports || '[]'),
267
+ },
268
+ requireUserVerification: true,
269
+ });
270
+ } catch (err) {
271
+ // Log failed attempt (audit item)
272
+ log.warn('Passkey auth: verification failed', {
273
+ credentialId,
274
+ ip: req.ip,
275
+ origin,
276
+ error: getErrorMessage(err),
277
+ });
278
+ res.status(401).json({ error: 'Authentication verification failed' });
279
+ return;
280
+ }
281
+
282
+ if (!verification.verified) {
283
+ log.warn('Passkey auth: assertion not verified', { credentialId, ip: req.ip, origin });
284
+ res.status(401).json({ error: 'Authentication verification failed' });
285
+ return;
286
+ }
287
+
288
+ // Counter validation (clone detection) — @simplewebauthn handles this,
289
+ // but we double-check per audit
290
+ const newCounter = verification.authenticationInfo.newCounter;
291
+ if (newCounter > 0 && newCounter <= passkey.counter) {
292
+ log.warn('Passkey auth: counter regression (possible clone)', {
293
+ credentialId,
294
+ storedCounter: passkey.counter,
295
+ newCounter,
296
+ ip: req.ip,
297
+ origin,
298
+ });
299
+ res.status(401).json({ error: 'Credential counter regression detected' });
300
+ return;
301
+ }
302
+
303
+ // Update counter and lastUsedAt
304
+ await prisma.passkey.update({
305
+ where: { credentialId: passkey.credentialId },
306
+ data: {
307
+ counter: newCounter,
308
+ lastUsedAt: new Date(),
309
+ },
310
+ });
311
+
312
+ // Issue admin token (same as rekey flow)
313
+ const normalizedPubkey = normalizeAgentPubkey(pubkey);
314
+ const token = await createAdminToken(normalizedPubkey);
315
+
316
+ res.json({
317
+ success: true,
318
+ token,
319
+ });
320
+ } catch (error) {
321
+ res.status(500).json({ error: getErrorMessage(error) });
322
+ }
323
+ });
324
+
325
+ // ─── Delete ─────────────────────────────────────────────────────────────────
326
+
327
+ // DELETE /auth/passkey/:credentialId — requires admin token
328
+ router.delete('/:credentialId', requireWalletAuth, requireAdmin, async (req: Request<{ credentialId: string }>, res: Response) => {
329
+ try {
330
+ const { credentialId } = req.params;
331
+
332
+ const passkey = await prisma.passkey.findUnique({ where: { credentialId } });
333
+ if (!passkey) {
334
+ res.status(404).json({ error: 'Passkey not found' });
335
+ return;
336
+ }
337
+
338
+ await prisma.passkey.delete({ where: { credentialId } });
339
+
340
+ res.json({ success: true });
341
+ } catch (error) {
342
+ res.status(500).json({ error: getErrorMessage(error) });
343
+ }
344
+ });
345
+
346
+ export default router;