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.
- package/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight request/response logging middleware using Pino
|
|
3
|
+
*
|
|
4
|
+
* Console logging via Pino (structured, with request IDs and timing).
|
|
5
|
+
* Only stores events in DB for errors and security-relevant failures (4xx/5xx).
|
|
6
|
+
* Business events (send, fund, swap, token create, etc.) are logged separately
|
|
7
|
+
* by each route via the logger module, which handles DB + WebSocket storage.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomBytes } from 'crypto';
|
|
11
|
+
import { Request, Response, NextFunction } from 'express';
|
|
12
|
+
import { log } from '../lib/pino';
|
|
13
|
+
import { events } from '../lib/events';
|
|
14
|
+
|
|
15
|
+
// Paths to skip logging entirely (high-frequency/low-value)
|
|
16
|
+
const SKIP_PATHS = new Set(['/health']);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Express middleware that logs request/response details via Pino
|
|
20
|
+
* Only persists error/security events to DB to avoid bloat
|
|
21
|
+
*/
|
|
22
|
+
export function requestLogger(req: Request, res: Response, next: NextFunction): void {
|
|
23
|
+
if (SKIP_PATHS.has(req.path)) {
|
|
24
|
+
next();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const requestId = randomBytes(4).toString('hex');
|
|
29
|
+
const startTime = process.hrtime.bigint();
|
|
30
|
+
|
|
31
|
+
const child = log.child({ requestId });
|
|
32
|
+
|
|
33
|
+
child.debug({
|
|
34
|
+
method: req.method,
|
|
35
|
+
url: req.originalUrl || req.path,
|
|
36
|
+
}, 'request start');
|
|
37
|
+
|
|
38
|
+
res.on('finish', () => {
|
|
39
|
+
const durationNs = process.hrtime.bigint() - startTime;
|
|
40
|
+
const durationMs = Number(durationNs) / 1_000_000;
|
|
41
|
+
const statusCode = res.statusCode;
|
|
42
|
+
|
|
43
|
+
const logData: Record<string, unknown> = {
|
|
44
|
+
method: req.method,
|
|
45
|
+
url: req.originalUrl || req.path,
|
|
46
|
+
statusCode,
|
|
47
|
+
durationMs: Math.round(durationMs * 100) / 100,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Add agent identification for authenticated requests
|
|
51
|
+
if (req.auth) {
|
|
52
|
+
logData.agentId = req.auth.token.agentId;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Log level based on status code
|
|
56
|
+
if (statusCode >= 500) {
|
|
57
|
+
child.error(logData, 'request error');
|
|
58
|
+
} else if (statusCode >= 400) {
|
|
59
|
+
child.warn(logData, 'request complete');
|
|
60
|
+
} else {
|
|
61
|
+
child.debug(logData, 'request complete');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Only persist security-relevant failures to DB (auth failures, forbidden, rate limits, server errors)
|
|
65
|
+
if (statusCode === 401 || statusCode === 403 || statusCode === 429 || statusCode >= 500) {
|
|
66
|
+
const eventType =
|
|
67
|
+
statusCode === 401 ? 'request:auth_failed' :
|
|
68
|
+
statusCode === 403 ? 'request:forbidden' :
|
|
69
|
+
statusCode === 429 ? 'request:rate_limited' :
|
|
70
|
+
'request:server_error';
|
|
71
|
+
|
|
72
|
+
events.custom(eventType, {
|
|
73
|
+
requestId,
|
|
74
|
+
method: req.method,
|
|
75
|
+
path: req.originalUrl || req.path,
|
|
76
|
+
statusCode,
|
|
77
|
+
durationMs: Math.round(durationMs * 100) / 100,
|
|
78
|
+
agentId: req.auth?.token?.agentId,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
next();
|
|
84
|
+
}
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { Router, Request, Response } from 'express';
|
|
2
|
+
import { randomBytes } from 'crypto';
|
|
3
|
+
import { prisma } from '../lib/db';
|
|
4
|
+
import { events } from '../lib/events';
|
|
5
|
+
import { requireWalletAuth } from '../middleware/auth';
|
|
6
|
+
import { requireAdmin, isAdmin, requirePermission } from '../lib/permissions';
|
|
7
|
+
import { createToken, getTokenHash, type AgentTokenPayload } from '../lib/auth';
|
|
8
|
+
import { isValidAgentPubkey, normalizeAgentPubkey, encryptToAgentPubkey } from '../lib/credential-transport';
|
|
9
|
+
import { hashSecret } from '../lib/crypto';
|
|
10
|
+
import { isUnlocked } from '../lib/cold';
|
|
11
|
+
import { normalizeAddress } from '../lib/address';
|
|
12
|
+
import { generateVerifiedSummary } from '../lib/verified-summary';
|
|
13
|
+
import { listTokensFromDb, revokeToken } from '../lib/sessions';
|
|
14
|
+
import { createHumanActionNotification, createNotification } from '../lib/notifications';
|
|
15
|
+
import { getDefault } from '../lib/defaults';
|
|
16
|
+
import { logger } from '../lib/logger';
|
|
17
|
+
import { getErrorMessage } from '../lib/error';
|
|
18
|
+
import { resolveAction } from '../lib/resolve-action';
|
|
19
|
+
import { AgentProfileError, resolveProfileToEffectivePolicy } from '../lib/agent-profiles';
|
|
20
|
+
import { buildPolicyPreviewV1, mapPreviewError } from '../lib/policy-preview';
|
|
21
|
+
|
|
22
|
+
const router = Router();
|
|
23
|
+
|
|
24
|
+
// GET /actions/pending — List all pending human actions
|
|
25
|
+
router.get('/pending', requireWalletAuth, requirePermission('action:read'), async (_req: Request, res: Response) => {
|
|
26
|
+
try {
|
|
27
|
+
const actions = await prisma.humanAction.findMany({
|
|
28
|
+
where: {
|
|
29
|
+
status: 'pending',
|
|
30
|
+
NOT: { type: 'strategy:message' },
|
|
31
|
+
},
|
|
32
|
+
orderBy: { createdAt: 'desc' },
|
|
33
|
+
});
|
|
34
|
+
res.json({ success: true, actions });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const message = getErrorMessage(error);
|
|
37
|
+
res.status(500).json({ success: false, error: message });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// POST /actions — Create a human action request (app proposes an action for approval)
|
|
42
|
+
// Requires Bearer token with action:create permission
|
|
43
|
+
router.post('/', requireWalletAuth, requirePermission('action:create'), async (req: Request, res: Response) => {
|
|
44
|
+
try {
|
|
45
|
+
const { summary, permissions, limits, walletAccess, ttl, type, metadata, notify, pubkey, credentialAccess } = req.body;
|
|
46
|
+
|
|
47
|
+
// Validate summary (required for all types)
|
|
48
|
+
if (!summary || typeof summary !== 'string' || summary.trim().length === 0) {
|
|
49
|
+
res.status(400).json({ success: false, error: 'summary is required and must be a non-empty string' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const MAX_SUMMARY_LENGTH = 500;
|
|
54
|
+
if (summary.length > MAX_SUMMARY_LENGTH) {
|
|
55
|
+
res.status(400).json({ success: false, error: `summary must be ${MAX_SUMMARY_LENGTH} characters or fewer` });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const callerAgentId = req.auth!.token.agentId;
|
|
60
|
+
|
|
61
|
+
// === Notification-only branch: no permissions/limits needed ===
|
|
62
|
+
if (type === 'notify') {
|
|
63
|
+
const request = await prisma.humanAction.create({
|
|
64
|
+
data: {
|
|
65
|
+
type: 'notify',
|
|
66
|
+
fromTier: 'system',
|
|
67
|
+
toAddress: null,
|
|
68
|
+
amount: null,
|
|
69
|
+
chain: 'base',
|
|
70
|
+
status: 'acknowledged',
|
|
71
|
+
resolvedAt: new Date(),
|
|
72
|
+
metadata: JSON.stringify({ agentId: callerAgentId, summary, ...(metadata || {}) }),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Info notification (dismiss only, no approve/reject)
|
|
77
|
+
await createNotification({
|
|
78
|
+
type: 'info',
|
|
79
|
+
category: 'general',
|
|
80
|
+
title: 'Notification',
|
|
81
|
+
message: summary,
|
|
82
|
+
actions: [{ id: 'dismiss', label: 'DISMISS', type: 'secondary', action: 'dismiss' }],
|
|
83
|
+
metadata: { ...(metadata || {}), agentId: callerAgentId },
|
|
84
|
+
source: 'agent',
|
|
85
|
+
agentId: callerAgentId,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Emit to WebSocket for dashboard; adapters check type themselves
|
|
89
|
+
if (notify !== false) {
|
|
90
|
+
events.actionCreated({
|
|
91
|
+
id: request.id,
|
|
92
|
+
type: 'notify',
|
|
93
|
+
source: `agent:${callerAgentId}`,
|
|
94
|
+
summary,
|
|
95
|
+
expiresAt: null,
|
|
96
|
+
metadata: { agentId: callerAgentId, ...(metadata || {}) },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logger.actionCreated(callerAgentId, request.id, 'notify', summary);
|
|
101
|
+
|
|
102
|
+
res.json({ success: true, id: request.id });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// === Standard approval flow: permissions required ===
|
|
107
|
+
|
|
108
|
+
// Validate permissions
|
|
109
|
+
if (!permissions || !Array.isArray(permissions) || permissions.length === 0) {
|
|
110
|
+
res.status(400).json({ success: false, error: 'permissions must be a non-empty array' });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Block privilege escalation — cannot request admin:* or action:create
|
|
115
|
+
const blocked = permissions.filter((p: string) => p === 'admin:*' || p === 'action:create');
|
|
116
|
+
if (blocked.length > 0) {
|
|
117
|
+
res.status(400).json({ success: false, error: `Cannot request privileged permissions: ${blocked.join(', ')}` });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const requestedCredentialAccess = credentialAccess && typeof credentialAccess === 'object' && !Array.isArray(credentialAccess)
|
|
122
|
+
? credentialAccess as AgentTokenPayload['credentialAccess']
|
|
123
|
+
: undefined;
|
|
124
|
+
if (typeof pubkey !== 'string' || !pubkey.trim()) {
|
|
125
|
+
res.status(400).json({ success: false, error: 'pubkey is required' });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!isValidAgentPubkey(pubkey)) {
|
|
129
|
+
res.status(400).json({ success: false, error: 'pubkey must be a valid RSA public key (PEM or base64)' });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const normalizedPubkey = normalizeAgentPubkey(pubkey);
|
|
133
|
+
|
|
134
|
+
const defaultActionTtl = await getDefault<number>('ttl.action', 60);
|
|
135
|
+
const actionTtl = typeof ttl === 'number' && ttl > 0 ? ttl : defaultActionTtl;
|
|
136
|
+
|
|
137
|
+
// Generate secret for polling (same pattern as POST /auth)
|
|
138
|
+
const secret = randomBytes(32).toString('hex');
|
|
139
|
+
const secretHash = hashSecret(secret);
|
|
140
|
+
|
|
141
|
+
// Preserve pre-computed action from metadata if provided (for auto-execute on approval)
|
|
142
|
+
const precomputedAction = metadata?.action || undefined;
|
|
143
|
+
|
|
144
|
+
// Generate server-verified summary from actual action parameters
|
|
145
|
+
const verifiedSummary = generateVerifiedSummary({
|
|
146
|
+
agentId: callerAgentId,
|
|
147
|
+
summary,
|
|
148
|
+
permissions,
|
|
149
|
+
limits: limits || undefined,
|
|
150
|
+
walletAccess: walletAccess || undefined,
|
|
151
|
+
ttl: actionTtl,
|
|
152
|
+
action: precomputedAction,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const request = await prisma.humanAction.create({
|
|
156
|
+
data: {
|
|
157
|
+
type: 'action',
|
|
158
|
+
fromTier: 'system',
|
|
159
|
+
toAddress: null,
|
|
160
|
+
amount: null,
|
|
161
|
+
chain: 'base',
|
|
162
|
+
status: 'pending',
|
|
163
|
+
metadata: JSON.stringify({
|
|
164
|
+
agentId: callerAgentId,
|
|
165
|
+
permissions,
|
|
166
|
+
limits: limits || undefined,
|
|
167
|
+
walletAccess: walletAccess || undefined,
|
|
168
|
+
credentialAccess: requestedCredentialAccess,
|
|
169
|
+
pubkey: normalizedPubkey,
|
|
170
|
+
ttl: actionTtl,
|
|
171
|
+
secretHash,
|
|
172
|
+
summary,
|
|
173
|
+
action: precomputedAction,
|
|
174
|
+
verifiedSummary,
|
|
175
|
+
}),
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Create notification for human approval
|
|
180
|
+
await createHumanActionNotification(request);
|
|
181
|
+
|
|
182
|
+
// Emit WebSocket event
|
|
183
|
+
events.actionCreated({
|
|
184
|
+
id: request.id,
|
|
185
|
+
type: 'action',
|
|
186
|
+
source: `agent:${callerAgentId}`,
|
|
187
|
+
summary,
|
|
188
|
+
expiresAt: null,
|
|
189
|
+
metadata: { agentId: callerAgentId, permissions, limits, summary, verifiedSummary },
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
logger.actionCreated(callerAgentId, request.id, 'action', summary);
|
|
193
|
+
|
|
194
|
+
res.json({
|
|
195
|
+
success: true,
|
|
196
|
+
requestId: request.id,
|
|
197
|
+
secret,
|
|
198
|
+
message: 'Waiting for human approval',
|
|
199
|
+
});
|
|
200
|
+
} catch (error) {
|
|
201
|
+
const message = getErrorMessage(error);
|
|
202
|
+
res.status(500).json({ success: false, error: message });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// POST /actions/:id/resolve — Approve or reject a human action
|
|
207
|
+
router.post('/:id/resolve', requireWalletAuth, requirePermission('action:resolve'), async (req: Request<{ id: string }>, res: Response) => {
|
|
208
|
+
try {
|
|
209
|
+
const { approved, walletAccess, limits } = req.body;
|
|
210
|
+
const result = await resolveAction(req.params.id, approved, { walletAccess, limits });
|
|
211
|
+
res.status(result.statusCode).json(result.data);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const message = getErrorMessage(error);
|
|
214
|
+
res.status(500).json({ success: false, error: message });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// POST /actions/token/preview - Preview effective token policy without issuing token (requires admin)
|
|
219
|
+
router.post('/token/preview', requireWalletAuth, requireAdmin, async (req: Request, res: Response) => {
|
|
220
|
+
try {
|
|
221
|
+
const { profile, profileVersion, profileOverrides } = req.body;
|
|
222
|
+
|
|
223
|
+
if (typeof profile !== 'string' || profile.trim().length === 0) {
|
|
224
|
+
res.status(422).json({ version: 'v1', code: 'ERR_OVERRIDE_INVALID', error: 'profile is required' });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const previewInput = {
|
|
229
|
+
profileId: profile,
|
|
230
|
+
profileVersion: typeof profileVersion === 'string' ? profileVersion : undefined,
|
|
231
|
+
overrides: profileOverrides,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const resolved = resolveProfileToEffectivePolicy(previewInput);
|
|
235
|
+
const preview = buildPolicyPreviewV1(previewInput, resolved);
|
|
236
|
+
|
|
237
|
+
res.json(preview);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (error instanceof AgentProfileError) {
|
|
240
|
+
const mapped = mapPreviewError(error.code);
|
|
241
|
+
res.status(mapped.status).json(mapped.error);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const mapped = mapPreviewError('ERR_RESOLUTION_FAILED');
|
|
245
|
+
res.status(mapped.status).json(mapped.error);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// POST /actions/token - Create signed token for agent (requires admin)
|
|
250
|
+
router.post('/token', requireWalletAuth, requireAdmin, async (req: Request, res: Response) => {
|
|
251
|
+
try {
|
|
252
|
+
const {
|
|
253
|
+
agentId,
|
|
254
|
+
limit,
|
|
255
|
+
permissions,
|
|
256
|
+
ttl,
|
|
257
|
+
limits, // Per-permission limits
|
|
258
|
+
walletAccess, // Wallet access grants
|
|
259
|
+
credentialAccess,
|
|
260
|
+
pubkey,
|
|
261
|
+
profile,
|
|
262
|
+
profileVersion,
|
|
263
|
+
profileOverrides,
|
|
264
|
+
} = req.body;
|
|
265
|
+
|
|
266
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
267
|
+
res.status(400).json({ error: 'agentId is required' });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Legacy limit or new limits.fund
|
|
272
|
+
const fundLimit = typeof limit === 'number' ? limit : (limits?.fund ?? 0);
|
|
273
|
+
if (fundLimit < 0) {
|
|
274
|
+
res.status(400).json({ error: 'limit must be a non-negative number (in ETH)' });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Wallet must be unlocked
|
|
279
|
+
if (!isUnlocked()) {
|
|
280
|
+
res.status(401).json({ error: 'Wallet is locked. Unlock first.' });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const defaultSendLimit = await getDefault<number>('limits.send', 0.1);
|
|
285
|
+
const defaultSwapLimit = await getDefault<number>('limits.swap', 0.1);
|
|
286
|
+
const defaultPermissions = await getDefault<string[]>('permissions.default', ['wallet:create:hot', 'send:hot', 'swap', 'fund', 'action:create']);
|
|
287
|
+
const defaultTtl = await getDefault<number>('ttl.agent', 3600);
|
|
288
|
+
|
|
289
|
+
const resolvedProfile = typeof profile === 'string' && profile.trim().length > 0
|
|
290
|
+
? resolveProfileToEffectivePolicy({
|
|
291
|
+
profileId: profile,
|
|
292
|
+
profileVersion: typeof profileVersion === 'string' ? profileVersion : undefined,
|
|
293
|
+
overrides: profileOverrides,
|
|
294
|
+
})
|
|
295
|
+
: null;
|
|
296
|
+
|
|
297
|
+
const validPermissions = resolvedProfile
|
|
298
|
+
? [...resolvedProfile.permissions]
|
|
299
|
+
: (Array.isArray(permissions) ? [...permissions] : [...defaultPermissions]);
|
|
300
|
+
|
|
301
|
+
if (typeof pubkey !== 'string' || !pubkey.trim()) {
|
|
302
|
+
res.status(400).json({ error: 'pubkey is required' });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!isValidAgentPubkey(pubkey)) {
|
|
306
|
+
res.status(400).json({ error: 'pubkey must be a valid RSA public key (PEM or base64)' });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const normalizedPubkey = normalizeAgentPubkey(pubkey);
|
|
310
|
+
|
|
311
|
+
const ttlSeconds = resolvedProfile
|
|
312
|
+
? resolvedProfile.ttlSeconds
|
|
313
|
+
: (typeof ttl === 'number' ? ttl : defaultTtl);
|
|
314
|
+
|
|
315
|
+
// Normalize wallet access addresses
|
|
316
|
+
const normalizedWalletAccess = walletAccess && Array.isArray(walletAccess)
|
|
317
|
+
? walletAccess.map((addr: string) => normalizeAddress(addr))
|
|
318
|
+
: undefined;
|
|
319
|
+
|
|
320
|
+
// Build limits: per-token overrides > system defaults
|
|
321
|
+
const baseLimits = { fund: fundLimit, send: defaultSendLimit, swap: defaultSwapLimit };
|
|
322
|
+
const tokenLimits = limits ? { ...baseLimits, ...limits } : baseLimits;
|
|
323
|
+
|
|
324
|
+
const effectiveCredentialAccess = resolvedProfile
|
|
325
|
+
? resolvedProfile.credentialAccess
|
|
326
|
+
: credentialAccess;
|
|
327
|
+
|
|
328
|
+
const token = await createToken(agentId, fundLimit, validPermissions, ttlSeconds, {
|
|
329
|
+
limits: tokenLimits,
|
|
330
|
+
walletAccess: normalizedWalletAccess,
|
|
331
|
+
credentialAccess: effectiveCredentialAccess,
|
|
332
|
+
agentPubkey: normalizedPubkey,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const tokenHash = getTokenHash(token);
|
|
336
|
+
|
|
337
|
+
// Emit WebSocket event for direct token creation
|
|
338
|
+
events.tokenCreated({
|
|
339
|
+
tokenHash,
|
|
340
|
+
agentId,
|
|
341
|
+
limit: fundLimit,
|
|
342
|
+
permissions: validPermissions,
|
|
343
|
+
expiresAt: Date.now() + ttlSeconds * 1000,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
if (resolvedProfile) {
|
|
347
|
+
events.custom('agent_profile:issued', {
|
|
348
|
+
eventSchemaVersion: 1,
|
|
349
|
+
eventType: 'agent_profile.issued',
|
|
350
|
+
profile: resolvedProfile.profile,
|
|
351
|
+
effectivePolicyHash: resolvedProfile.effectivePolicyHash,
|
|
352
|
+
overrideDelta: resolvedProfile.overrideDelta,
|
|
353
|
+
actor: 'admin',
|
|
354
|
+
agentId,
|
|
355
|
+
tokenHash,
|
|
356
|
+
timestamp: Date.now(),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Encrypt token to agent pubkey if provided (prevents model provider from seeing it)
|
|
361
|
+
const responseToken = normalizedPubkey
|
|
362
|
+
? { encryptedToken: encryptToAgentPubkey(token, normalizedPubkey) }
|
|
363
|
+
: { token };
|
|
364
|
+
|
|
365
|
+
res.json({
|
|
366
|
+
success: true,
|
|
367
|
+
...responseToken,
|
|
368
|
+
agentId,
|
|
369
|
+
limit: fundLimit,
|
|
370
|
+
limits: tokenLimits,
|
|
371
|
+
permissions: validPermissions,
|
|
372
|
+
walletAccess: normalizedWalletAccess,
|
|
373
|
+
credentialAccess: effectiveCredentialAccess,
|
|
374
|
+
profile: resolvedProfile ? resolvedProfile.profile : undefined,
|
|
375
|
+
effectivePolicyHash: resolvedProfile ? resolvedProfile.effectivePolicyHash : undefined,
|
|
376
|
+
overrideDelta: resolvedProfile ? resolvedProfile.overrideDelta : undefined,
|
|
377
|
+
warnings: resolvedProfile ? resolvedProfile.warnings : undefined,
|
|
378
|
+
hasPubkey: !!normalizedPubkey,
|
|
379
|
+
expiresIn: ttlSeconds
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (error instanceof AgentProfileError) {
|
|
383
|
+
res.status(400).json({ error: error.message, code: error.code });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const message = getErrorMessage(error);
|
|
387
|
+
res.status(400).json({ error: message });
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// GET /actions/tokens - List all agent tokens (requires admin)
|
|
392
|
+
router.get('/tokens', requireWalletAuth, requireAdmin, async (_req: Request, res: Response) => {
|
|
393
|
+
try {
|
|
394
|
+
const tokens = await listTokensFromDb();
|
|
395
|
+
|
|
396
|
+
const active = tokens.filter(t => t.isActive && !t.isExpired && !t.isRevoked && t.remaining > 0);
|
|
397
|
+
const inactive = tokens.filter(t => !t.isActive && !t.isExpired && !t.isRevoked);
|
|
398
|
+
const expired = tokens.filter(t => t.isExpired);
|
|
399
|
+
const revoked = tokens.filter(t => t.isRevoked);
|
|
400
|
+
const depleted = tokens.filter(t => !t.isExpired && !t.isRevoked && t.remaining <= 0);
|
|
401
|
+
|
|
402
|
+
res.json({
|
|
403
|
+
success: true,
|
|
404
|
+
tokens: {
|
|
405
|
+
active,
|
|
406
|
+
inactive,
|
|
407
|
+
expired,
|
|
408
|
+
revoked,
|
|
409
|
+
depleted
|
|
410
|
+
},
|
|
411
|
+
total: tokens.length
|
|
412
|
+
});
|
|
413
|
+
} catch (error) {
|
|
414
|
+
const message = getErrorMessage(error);
|
|
415
|
+
res.status(400).json({ error: message });
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// POST /actions/tokens/revoke - Revoke a token (admin or agent with own token)
|
|
420
|
+
router.post('/tokens/revoke', requireWalletAuth, async (req: Request, res: Response) => {
|
|
421
|
+
try {
|
|
422
|
+
const { tokenHash } = req.body;
|
|
423
|
+
const auth = req.auth!;
|
|
424
|
+
|
|
425
|
+
// Agent can only revoke their own token
|
|
426
|
+
if (!isAdmin(auth)) {
|
|
427
|
+
if (tokenHash && tokenHash !== auth.tokenHash) {
|
|
428
|
+
res.status(403).json({ error: 'Agents can only revoke their own token' });
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const success = await revokeToken(auth.tokenHash);
|
|
432
|
+
if (success) {
|
|
433
|
+
logger.tokenRevoked(auth.tokenHash, auth.token.agentId);
|
|
434
|
+
}
|
|
435
|
+
res.json({ success, message: success ? 'Token revoked' : 'Token not found' });
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Admin revoking any token
|
|
440
|
+
if (!tokenHash || typeof tokenHash !== 'string') {
|
|
441
|
+
res.status(400).json({ error: 'tokenHash is required' });
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const success = await revokeToken(tokenHash);
|
|
446
|
+
|
|
447
|
+
if (success) {
|
|
448
|
+
events.tokenRevoked({ tokenHash });
|
|
449
|
+
logger.tokenRevoked(tokenHash, 'admin');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
res.json({ success, message: success ? 'Token revoked' : 'Token not found' });
|
|
453
|
+
} catch (error) {
|
|
454
|
+
const message = getErrorMessage(error);
|
|
455
|
+
res.status(400).json({ error: message });
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
export default router;
|