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,601 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App REST Routes
|
|
3
|
+
* ==================
|
|
4
|
+
* All app endpoints consolidated under /apps:
|
|
5
|
+
* - Storage (gated by app:storage)
|
|
6
|
+
* - Token retrieval (admin only, for iframe injection)
|
|
7
|
+
* - API Keys (gated by app:accesskey)
|
|
8
|
+
* - App Approval (gated by strategy:manage)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
12
|
+
import { createHash } from 'crypto';
|
|
13
|
+
import rateLimit, { ipKeyGenerator } from 'express-rate-limit';
|
|
14
|
+
import { requireWalletAuth } from '../middleware/auth';
|
|
15
|
+
import { requirePermission, isAdmin } from '../lib/permissions';
|
|
16
|
+
import { prisma } from '../lib/db';
|
|
17
|
+
import {
|
|
18
|
+
getRuntime,
|
|
19
|
+
enableStrategy,
|
|
20
|
+
disableStrategy,
|
|
21
|
+
handleAppMessage,
|
|
22
|
+
enqueueAppMessage,
|
|
23
|
+
waitForQueuedAppMessage,
|
|
24
|
+
STRATEGY_ENABLED_STORAGE_KEY,
|
|
25
|
+
} from '../lib/strategy/engine';
|
|
26
|
+
import { loadStrategyManifests } from '../lib/strategy/loader';
|
|
27
|
+
import { createAppToken, revokeAppToken, getAppToken } from '../lib/app-tokens';
|
|
28
|
+
import { validateExternalUrl } from '../lib/network';
|
|
29
|
+
import { onDefaultChanged, parseRateLimit, getDefaultSync } from '../lib/defaults';
|
|
30
|
+
import { logger } from '../lib/logger';
|
|
31
|
+
import { getErrorMessage } from '../lib/error';
|
|
32
|
+
|
|
33
|
+
const router = Router();
|
|
34
|
+
|
|
35
|
+
const bypassRateLimit = process.env.BYPASS_RATE_LIMIT === 'true';
|
|
36
|
+
|
|
37
|
+
function buildLimiterKey(req: Request, prefix: string): string {
|
|
38
|
+
const authHeader = req.headers.authorization;
|
|
39
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
40
|
+
return prefix + createHash('sha256').update(authHeader.slice(7)).digest('hex').slice(0, 16);
|
|
41
|
+
}
|
|
42
|
+
return prefix + ipKeyGenerator(req.ip || req.socket.remoteAddress || '127.0.0.1');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Hot-reloadable rate limiter for app endpoints
|
|
46
|
+
function createAppHotLimiter(
|
|
47
|
+
defaultKey: string,
|
|
48
|
+
fallback: string,
|
|
49
|
+
prefix: string,
|
|
50
|
+
errorMsg: string,
|
|
51
|
+
): (req: Request, res: Response, next: NextFunction) => void {
|
|
52
|
+
if (bypassRateLimit) return (_req, _res, next) => next();
|
|
53
|
+
|
|
54
|
+
const { max, windowMs } = parseRateLimit(getDefaultSync(defaultKey, fallback));
|
|
55
|
+
let inner = rateLimit({
|
|
56
|
+
windowMs, max, standardHeaders: true, legacyHeaders: false,
|
|
57
|
+
keyGenerator: (req) => buildLimiterKey(req, prefix),
|
|
58
|
+
handler: (_req, res) => { res.status(429).json({ success: false, error: errorMsg }); },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
onDefaultChanged(defaultKey, (_key, value) => {
|
|
62
|
+
const updated = parseRateLimit(value);
|
|
63
|
+
inner = rateLimit({
|
|
64
|
+
windowMs: updated.windowMs, max: updated.max, standardHeaders: true, legacyHeaders: false,
|
|
65
|
+
keyGenerator: (req) => buildLimiterKey(req, prefix),
|
|
66
|
+
handler: (_req, res) => { res.status(429).json({ success: false, error: errorMsg }); },
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return (req, res, next) => inner(req, res, next);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Rate limit for /message endpoint (LLM calls are expensive)
|
|
74
|
+
const messageLimit = createAppHotLimiter('rate.app_message', '10,60000', '', 'Message rate limit exceeded');
|
|
75
|
+
|
|
76
|
+
// Rate limit for /fetch proxy
|
|
77
|
+
const fetchRateLimit = createAppHotLimiter('rate.app_fetch', '60,60000', 'fetch:', 'Fetch rate limit exceeded');
|
|
78
|
+
|
|
79
|
+
// ─── Storage scope enforcement ──────────────────────────────────────
|
|
80
|
+
// Ensures a app:storage token can only access its own storage.
|
|
81
|
+
// Admin tokens and app:storage:all bypass the scope check.
|
|
82
|
+
|
|
83
|
+
function getCallerAppId(agentId: string): string {
|
|
84
|
+
if (agentId.startsWith('strategy:')) return agentId.slice('strategy:'.length);
|
|
85
|
+
if (agentId.startsWith('app:')) return agentId.slice('app:'.length);
|
|
86
|
+
return agentId;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function enforceStorageScope(req: Request, res: Response, next: NextFunction): void {
|
|
90
|
+
const auth = req.auth!;
|
|
91
|
+
// Admin tokens and app:storage:all bypass scope
|
|
92
|
+
if (isAdmin(auth) || auth.token.permissions.includes('app:storage:all')) {
|
|
93
|
+
return next();
|
|
94
|
+
}
|
|
95
|
+
// Derive the caller's appId from the token's agentId
|
|
96
|
+
// Strategy tokens use "strategy:<appId>", app tokens use "app:<appId>"
|
|
97
|
+
const callerAppId = getCallerAppId(auth.token.agentId);
|
|
98
|
+
if (req.params.appId !== callerAppId) {
|
|
99
|
+
res.status(403).json({ success: false, error: "Cannot access another app's storage" });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
next();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Authenticated Storage (app:storage) ───────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* GET /apps/:appId/storage — List all keys for a app
|
|
109
|
+
*/
|
|
110
|
+
router.get('/:appId/storage', requireWalletAuth, requirePermission('app:storage'), enforceStorageScope, async (req: Request, res: Response) => {
|
|
111
|
+
try {
|
|
112
|
+
const { appId } = req.params;
|
|
113
|
+
|
|
114
|
+
const entries = await prisma.appStorage.findMany({
|
|
115
|
+
where: { appId },
|
|
116
|
+
select: { key: true, value: true, updatedAt: true },
|
|
117
|
+
orderBy: { updatedAt: 'desc' },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
res.json({
|
|
121
|
+
success: true,
|
|
122
|
+
appId,
|
|
123
|
+
entries: entries.map(e => ({
|
|
124
|
+
key: e.key,
|
|
125
|
+
value: JSON.parse(e.value),
|
|
126
|
+
updatedAt: e.updatedAt.toISOString(),
|
|
127
|
+
})),
|
|
128
|
+
});
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const message = getErrorMessage(err);
|
|
131
|
+
res.status(500).json({ success: false, error: message });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* GET /apps/:appId/storage/:key — Get a single value
|
|
137
|
+
*/
|
|
138
|
+
router.get('/:appId/storage/:key', requireWalletAuth, requirePermission('app:storage'), enforceStorageScope, async (req: Request, res: Response) => {
|
|
139
|
+
try {
|
|
140
|
+
const { appId, key } = req.params;
|
|
141
|
+
|
|
142
|
+
const entry = await prisma.appStorage.findUnique({
|
|
143
|
+
where: { appId_key: { appId, key } },
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!entry) {
|
|
147
|
+
res.status(404).json({ success: false, error: 'Key not found' });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
res.json({
|
|
152
|
+
success: true,
|
|
153
|
+
appId,
|
|
154
|
+
key,
|
|
155
|
+
value: JSON.parse(entry.value),
|
|
156
|
+
updatedAt: entry.updatedAt.toISOString(),
|
|
157
|
+
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const message = getErrorMessage(err);
|
|
160
|
+
res.status(500).json({ success: false, error: message });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* PUT /apps/:appId/storage/:key — Set a value (upsert)
|
|
166
|
+
*/
|
|
167
|
+
router.put('/:appId/storage/:key', requireWalletAuth, requirePermission('app:storage'), enforceStorageScope, async (req: Request, res: Response) => {
|
|
168
|
+
try {
|
|
169
|
+
const { appId, key } = req.params;
|
|
170
|
+
const { value } = req.body;
|
|
171
|
+
|
|
172
|
+
if (value === undefined) {
|
|
173
|
+
res.status(400).json({ success: false, error: 'value is required' });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const serialized = JSON.stringify(value);
|
|
178
|
+
|
|
179
|
+
const entry = await prisma.appStorage.upsert({
|
|
180
|
+
where: { appId_key: { appId, key } },
|
|
181
|
+
update: { value: serialized },
|
|
182
|
+
create: { appId, key, value: serialized },
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
res.json({
|
|
186
|
+
success: true,
|
|
187
|
+
appId,
|
|
188
|
+
key,
|
|
189
|
+
value: JSON.parse(entry.value),
|
|
190
|
+
updatedAt: entry.updatedAt.toISOString(),
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
const message = getErrorMessage(err);
|
|
194
|
+
res.status(500).json({ success: false, error: message });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* DELETE /apps/:appId/storage/:key — Delete a key
|
|
200
|
+
*/
|
|
201
|
+
router.delete('/:appId/storage/:key', requireWalletAuth, requirePermission('app:storage'), enforceStorageScope, async (req: Request, res: Response) => {
|
|
202
|
+
try {
|
|
203
|
+
const { appId, key } = req.params;
|
|
204
|
+
|
|
205
|
+
const existing = await prisma.appStorage.findUnique({
|
|
206
|
+
where: { appId_key: { appId, key } },
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (!existing) {
|
|
210
|
+
res.status(404).json({ success: false, error: 'Key not found' });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await prisma.appStorage.delete({
|
|
215
|
+
where: { appId_key: { appId, key } },
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
res.json({ success: true, appId, key });
|
|
219
|
+
} catch (err) {
|
|
220
|
+
const message = getErrorMessage(err);
|
|
221
|
+
res.status(500).json({ success: false, error: message });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ─── App Messaging ──────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* POST /apps/:appId/message — Send a message to the app's AI
|
|
229
|
+
* Body: { message: string }
|
|
230
|
+
* Requires: app:storage permission (same as storage ops), scoped to own app
|
|
231
|
+
*/
|
|
232
|
+
router.post('/:appId/message', messageLimit, requireWalletAuth, requirePermission('app:storage'), enforceStorageScope, async (req: Request, res: Response) => {
|
|
233
|
+
const { appId } = req.params;
|
|
234
|
+
const { message, adapter } = req.body;
|
|
235
|
+
|
|
236
|
+
if (!message || typeof message !== 'string') {
|
|
237
|
+
res.status(400).json({ success: false, error: 'message is required' });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const adapterName = typeof adapter === 'string' ? adapter : 'dashboard';
|
|
243
|
+
|
|
244
|
+
// Fast path for built-in system chat: avoid queue + cron poll latency.
|
|
245
|
+
if (appId === '__system__') {
|
|
246
|
+
const direct = await handleAppMessage(appId, message, undefined, adapterName);
|
|
247
|
+
if (direct.error) {
|
|
248
|
+
res.status(400).json({ success: false, error: direct.error });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
res.json({ success: true, reply: direct.reply });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// agent-chat widget flow: process directly with the caller's scoped app token.
|
|
256
|
+
const auth = req.auth!;
|
|
257
|
+
const callerAppId = getCallerAppId(auth.token.agentId);
|
|
258
|
+
const isScopedWidgetCaller = !isAdmin(auth)
|
|
259
|
+
&& !auth.token.permissions.includes('app:storage:all')
|
|
260
|
+
&& callerAppId === appId;
|
|
261
|
+
|
|
262
|
+
if (appId === 'agent-chat' && isScopedWidgetCaller) {
|
|
263
|
+
const authHeader = req.header('authorization') || '';
|
|
264
|
+
const callerToken = authHeader.startsWith('Bearer ')
|
|
265
|
+
? authHeader.slice('Bearer '.length).trim()
|
|
266
|
+
: '';
|
|
267
|
+
|
|
268
|
+
if (!callerToken) {
|
|
269
|
+
res.status(401).json({ success: false, error: 'Missing bearer token' });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const direct = await handleAppMessage(appId, message, undefined, adapterName, callerToken);
|
|
274
|
+
if (direct.error) {
|
|
275
|
+
res.status(400).json({ success: false, error: direct.error });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
res.json({ success: true, reply: direct.reply });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const requestId = await enqueueAppMessage(appId, message, adapterName);
|
|
283
|
+
const timeoutMs = getDefaultSync<number>('strategy.message_timeout_ms', 120_000);
|
|
284
|
+
const result = await waitForQueuedAppMessage(requestId, timeoutMs);
|
|
285
|
+
|
|
286
|
+
if (result.status === 'timeout') {
|
|
287
|
+
res.status(504).json({ success: false, error: result.error || 'Timed out waiting for message processing' });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (result.status === 'error') {
|
|
292
|
+
res.status(400).json({ success: false, error: result.error || 'Message processing failed' });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
res.json({ success: true, reply: result.reply });
|
|
297
|
+
} catch (err) {
|
|
298
|
+
const msg = getErrorMessage(err);
|
|
299
|
+
res.status(500).json({ success: false, error: msg });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ─── Fetch Proxy ─────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
const ALLOWED_FETCH_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* POST /apps/:appId/fetch — Proxy an external HTTP request on behalf of a app
|
|
309
|
+
* Body: { url: string, method?: string, headers?: object, body?: string }
|
|
310
|
+
* Requires: app:storage permission (same as storage ops), scoped to own app
|
|
311
|
+
*/
|
|
312
|
+
router.post('/:appId/fetch', fetchRateLimit, requireWalletAuth, requirePermission('app:storage'), enforceStorageScope, async (req: Request, res: Response) => {
|
|
313
|
+
const { url, method = 'GET', headers, body } = req.body;
|
|
314
|
+
|
|
315
|
+
// Validate URL
|
|
316
|
+
if (!url || typeof url !== 'string') {
|
|
317
|
+
res.status(400).json({ success: false, error: 'url is required' });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// SSRF protection: validate protocol, resolve DNS, check for private IPs
|
|
322
|
+
try {
|
|
323
|
+
await validateExternalUrl(url);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
const msg = getErrorMessage(err);
|
|
326
|
+
res.status(403).json({ success: false, error: msg });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Validate method
|
|
331
|
+
const upperMethod = (typeof method === 'string' ? method : 'GET').toUpperCase();
|
|
332
|
+
if (!ALLOWED_FETCH_METHODS.includes(upperMethod)) {
|
|
333
|
+
res.status(400).json({ success: false, error: `Method must be one of: ${ALLOWED_FETCH_METHODS.join(', ')}` });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const fetchOpts: RequestInit = {
|
|
339
|
+
method: upperMethod,
|
|
340
|
+
redirect: 'error',
|
|
341
|
+
signal: AbortSignal.timeout(10000),
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
if (headers && typeof headers === 'object') {
|
|
345
|
+
fetchOpts.headers = headers;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (body !== undefined && upperMethod !== 'GET') {
|
|
349
|
+
fetchOpts.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const response = await fetch(url, fetchOpts);
|
|
353
|
+
|
|
354
|
+
// Try to parse as JSON, fall back to text
|
|
355
|
+
const contentType = response.headers.get('content-type') || '';
|
|
356
|
+
let data: unknown;
|
|
357
|
+
if (contentType.includes('application/json')) {
|
|
358
|
+
data = await response.json();
|
|
359
|
+
} else {
|
|
360
|
+
data = await response.text();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
res.json({ success: true, status: response.status, data });
|
|
364
|
+
} catch (err) {
|
|
365
|
+
if (err instanceof DOMException && err.name === 'TimeoutError') {
|
|
366
|
+
res.status(504).json({ success: false, error: 'Request timed out (10s)' });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const msg = getErrorMessage(err);
|
|
370
|
+
res.status(502).json({ success: false, error: msg });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* GET /apps/:appId/token — Get the pre-created token for a app
|
|
376
|
+
* Used by ThirdPartyApp to inject token into iframe blob.
|
|
377
|
+
*/
|
|
378
|
+
router.get('/:appId/token', requireWalletAuth, (req: Request, res: Response, next: NextFunction) => {
|
|
379
|
+
if (!isAdmin(req.auth!)) {
|
|
380
|
+
res.status(403).json({ success: false, error: 'Admin access required' });
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
next();
|
|
384
|
+
}, (req: Request, res: Response) => {
|
|
385
|
+
const { appId } = req.params;
|
|
386
|
+
const token = getAppToken(appId);
|
|
387
|
+
|
|
388
|
+
if (!token) {
|
|
389
|
+
res.status(404).json({ success: false, error: `No token for app "${appId}"` });
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
res.json({ success: true, token });
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// ─── API Key Access (app:accesskey) ────────────────────────────
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* GET /apps/:appId/apikey/:keyName — Get API key value from app storage
|
|
400
|
+
*/
|
|
401
|
+
router.get('/:appId/apikey/:keyName', requireWalletAuth, requirePermission('app:accesskey'), async (req: Request, res: Response) => {
|
|
402
|
+
try {
|
|
403
|
+
const { appId, keyName } = req.params;
|
|
404
|
+
|
|
405
|
+
const entry = await prisma.appStorage.findUnique({
|
|
406
|
+
where: { appId_key: { appId, key: keyName } },
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (!entry) {
|
|
410
|
+
res.status(404).json({ success: false, error: 'API key not found' });
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
res.json({
|
|
415
|
+
success: true,
|
|
416
|
+
appId,
|
|
417
|
+
keyName,
|
|
418
|
+
value: JSON.parse(entry.value),
|
|
419
|
+
});
|
|
420
|
+
} catch (err) {
|
|
421
|
+
const message = getErrorMessage(err);
|
|
422
|
+
res.status(500).json({ success: false, error: message });
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// ─── App Hot Reload (admin only) ───────────────────────────────
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* POST /apps/:appId/reload — Create token for a newly installed app
|
|
430
|
+
* without requiring a full server restart.
|
|
431
|
+
*/
|
|
432
|
+
router.post('/:appId/reload', requireWalletAuth, (req: Request, res: Response, next: NextFunction) => {
|
|
433
|
+
if (!isAdmin(req.auth!)) {
|
|
434
|
+
res.status(403).json({ success: false, error: 'Admin access required' });
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
next();
|
|
438
|
+
}, async (req: Request, res: Response) => {
|
|
439
|
+
const { appId } = req.params;
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
const token = await createAppToken(appId);
|
|
443
|
+
if (!token) {
|
|
444
|
+
res.status(500).json({ success: false, error: 'Failed to create app token' });
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
logger.appOperation('reload', appId);
|
|
449
|
+
res.json({ success: true, appId, reloaded: true });
|
|
450
|
+
} catch (err) {
|
|
451
|
+
const message = getErrorMessage(err);
|
|
452
|
+
res.status(500).json({ success: false, error: message });
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// ─── App Approval (strategy:manage) ────────────────────────────
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* POST /apps/:appId/approve — Approve app permissions
|
|
460
|
+
* Reads the app's manifest, creates a HumanAction record, enables strategy if loaded.
|
|
461
|
+
*/
|
|
462
|
+
router.post('/:appId/approve', requireWalletAuth, requirePermission('strategy:manage'), async (req: Request, res: Response) => {
|
|
463
|
+
try {
|
|
464
|
+
const { appId } = req.params;
|
|
465
|
+
|
|
466
|
+
// Find the app's manifest
|
|
467
|
+
const manifests = loadStrategyManifests();
|
|
468
|
+
const manifest = manifests.find(m => m.id === appId);
|
|
469
|
+
|
|
470
|
+
if (!manifest) {
|
|
471
|
+
res.status(404).json({ success: false, error: `App "${appId}" not found or has no strategy manifest` });
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Create or update approval record
|
|
476
|
+
// Find existing approval for this app
|
|
477
|
+
const existingApprovals = await prisma.humanAction.findMany({
|
|
478
|
+
where: { type: 'app:approve', status: 'approved' },
|
|
479
|
+
});
|
|
480
|
+
const existing = existingApprovals.find(a => {
|
|
481
|
+
try { return JSON.parse(a.metadata || '{}').appId === appId; } catch { return false; }
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const metadata = JSON.stringify({
|
|
485
|
+
appId,
|
|
486
|
+
permissions: manifest.permissions,
|
|
487
|
+
limits: manifest.limits || null,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const approval = existing
|
|
491
|
+
? await prisma.humanAction.update({
|
|
492
|
+
where: { id: existing.id },
|
|
493
|
+
data: { metadata, resolvedAt: new Date() },
|
|
494
|
+
})
|
|
495
|
+
: await prisma.humanAction.create({
|
|
496
|
+
data: {
|
|
497
|
+
type: 'app:approve',
|
|
498
|
+
fromTier: 'system',
|
|
499
|
+
chain: 'base',
|
|
500
|
+
status: 'approved',
|
|
501
|
+
resolvedAt: new Date(),
|
|
502
|
+
metadata,
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Create/replace app token in central registry
|
|
507
|
+
await createAppToken(appId);
|
|
508
|
+
|
|
509
|
+
// Keep explicit enable override aligned with approval flow.
|
|
510
|
+
await prisma.appStorage.upsert({
|
|
511
|
+
where: { appId_key: { appId, key: STRATEGY_ENABLED_STORAGE_KEY } },
|
|
512
|
+
create: {
|
|
513
|
+
appId,
|
|
514
|
+
key: STRATEGY_ENABLED_STORAGE_KEY,
|
|
515
|
+
value: JSON.stringify(true),
|
|
516
|
+
},
|
|
517
|
+
update: { value: JSON.stringify(true) },
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Enable the strategy if it's loaded and not already enabled
|
|
521
|
+
const runtime = getRuntime(appId);
|
|
522
|
+
if (runtime && !runtime.enabled) {
|
|
523
|
+
try {
|
|
524
|
+
await enableStrategy(appId);
|
|
525
|
+
} catch (err) {
|
|
526
|
+
console.error(`[apps] Failed to enable strategy ${appId} after approval:`, err);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
logger.appOperation('install', appId);
|
|
531
|
+
|
|
532
|
+
res.json({
|
|
533
|
+
success: true,
|
|
534
|
+
appId,
|
|
535
|
+
permissions: manifest.permissions,
|
|
536
|
+
limits: manifest.limits || null,
|
|
537
|
+
approvedAt: approval.resolvedAt?.toISOString() || approval.createdAt.toISOString(),
|
|
538
|
+
});
|
|
539
|
+
} catch (err) {
|
|
540
|
+
const message = getErrorMessage(err);
|
|
541
|
+
res.status(500).json({ success: false, error: message });
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* DELETE /apps/:appId/approve — Revoke app approval
|
|
547
|
+
* Disables the strategy and deletes the approval record.
|
|
548
|
+
*/
|
|
549
|
+
router.delete('/:appId/approve', requireWalletAuth, requirePermission('strategy:manage'), async (req: Request, res: Response) => {
|
|
550
|
+
try {
|
|
551
|
+
const { appId } = req.params;
|
|
552
|
+
|
|
553
|
+
// Disable the strategy if running
|
|
554
|
+
const runtime = getRuntime(appId);
|
|
555
|
+
if (runtime && runtime.enabled) {
|
|
556
|
+
try {
|
|
557
|
+
await disableStrategy(appId);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
console.error(`[apps] Failed to disable strategy ${appId} on revoke:`, err);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Revoke app token from central registry + add to revokedTokens set
|
|
564
|
+
await revokeAppToken(appId);
|
|
565
|
+
|
|
566
|
+
// Explicitly disable strategy runtime reconciliation for this app.
|
|
567
|
+
await prisma.appStorage.upsert({
|
|
568
|
+
where: { appId_key: { appId, key: STRATEGY_ENABLED_STORAGE_KEY } },
|
|
569
|
+
create: {
|
|
570
|
+
appId,
|
|
571
|
+
key: STRATEGY_ENABLED_STORAGE_KEY,
|
|
572
|
+
value: JSON.stringify(false),
|
|
573
|
+
},
|
|
574
|
+
update: { value: JSON.stringify(false) },
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Delete the approval record
|
|
578
|
+
try {
|
|
579
|
+
const approvalRecords = await prisma.humanAction.findMany({
|
|
580
|
+
where: { type: 'app:approve', status: 'approved' },
|
|
581
|
+
});
|
|
582
|
+
const existing = approvalRecords.find(a => {
|
|
583
|
+
try { return JSON.parse(a.metadata || '{}').appId === appId; } catch { return false; }
|
|
584
|
+
});
|
|
585
|
+
if (existing) {
|
|
586
|
+
await prisma.humanAction.delete({ where: { id: existing.id } });
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
// Record may not exist — that's fine
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
logger.appOperation('uninstall', appId);
|
|
593
|
+
|
|
594
|
+
res.json({ success: true, appId, revoked: true });
|
|
595
|
+
} catch (err) {
|
|
596
|
+
const message = getErrorMessage(err);
|
|
597
|
+
res.status(500).json({ success: false, error: message });
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
export default router;
|