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,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Token Registry
|
|
3
|
+
* =====================
|
|
4
|
+
* Central registry for all app tokens. Creates tokens on server start,
|
|
5
|
+
* provides token lookup for strategy engine and REST endpoints.
|
|
6
|
+
*
|
|
7
|
+
* Token lifecycle:
|
|
8
|
+
* Server start → createAppTokens() for all installed apps
|
|
9
|
+
* Approve flow → createAppToken(id) creates/replaces token
|
|
10
|
+
* Revoke flow → revokeAppToken(id) removes token
|
|
11
|
+
* Strategy enable → getAppToken(id) reads from registry
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { parse as parseYaml } from 'yaml';
|
|
17
|
+
import { createToken, getTokenHash } from './auth';
|
|
18
|
+
import { setToken, clearToken } from './strategy/state';
|
|
19
|
+
import { revokeToken } from './sessions';
|
|
20
|
+
import { getDefaultSync, onDefaultChanged } from './defaults';
|
|
21
|
+
import { prisma } from './db';
|
|
22
|
+
|
|
23
|
+
/** appId → raw token */
|
|
24
|
+
const tokens = new Map<string, string>();
|
|
25
|
+
|
|
26
|
+
/** appId → token hash */
|
|
27
|
+
const tokenHashes = new Map<string, string>();
|
|
28
|
+
|
|
29
|
+
interface AppManifest {
|
|
30
|
+
id: string;
|
|
31
|
+
permissions: string[];
|
|
32
|
+
limits?: { fund?: number; send?: number };
|
|
33
|
+
sources?: Array<{ key?: string }>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load all app manifests from apps/ directory.
|
|
38
|
+
* Returns ALL apps (not just strategies with ticker/jobs).
|
|
39
|
+
*/
|
|
40
|
+
function loadAllAppManifests(): AppManifest[] {
|
|
41
|
+
const appsDir = path.join(process.cwd(), 'apps');
|
|
42
|
+
if (!fs.existsSync(appsDir)) return [];
|
|
43
|
+
|
|
44
|
+
const apps: AppManifest[] = [];
|
|
45
|
+
|
|
46
|
+
for (const entry of fs.readdirSync(appsDir, { withFileTypes: true })) {
|
|
47
|
+
if (!entry.isDirectory()) continue;
|
|
48
|
+
const mdPath = path.join(appsDir, entry.name, 'app.md');
|
|
49
|
+
if (!fs.existsSync(mdPath)) continue;
|
|
50
|
+
|
|
51
|
+
const raw = fs.readFileSync(mdPath, 'utf-8');
|
|
52
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
53
|
+
if (!match) continue;
|
|
54
|
+
|
|
55
|
+
let manifest: Record<string, unknown>;
|
|
56
|
+
try {
|
|
57
|
+
manifest = parseYaml(match[1]);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(`[app-tokens] Failed to parse ${mdPath}:`, err);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!manifest) continue;
|
|
64
|
+
|
|
65
|
+
apps.push({
|
|
66
|
+
id: entry.name,
|
|
67
|
+
permissions: (manifest.permissions as string[]) || [],
|
|
68
|
+
limits: manifest.limits as AppManifest['limits'],
|
|
69
|
+
sources: manifest.sources as AppManifest['sources'],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return apps;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create tokens for all installed apps on startup.
|
|
78
|
+
* - Apps with permissions/limits → require HumanAction approval → token with approved perms + app:storage
|
|
79
|
+
* - Apps without declared permissions/limits → token uses default permissions
|
|
80
|
+
* - Needs approval but missing → no token (log warning)
|
|
81
|
+
*/
|
|
82
|
+
export async function createAppTokens(): Promise<void> {
|
|
83
|
+
const manifests = loadAllAppManifests();
|
|
84
|
+
|
|
85
|
+
// Register built-in __system_chat__ app (system chat widget needs admin permissions)
|
|
86
|
+
manifests.push({
|
|
87
|
+
id: '__system_chat__',
|
|
88
|
+
permissions: ['admin:*'],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (manifests.length === 0) return;
|
|
92
|
+
|
|
93
|
+
// Load all approvals
|
|
94
|
+
const approvals = await prisma.humanAction.findMany({ where: { type: 'app:approve', status: 'approved' } });
|
|
95
|
+
const approvalMap = new Map(approvals.map(a => {
|
|
96
|
+
try {
|
|
97
|
+
const meta = JSON.parse(a.metadata || '{}');
|
|
98
|
+
return [meta.appId as string, { permissions: meta.permissions, limits: meta.limits }] as const;
|
|
99
|
+
} catch { return null; }
|
|
100
|
+
}).filter((x): x is [string, { permissions: unknown; limits: unknown }] => x !== null));
|
|
101
|
+
|
|
102
|
+
for (const manifest of manifests) {
|
|
103
|
+
const hasPermissionsOrLimits = manifest.permissions.length > 0 || manifest.limits;
|
|
104
|
+
|
|
105
|
+
if (hasPermissionsOrLimits) {
|
|
106
|
+
const approval = approvalMap.get(manifest.id);
|
|
107
|
+
if (!approval) {
|
|
108
|
+
console.warn(`[app-tokens] ${manifest.id}: needs approval, skipping token creation`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const approvedPermissions = (approval.permissions || []) as string[];
|
|
114
|
+
const approvedLimits = approval.limits as { fund?: number; send?: number } | undefined;
|
|
115
|
+
await createAppToken(manifest.id, approvedPermissions, manifest, approvedLimits);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error(`[app-tokens] ${manifest.id}: failed to create token:`, err);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
// App without declared permissions/limits
|
|
121
|
+
try {
|
|
122
|
+
await createAppToken(manifest.id, [], manifest);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error(`[app-tokens] ${manifest.id}: failed to create minimal token:`, err);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(`[app-tokens] Created tokens for ${tokens.size}/${manifests.length} apps`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create or replace token for a single app.
|
|
134
|
+
* Returns the token string, or null if creation failed.
|
|
135
|
+
*/
|
|
136
|
+
export async function createAppToken(
|
|
137
|
+
appId: string,
|
|
138
|
+
permissions?: string[],
|
|
139
|
+
manifest?: AppManifest,
|
|
140
|
+
limits?: { fund?: number; send?: number },
|
|
141
|
+
): Promise<string | null> {
|
|
142
|
+
// If no permissions provided, look up approval
|
|
143
|
+
if (!permissions) {
|
|
144
|
+
const approvalRecords = await prisma.humanAction.findMany({
|
|
145
|
+
where: { type: 'app:approve', status: 'approved' },
|
|
146
|
+
});
|
|
147
|
+
const approval = approvalRecords.find(a => {
|
|
148
|
+
try { return JSON.parse(a.metadata || '{}').appId === appId; } catch { return false; }
|
|
149
|
+
});
|
|
150
|
+
if (approval) {
|
|
151
|
+
try {
|
|
152
|
+
const meta = JSON.parse(approval.metadata || '{}');
|
|
153
|
+
permissions = (meta.permissions || []) as string[];
|
|
154
|
+
limits = meta.limits as { fund?: number; send?: number } | undefined;
|
|
155
|
+
} catch {
|
|
156
|
+
permissions = [];
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
permissions = [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// agent-chat: respect permission tier setting
|
|
164
|
+
if (appId === 'agent-chat') {
|
|
165
|
+
const tier = getDefaultSync<string>('permissions.agent_tier', 'admin');
|
|
166
|
+
if (tier === 'admin') {
|
|
167
|
+
permissions = ['admin:*'];
|
|
168
|
+
}
|
|
169
|
+
// else: keep declared permissions (wallet:list, action:create)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Apps that omit permissions inherit system defaults.
|
|
173
|
+
if (!permissions || permissions.length === 0) {
|
|
174
|
+
permissions = getDefaultSync<string[]>(
|
|
175
|
+
'permissions.default',
|
|
176
|
+
['wallet:create:hot', 'send:hot', 'swap', 'fund', 'action:create'],
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Always include app:storage
|
|
181
|
+
const allPerms = new Set(permissions);
|
|
182
|
+
allPerms.add('app:storage');
|
|
183
|
+
|
|
184
|
+
// Auto-add app:accesskey if any source uses a key field
|
|
185
|
+
if (manifest?.sources?.some(s => s.key)) {
|
|
186
|
+
allPerms.add('app:accesskey');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const appTtl = getDefaultSync<number>('ttl.app', 86400);
|
|
191
|
+
const token = await createToken(
|
|
192
|
+
`app:${appId}`,
|
|
193
|
+
limits?.fund || 0,
|
|
194
|
+
Array.from(allPerms),
|
|
195
|
+
appTtl,
|
|
196
|
+
{ limits },
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const hash = getTokenHash(token);
|
|
200
|
+
tokens.set(appId, token);
|
|
201
|
+
tokenHashes.set(appId, hash);
|
|
202
|
+
setToken(appId, token);
|
|
203
|
+
|
|
204
|
+
console.log(`[app-tokens] ${appId}: token created (hash=${hash.slice(0, 8)}...)`);
|
|
205
|
+
return token;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error(`[app-tokens] ${appId}: createToken failed:`, err);
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Revoke token for a app.
|
|
214
|
+
* Removes from registry AND adds to revokedTokens set so the token
|
|
215
|
+
* can no longer be used for API calls.
|
|
216
|
+
*/
|
|
217
|
+
export async function revokeAppToken(appId: string): Promise<void> {
|
|
218
|
+
const hash = tokenHashes.get(appId);
|
|
219
|
+
tokens.delete(appId);
|
|
220
|
+
tokenHashes.delete(appId);
|
|
221
|
+
clearToken(appId);
|
|
222
|
+
if (hash) {
|
|
223
|
+
await revokeToken(hash);
|
|
224
|
+
}
|
|
225
|
+
console.log(`[app-tokens] ${appId}: token revoked`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get existing token for a app.
|
|
230
|
+
*/
|
|
231
|
+
export function getAppToken(appId: string): string | undefined {
|
|
232
|
+
return tokens.get(appId);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get token hash for a app.
|
|
237
|
+
*/
|
|
238
|
+
export function getAppTokenHash(appId: string): string | undefined {
|
|
239
|
+
return tokenHashes.get(appId);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ─── Tier Change Listener ────────────────────────────────────────────
|
|
243
|
+
// When agent tier changes, revoke + recreate the agent-chat app token
|
|
244
|
+
onDefaultChanged('permissions.agent_tier', async () => {
|
|
245
|
+
await revokeAppToken('agent-chat');
|
|
246
|
+
await createAppToken('agent-chat');
|
|
247
|
+
});
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { randomBytes, createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
import { AgentTokenPayload } from '../types';
|
|
3
|
+
import { registerToken, revokeToken } from './sessions';
|
|
4
|
+
import { expandPermissions } from './permissions';
|
|
5
|
+
import { getDefaultSync } from './defaults';
|
|
6
|
+
|
|
7
|
+
// Random 32-byte signing key generated on startup - never persisted
|
|
8
|
+
const SIGNING_KEY = randomBytes(32);
|
|
9
|
+
|
|
10
|
+
// Track admin token hashes for revocation on lock
|
|
11
|
+
const adminTokenHashes: Set<string> = new Set();
|
|
12
|
+
|
|
13
|
+
// Re-export types for convenience
|
|
14
|
+
export type { AgentTokenPayload };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create an admin token for UI access
|
|
18
|
+
* Admin tokens are regular tokens with admin:* permission
|
|
19
|
+
* Multiple admin tokens can exist simultaneously
|
|
20
|
+
*/
|
|
21
|
+
export async function createAdminToken(agentPubkey: string): Promise<string> {
|
|
22
|
+
const token = await createToken(
|
|
23
|
+
'admin',
|
|
24
|
+
0, // No spending limit for admin
|
|
25
|
+
['admin:*'],
|
|
26
|
+
getDefaultSync<number>('ttl.admin', 604800), // Default 7 day TTL (effectively until lock/restart)
|
|
27
|
+
{ agentPubkey }
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
adminTokenHashes.add(getTokenHash(token));
|
|
31
|
+
return token;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Revoke all admin tokens (called on lock)
|
|
36
|
+
*/
|
|
37
|
+
export function revokeAdminTokens(): void {
|
|
38
|
+
for (const hash of adminTokenHashes) {
|
|
39
|
+
revokeToken(hash);
|
|
40
|
+
}
|
|
41
|
+
adminTokenHashes.clear();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get admin token hashes (for testing/logging)
|
|
46
|
+
*/
|
|
47
|
+
export function getAdminTokenHashes(): string[] {
|
|
48
|
+
return Array.from(adminTokenHashes);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a signed approval token for an agent
|
|
53
|
+
* Also registers it in memory + DB for tracking
|
|
54
|
+
*/
|
|
55
|
+
export async function createToken(
|
|
56
|
+
agentId: string,
|
|
57
|
+
limit: number,
|
|
58
|
+
permissions: string[],
|
|
59
|
+
ttlSeconds: number = 3600,
|
|
60
|
+
options?: {
|
|
61
|
+
limits?: AgentTokenPayload['limits'];
|
|
62
|
+
walletAccess?: string[];
|
|
63
|
+
credentialAccess?: AgentTokenPayload['credentialAccess'];
|
|
64
|
+
agentPubkey?: string;
|
|
65
|
+
}
|
|
66
|
+
): Promise<string> {
|
|
67
|
+
// Expand permissions for consistency
|
|
68
|
+
const expandedPermissions = expandPermissions(permissions);
|
|
69
|
+
|
|
70
|
+
const payload: AgentTokenPayload = {
|
|
71
|
+
agentId,
|
|
72
|
+
permissions: expandedPermissions,
|
|
73
|
+
exp: Date.now() + ttlSeconds * 1000,
|
|
74
|
+
iat: Date.now(),
|
|
75
|
+
// Legacy limit field for backward compatibility
|
|
76
|
+
limit,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Add per-permission limits if provided, otherwise use legacy limit for fund
|
|
80
|
+
if (options?.limits) {
|
|
81
|
+
payload.limits = options.limits;
|
|
82
|
+
} else if (limit > 0) {
|
|
83
|
+
// Map legacy limit to fund limit
|
|
84
|
+
payload.limits = { fund: limit };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Add wallet access grants if provided
|
|
88
|
+
if (options?.walletAccess && options.walletAccess.length > 0) {
|
|
89
|
+
payload.walletAccess = options.walletAccess.map(addr => addr.toLowerCase());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add credential access — use explicit value, or populate from defaults for secret:* tokens
|
|
93
|
+
if (options?.credentialAccess) {
|
|
94
|
+
payload.credentialAccess = options.credentialAccess;
|
|
95
|
+
} else if (
|
|
96
|
+
expandedPermissions.includes('secret:read' as never) ||
|
|
97
|
+
expandedPermissions.includes('secret:write' as never)
|
|
98
|
+
) {
|
|
99
|
+
payload.credentialAccess = {
|
|
100
|
+
read: getDefaultSync<string[]>('defaults.credential.access.read', ['*']),
|
|
101
|
+
write: getDefaultSync<string[]>('defaults.credential.access.write', ['*']),
|
|
102
|
+
excludeFields: ['password', 'cvv'], // Exclude sensitive fields by default
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add agent public key if provided
|
|
107
|
+
if (options?.agentPubkey) {
|
|
108
|
+
payload.agentPubkey = options.agentPubkey;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const payloadStr = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
112
|
+
const signature = createHmac('sha256', SIGNING_KEY)
|
|
113
|
+
.update(payloadStr)
|
|
114
|
+
.digest('base64url');
|
|
115
|
+
|
|
116
|
+
const token = `${payloadStr}.${signature}`;
|
|
117
|
+
|
|
118
|
+
// Register token in memory + DB
|
|
119
|
+
const tokenHash = getTokenHash(token);
|
|
120
|
+
await registerToken(tokenHash, payload);
|
|
121
|
+
|
|
122
|
+
return token;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Validate a signed token and return the payload if valid
|
|
127
|
+
* Works for both agent tokens and admin tokens (admin is just a permission)
|
|
128
|
+
*/
|
|
129
|
+
export function validateToken(token: string): AgentTokenPayload | null {
|
|
130
|
+
const parts = token.split('.');
|
|
131
|
+
if (parts.length !== 2) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const [payloadStr, signature] = parts;
|
|
136
|
+
|
|
137
|
+
// Verify signature
|
|
138
|
+
const expectedSignature = createHmac('sha256', SIGNING_KEY)
|
|
139
|
+
.update(payloadStr)
|
|
140
|
+
.digest('base64url');
|
|
141
|
+
|
|
142
|
+
const sigBuf = Buffer.from(signature, 'base64url');
|
|
143
|
+
const expectedBuf = Buffer.from(expectedSignature, 'base64url');
|
|
144
|
+
if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Parse and validate payload
|
|
149
|
+
try {
|
|
150
|
+
const payload = JSON.parse(
|
|
151
|
+
Buffer.from(payloadStr, 'base64url').toString('utf-8')
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Must have agentId and exp
|
|
155
|
+
if (!payload.agentId || !payload.exp) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check expiry
|
|
160
|
+
if (payload.exp < Date.now()) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Ensure permissions array exists
|
|
165
|
+
if (!Array.isArray(payload.permissions)) {
|
|
166
|
+
payload.permissions = [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return payload as AgentTokenPayload;
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get a hash of the token for use as a session key
|
|
177
|
+
*/
|
|
178
|
+
export function getTokenHash(token: string): string {
|
|
179
|
+
return createHmac('sha256', SIGNING_KEY)
|
|
180
|
+
.update(token)
|
|
181
|
+
.digest('hex')
|
|
182
|
+
.slice(0, 16);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get the fund limit from a token (supports both legacy and new format)
|
|
187
|
+
*/
|
|
188
|
+
export function getFundLimit(token: AgentTokenPayload): number {
|
|
189
|
+
const fundLimit = token.limits?.fund;
|
|
190
|
+
|
|
191
|
+
// New format: plain number (legacy/legacy-compatible single-currency)
|
|
192
|
+
if (typeof fundLimit === 'number') {
|
|
193
|
+
return fundLimit;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Address-keyed fund limits are per-currency and require explicit currency context
|
|
197
|
+
// for enforcement in checkLimitByType/getSessionBudget.
|
|
198
|
+
// Treat missing default-currency fund limit as zero here to keep behavior fail-safe.
|
|
199
|
+
if (fundLimit && typeof fundLimit === 'object') {
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Fall back to legacy limit
|
|
204
|
+
return token.limit || 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get a specific limit from a token
|
|
209
|
+
*/
|
|
210
|
+
export function getLimit(token: AgentTokenPayload, limitType: 'fund' | 'send' | 'swap'): number | undefined {
|
|
211
|
+
return token.limits?.[limitType];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if a token has wallet access to a specific address
|
|
216
|
+
*/
|
|
217
|
+
export function hasWalletAccess(token: AgentTokenPayload, address: string): boolean {
|
|
218
|
+
if (!token.walletAccess) return false;
|
|
219
|
+
return token.walletAccess.includes(address.toLowerCase());
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// In-memory token escrow: raw tokens never touch the DB
|
|
223
|
+
interface EscrowEntry { token: string; expiresAt: number; }
|
|
224
|
+
const tokenEscrow = new Map<string, EscrowEntry>();
|
|
225
|
+
const ESCROW_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
226
|
+
|
|
227
|
+
export function escrowToken(requestId: string, token: string): void {
|
|
228
|
+
tokenEscrow.set(requestId, { token, expiresAt: Date.now() + ESCROW_TTL_MS });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function claimEscrowedToken(requestId: string): string | null {
|
|
232
|
+
const entry = tokenEscrow.get(requestId);
|
|
233
|
+
if (!entry) return null;
|
|
234
|
+
tokenEscrow.delete(requestId);
|
|
235
|
+
if (entry.expiresAt < Date.now()) return null; // expired
|
|
236
|
+
return entry.token;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Periodic sweep of expired escrow entries
|
|
240
|
+
const escrowSweepInterval = setInterval(() => {
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
for (const [id, entry] of tokenEscrow) {
|
|
243
|
+
if (entry.expiresAt < now) tokenEscrow.delete(id);
|
|
244
|
+
}
|
|
245
|
+
}, 60_000);
|
|
246
|
+
escrowSweepInterval.unref(); // Don't keep process alive
|
|
247
|
+
|
|
248
|
+
export function clearEscrowSweep(): void {
|
|
249
|
+
clearInterval(escrowSweepInterval);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Regenerate the signing key (for testing)
|
|
254
|
+
* WARNING: This invalidates all existing tokens
|
|
255
|
+
*/
|
|
256
|
+
export function regenerateSigningKey(): void {
|
|
257
|
+
// This is a no-op since SIGNING_KEY is const
|
|
258
|
+
// In production, restarting the server achieves this
|
|
259
|
+
// For tests, we just note that this would invalidate tokens
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create a signed token synchronously (for testing only)
|
|
264
|
+
* Does NOT register in DB - use createToken() for production
|
|
265
|
+
*/
|
|
266
|
+
export function createTokenSync(payload: {
|
|
267
|
+
agentId: string;
|
|
268
|
+
permissions: string[];
|
|
269
|
+
exp: number;
|
|
270
|
+
iat?: number;
|
|
271
|
+
limits?: AgentTokenPayload['limits'];
|
|
272
|
+
walletAccess?: string[];
|
|
273
|
+
credentialAccess?: AgentTokenPayload['credentialAccess'];
|
|
274
|
+
agentPubkey?: string;
|
|
275
|
+
}): string {
|
|
276
|
+
const fullPayload: AgentTokenPayload = {
|
|
277
|
+
agentId: payload.agentId,
|
|
278
|
+
permissions: payload.permissions,
|
|
279
|
+
exp: payload.exp,
|
|
280
|
+
iat: payload.iat ?? Date.now(),
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (payload.limits) {
|
|
284
|
+
fullPayload.limits = payload.limits;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (payload.walletAccess && payload.walletAccess.length > 0) {
|
|
288
|
+
fullPayload.walletAccess = payload.walletAccess.map(addr => addr.toLowerCase());
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (payload.credentialAccess) {
|
|
292
|
+
fullPayload.credentialAccess = payload.credentialAccess;
|
|
293
|
+
} else if (
|
|
294
|
+
payload.permissions.includes('secret:read') ||
|
|
295
|
+
payload.permissions.includes('secret:write')
|
|
296
|
+
) {
|
|
297
|
+
fullPayload.credentialAccess = {
|
|
298
|
+
read: getDefaultSync<string[]>('defaults.credential.access.read', ['*']),
|
|
299
|
+
write: getDefaultSync<string[]>('defaults.credential.access.write', ['*']),
|
|
300
|
+
excludeFields: ['password', 'cvv'], // Exclude sensitive fields by default
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (payload.agentPubkey) {
|
|
305
|
+
fullPayload.agentPubkey = payload.agentPubkey;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const payloadStr = Buffer.from(JSON.stringify(fullPayload)).toString('base64url');
|
|
309
|
+
const signature = createHmac('sha256', SIGNING_KEY)
|
|
310
|
+
.update(payloadStr)
|
|
311
|
+
.digest('base64url');
|
|
312
|
+
|
|
313
|
+
return `${payloadStr}.${signature}`;
|
|
314
|
+
}
|