auramaxx 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/README.md +77 -0
- package/apps/desktop-electron/main.js +428 -0
- package/bin/auramaxx.js +1063 -0
- package/docs/ADAPTERS.md +466 -0
- package/docs/AGENT_SETUP.md +159 -0
- package/docs/API.md +127 -0
- package/docs/APPS.md +199 -0
- package/docs/ARCHITECTURE.md +235 -0
- package/docs/AUTH.md +318 -0
- package/docs/BEST-PRACTICES.md +82 -0
- package/docs/CLI.md +141 -0
- package/docs/DESKTOP_ELECTRON.md +26 -0
- package/docs/DEVELOPING-APPS.md +453 -0
- package/docs/MCP.md +122 -0
- package/docs/PACKAGING_POLICY.md +19 -0
- package/docs/PERMISSION.md +137 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/README.md +50 -0
- package/docs/SKILLS.md +132 -0
- package/docs/TROUBLESHOOTING.md +376 -0
- package/docs/WORKSPACE.md +673 -0
- package/docs/agent-auth.md +14 -0
- package/docs/api/authentication.md +79 -0
- package/docs/api/secrets/api-keys.md +28 -0
- package/docs/api/secrets/credentials.md +80 -0
- package/docs/api/secrets/sharing.md +48 -0
- package/docs/api/system.md +41 -0
- package/docs/api/wallets/apps-strategies.md +66 -0
- package/docs/api/wallets/core.md +46 -0
- package/docs/api/wallets/data-portfolio.md +42 -0
- package/docs/aura-file.md +48 -0
- package/docs/core-concepts/FEATURES.md +114 -0
- package/docs/credentials.md +120 -0
- package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
- package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
- package/docs/external/getting-started.md +10 -0
- package/docs/external/overview.md +19 -0
- package/docs/external/persona-paths.md +7 -0
- package/docs/external/share-secret.md +76 -0
- package/docs/external/why-aura.md +7 -0
- package/docs/security.md +227 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -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 +28 -0
- package/package.json +167 -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/20260222090000_update_admin_ttl_default/migration.sql +10 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/actions.ts +474 -0
- package/server/cli/commands/api.ts +220 -0
- package/server/cli/commands/apikey.ts +277 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/auth.ts +464 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/diary.ts +274 -0
- package/server/cli/commands/doctor.ts +1247 -0
- package/server/cli/commands/env.ts +476 -0
- package/server/cli/commands/experimental.ts +69 -0
- package/server/cli/commands/init.ts +798 -0
- package/server/cli/commands/lock.ts +157 -0
- package/server/cli/commands/mcp.ts +285 -0
- package/server/cli/commands/quickhack.ts +86 -0
- package/server/cli/commands/release-check.ts +231 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/service.ts +320 -0
- package/server/cli/commands/shell-hook.ts +512 -0
- package/server/cli/commands/skill.ts +216 -0
- package/server/cli/commands/start.ts +139 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +36 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +50 -0
- package/server/cli/commands/vault.ts +1323 -0
- package/server/cli/commands/wallet.ts +209 -0
- package/server/cli/index.ts +280 -0
- package/server/cli/lib/approval-poll.ts +94 -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 +280 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/escalation.ts +57 -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/lock-unlock-helper.ts +71 -0
- package/server/cli/lib/process.ts +162 -0
- package/server/cli/lib/prompt.ts +294 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +579 -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 +420 -0
- package/server/lib/adapters/factory.ts +119 -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 +419 -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 +258 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/approval-link.ts +27 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/auto-execute.ts +160 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +1048 -0
- package/server/lib/config.ts +408 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +111 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +608 -0
- package/server/lib/credential-scope.ts +102 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +533 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +422 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +58 -0
- package/server/lib/defaults.ts +386 -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/diary.ts +34 -0
- package/server/lib/dont-ask-again-policy.ts +41 -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 +114 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +217 -0
- package/server/lib/feature-flags.ts +93 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/human-action-summary.ts +80 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +340 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +230 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +71 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +299 -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 +297 -0
- package/server/lib/resolve-action.ts +328 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/secret-gist-share.ts +296 -0
- package/server/lib/sessions.ts +634 -0
- package/server/lib/socket-path.ts +56 -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 +159 -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 +237 -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 +84 -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/update-check.ts +35 -0
- package/server/lib/verified-summary.ts +414 -0
- package/server/lib/view-registry.ts +80 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +1589 -0
- package/server/mcp/tools.ts +276 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +539 -0
- package/server/routes/adapters.ts +711 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +343 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +406 -0
- package/server/routes/backup.ts +404 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +380 -0
- package/server/routes/credential-vaults.ts +159 -0
- package/server/routes/credentials.ts +1782 -0
- package/server/routes/dashboard.ts +97 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/flags.ts +11 -0
- package/server/routes/fund.ts +225 -0
- package/server/routes/heartbeat.ts +375 -0
- package/server/routes/import.ts +364 -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 +366 -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 +352 -0
- package/server/routes/swap-solana.ts +176 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +467 -0
- package/server/routes/views.ts +41 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +709 -0
- package/server/types.ts +146 -0
- package/shared/credential-field-schema.ts +248 -0
- package/skills/auramaxx/HEARTBEAT.md +78 -0
- package/skills/auramaxx/SKILL.md +745 -0
- package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
- package/skills/auramaxx/docs/API.md +127 -0
- package/skills/auramaxx/docs/AUTH.md +318 -0
- package/skills/auramaxx/docs/CLI.md +130 -0
- package/skills/auramaxx/docs/MCP.md +122 -0
- package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
- package/skills/auramaxx/docs/WORKSPACE.md +673 -0
- package/skills/auramaxx/docs/security.md +227 -0
- package/skills/task-lifecycle/SKILL.md +378 -0
- package/src/app/api/[...doc]/page.tsx +36 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +132 -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/docs/plain/route.ts +74 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +290 -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 +40 -0
- package/src/app/api/workspace/config/route.ts +121 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/approve/[actionId]/page.tsx +409 -0
- package/src/app/docs/DocsPageContent.tsx +269 -0
- package/src/app/docs/[...doc]/page.tsx +41 -0
- package/src/app/docs/page.tsx +38 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +819 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +102 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +39 -0
- package/src/app/page.tsx +1964 -0
- package/src/app/privacy/page.tsx +63 -0
- package/src/app/providers.tsx +87 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/app/terms/page.tsx +80 -0
- package/src/components/ChainSelector.tsx +44 -0
- package/src/components/HumanActionBar.tsx +697 -0
- package/src/components/NotificationDrawer.tsx +387 -0
- package/src/components/PasskeyEnrollmentPrompt.tsx +235 -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 +88 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +147 -0
- package/src/components/design-system/ConfirmationModal.tsx +107 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/DownloadButton.tsx +149 -0
- package/src/components/design-system/Drawer.tsx +133 -0
- package/src/components/design-system/FilterDropdown.tsx +183 -0
- package/src/components/design-system/ItemPicker.tsx +157 -0
- package/src/components/design-system/Modal.tsx +296 -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 +65 -0
- package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
- package/src/components/design-system/index.ts +14 -0
- package/src/components/docs/ClientSideMarkdown.tsx +51 -0
- package/src/components/docs/DocsSearchBar.tsx +118 -0
- package/src/components/docs/DocsThemeToggle.tsx +38 -0
- package/src/components/docs/PersistentDocGroup.tsx +91 -0
- package/src/components/docs/ShareUrlButton.tsx +33 -0
- package/src/components/docs/SidebarScrollMemory.tsx +56 -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/CreateViewModal.tsx +88 -0
- package/src/components/layout/LeftRail.tsx +114 -0
- package/src/components/layout/TabBar.tsx +284 -0
- package/src/components/layout/WalletSidebar.tsx +1030 -0
- package/src/components/layout/index.ts +6 -0
- package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1272 -0
- package/src/components/vault/AuditConsole.tsx +600 -0
- package/src/components/vault/CredentialDetail.tsx +625 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +583 -0
- package/src/components/vault/CredentialForm.tsx +1484 -0
- package/src/components/vault/CredentialList.tsx +265 -0
- package/src/components/vault/CredentialRow.tsx +130 -0
- package/src/components/vault/CredentialShareModal.tsx +273 -0
- package/src/components/vault/CredentialVault.tsx +1662 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/DocsConsole.tsx +113 -0
- package/src/components/vault/ImportCredentialsModal.tsx +578 -0
- package/src/components/vault/LargeTypeModal.tsx +88 -0
- package/src/components/vault/PasswordGenerator.tsx +232 -0
- package/src/components/vault/TOTPDisplay.tsx +108 -0
- package/src/components/vault/TotpSetupPanel.tsx +198 -0
- package/src/components/vault/VaultSidebar.tsx +881 -0
- package/src/components/vault/credentialFormName.ts +91 -0
- package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
- package/src/components/vault/types.ts +56 -0
- package/src/context/AuthContext.tsx +365 -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 +4 -0
- package/src/hooks/useAgentActions.ts +552 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/hooks/useTheme.ts +156 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api-docs.ts +154 -0
- package/src/lib/api.ts +474 -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/credential-field-schema.ts +11 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +544 -0
- package/src/lib/events.ts +363 -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/totp-import.ts +57 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/view-registry.ts +57 -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 +170 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { HttpError } from './error';
|
|
5
|
+
|
|
6
|
+
// Production data lives in ~/.auramaxx/ (outside repo, safe from tests)
|
|
7
|
+
// Tests override via WALLET_DATA_DIR=server/test-data
|
|
8
|
+
const DEFAULT_DATA_DIR = path.join(os.homedir(), '.auramaxx');
|
|
9
|
+
|
|
10
|
+
// Lazy — read env at access time so tests can override WALLET_DATA_DIR
|
|
11
|
+
function getDataDir(): string {
|
|
12
|
+
const dir = process.env.WALLET_DATA_DIR || DEFAULT_DATA_DIR;
|
|
13
|
+
// Safety: never let tests write to the real user data directory.
|
|
14
|
+
if (dir === DEFAULT_DATA_DIR && (process.env.VITEST || process.env.NODE_ENV === 'test')) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`WALLET_DATA_DIR is not set — refusing to use ${DEFAULT_DATA_DIR} in test environment. ` +
|
|
17
|
+
'Set WALLET_DATA_DIR to a temp directory before running tests.'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
function getConfigPath(): string {
|
|
23
|
+
return path.join(getDataDir(), 'config.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the database file path (inside the data dir).
|
|
28
|
+
* Tests override via DATABASE_URL env var.
|
|
29
|
+
*/
|
|
30
|
+
export function getDbPath(): string {
|
|
31
|
+
return path.join(getDataDir(), 'auramaxx.db');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Backups dir — stored outside the main data dir so that nuking
|
|
36
|
+
* ~/.auramaxx/ doesn't destroy backups.
|
|
37
|
+
*
|
|
38
|
+
* Production: ~/.aurabak/
|
|
39
|
+
* Tests: <WALLET_DATA_DIR>/backups (stays inside the temp dir)
|
|
40
|
+
*/
|
|
41
|
+
const DEFAULT_BACKUPS_DIR = path.join(os.homedir(), '.aurabak');
|
|
42
|
+
|
|
43
|
+
export function getBackupsDir(): string {
|
|
44
|
+
const dataDir = process.env.WALLET_DATA_DIR;
|
|
45
|
+
// In test environments, keep backups inside the test data dir
|
|
46
|
+
if (dataDir && (process.env.VITEST || process.env.NODE_ENV === 'test')) {
|
|
47
|
+
return path.join(dataDir, 'backups');
|
|
48
|
+
}
|
|
49
|
+
return DEFAULT_BACKUPS_DIR;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get DATABASE_URL for Prisma, pointing to the data dir.
|
|
54
|
+
* Returns the env override if set to a non-default value.
|
|
55
|
+
*/
|
|
56
|
+
export function getDbUrl(): string {
|
|
57
|
+
const envUrl = process.env.DATABASE_URL;
|
|
58
|
+
// If explicitly set (e.g. tests), use it
|
|
59
|
+
if (envUrl) {
|
|
60
|
+
return envUrl;
|
|
61
|
+
}
|
|
62
|
+
return `file:${getDbPath()}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ChainConfig {
|
|
66
|
+
rpc: string;
|
|
67
|
+
chainId: number;
|
|
68
|
+
explorer: string;
|
|
69
|
+
nativeCurrency: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface WalletConfig {
|
|
73
|
+
chains: Record<string, ChainConfig>;
|
|
74
|
+
defaultChain: string;
|
|
75
|
+
server: {
|
|
76
|
+
port: number;
|
|
77
|
+
host: string;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const DEFAULT_CONFIG: WalletConfig = {
|
|
82
|
+
chains: {
|
|
83
|
+
base: {
|
|
84
|
+
rpc: 'https://mainnet.base.org',
|
|
85
|
+
chainId: 8453,
|
|
86
|
+
explorer: 'https://basescan.org',
|
|
87
|
+
nativeCurrency: 'ETH'
|
|
88
|
+
},
|
|
89
|
+
ethereum: {
|
|
90
|
+
rpc: 'https://eth.llamarpc.com',
|
|
91
|
+
chainId: 1,
|
|
92
|
+
explorer: 'https://etherscan.io',
|
|
93
|
+
nativeCurrency: 'ETH'
|
|
94
|
+
},
|
|
95
|
+
solana: {
|
|
96
|
+
rpc: 'https://api.mainnet-beta.solana.com',
|
|
97
|
+
chainId: 0,
|
|
98
|
+
explorer: 'https://solscan.io',
|
|
99
|
+
nativeCurrency: 'SOL'
|
|
100
|
+
},
|
|
101
|
+
'solana-devnet': {
|
|
102
|
+
rpc: 'https://api.devnet.solana.com',
|
|
103
|
+
chainId: 0,
|
|
104
|
+
explorer: 'https://solscan.io/?cluster=devnet',
|
|
105
|
+
nativeCurrency: 'SOL'
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
defaultChain: 'base',
|
|
109
|
+
server: {
|
|
110
|
+
port: 4242,
|
|
111
|
+
host: '127.0.0.1'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export function ensureDataDir(): void {
|
|
116
|
+
const dataDir = getDataDir();
|
|
117
|
+
const dirs = [
|
|
118
|
+
dataDir,
|
|
119
|
+
path.join(dataDir, 'hot'),
|
|
120
|
+
path.join(dataDir, 'pending'),
|
|
121
|
+
path.join(dataDir, 'credentials'),
|
|
122
|
+
path.join(dataDir, 'credentials-archive'),
|
|
123
|
+
path.join(dataDir, 'credentials-recently-deleted'),
|
|
124
|
+
path.join(dataDir, 'credential-shares'),
|
|
125
|
+
];
|
|
126
|
+
dirs.forEach(dir => {
|
|
127
|
+
if (!fs.existsSync(dir)) {
|
|
128
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
129
|
+
} else {
|
|
130
|
+
// Ensure correct permissions on existing dirs
|
|
131
|
+
try { fs.chmodSync(dir, 0o700); } catch {}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Auto-migrate from legacy in-repo data/ to ~/.auramaxx/
|
|
136
|
+
if (dataDir === DEFAULT_DATA_DIR) {
|
|
137
|
+
migrateFromLegacyDir();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* One-time migration: copy vault files, hot wallets, config from old data/ to ~/.auramaxx/.
|
|
143
|
+
* Only runs if ~/.auramaxx/.migrated marker doesn't exist and data/ has content.
|
|
144
|
+
*/
|
|
145
|
+
let migrationAttempted = false;
|
|
146
|
+
function migrateFromLegacyDir(): void {
|
|
147
|
+
if (migrationAttempted) return;
|
|
148
|
+
migrationAttempted = true;
|
|
149
|
+
|
|
150
|
+
const marker = path.join(DEFAULT_DATA_DIR, '.migrated');
|
|
151
|
+
if (fs.existsSync(marker)) return;
|
|
152
|
+
|
|
153
|
+
// Look for legacy data/ in common locations
|
|
154
|
+
const candidates = [
|
|
155
|
+
path.join(process.cwd(), 'data'),
|
|
156
|
+
path.join(process.cwd(), 'server', 'data'),
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
for (const legacyDir of candidates) {
|
|
160
|
+
if (!fs.existsSync(legacyDir)) continue;
|
|
161
|
+
|
|
162
|
+
const files = fs.readdirSync(legacyDir);
|
|
163
|
+
const hasVaultFiles = files.some(f => f.startsWith('vault-') || f === 'cold.json' || f === 'config.json');
|
|
164
|
+
if (!hasVaultFiles) continue;
|
|
165
|
+
|
|
166
|
+
console.log(`Migrating wallet data: ${legacyDir} → ${DEFAULT_DATA_DIR}`);
|
|
167
|
+
|
|
168
|
+
// Copy files (don't delete originals — user can do that manually)
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
const src = path.join(legacyDir, file);
|
|
171
|
+
const dest = path.join(DEFAULT_DATA_DIR, file);
|
|
172
|
+
const stat = fs.statSync(src);
|
|
173
|
+
|
|
174
|
+
if (stat.isFile() && !fs.existsSync(dest)) {
|
|
175
|
+
fs.copyFileSync(src, dest);
|
|
176
|
+
console.log(` copied ${file}`);
|
|
177
|
+
} else if (stat.isDirectory()) {
|
|
178
|
+
// Copy subdirectories (hot/, pending/)
|
|
179
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true, mode: 0o700 });
|
|
180
|
+
const subFiles = fs.readdirSync(src);
|
|
181
|
+
for (const sub of subFiles) {
|
|
182
|
+
const subSrc = path.join(src, sub);
|
|
183
|
+
const subDest = path.join(dest, sub);
|
|
184
|
+
if (fs.statSync(subSrc).isFile() && !fs.existsSync(subDest)) {
|
|
185
|
+
fs.copyFileSync(subSrc, subDest);
|
|
186
|
+
console.log(` copied ${file}/${sub}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Also migrate dev.db if it exists nearby
|
|
193
|
+
const dbCandidates = [
|
|
194
|
+
path.join(legacyDir, '..', 'dev.db'),
|
|
195
|
+
path.join(legacyDir, '..', 'prisma', 'dev.db'),
|
|
196
|
+
];
|
|
197
|
+
const destDb = path.join(DEFAULT_DATA_DIR, 'auramaxx.db');
|
|
198
|
+
if (!fs.existsSync(destDb)) {
|
|
199
|
+
for (const dbSrc of dbCandidates) {
|
|
200
|
+
if (fs.existsSync(dbSrc) && fs.statSync(dbSrc).size > 0) {
|
|
201
|
+
fs.copyFileSync(dbSrc, destDb);
|
|
202
|
+
console.log(` copied database → auramaxx.db`);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
break; // Only migrate from first found
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Write marker so we don't re-run
|
|
212
|
+
fs.writeFileSync(marker, new Date().toISOString());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function loadConfig(): WalletConfig {
|
|
216
|
+
ensureDataDir();
|
|
217
|
+
const configPath = getConfigPath();
|
|
218
|
+
if (!fs.existsSync(configPath)) {
|
|
219
|
+
saveConfig(DEFAULT_CONFIG);
|
|
220
|
+
return DEFAULT_CONFIG;
|
|
221
|
+
}
|
|
222
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
223
|
+
const fileConfig = JSON.parse(raw);
|
|
224
|
+
return {
|
|
225
|
+
...DEFAULT_CONFIG,
|
|
226
|
+
...fileConfig,
|
|
227
|
+
// Deep-merge chains so defaults (e.g. solana) aren't lost
|
|
228
|
+
chains: { ...DEFAULT_CONFIG.chains, ...fileConfig.chains },
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function saveConfig(config: WalletConfig): void {
|
|
233
|
+
ensureDataDir();
|
|
234
|
+
fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const DATA_PATHS = {
|
|
238
|
+
get config() { return getConfigPath(); },
|
|
239
|
+
get wallets() { return getDataDir(); },
|
|
240
|
+
get hotWallets() { return path.join(getDataDir(), 'hot'); },
|
|
241
|
+
get pending() { return path.join(getDataDir(), 'pending'); },
|
|
242
|
+
get credentials() { return path.join(getDataDir(), 'credentials'); },
|
|
243
|
+
get credentialsArchive() { return path.join(getDataDir(), 'credentials-archive'); },
|
|
244
|
+
get credentialsRecentlyDeleted() { return path.join(getDataDir(), 'credentials-recently-deleted'); },
|
|
245
|
+
get credentialShares() { return path.join(getDataDir(), 'credential-shares'); },
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export const SERVER_PORT = process.env.WALLET_SERVER_PORT
|
|
249
|
+
? parseInt(process.env.WALLET_SERVER_PORT, 10)
|
|
250
|
+
: 4242;
|
|
251
|
+
|
|
252
|
+
// Fallback public RPCs for common chains
|
|
253
|
+
const PUBLIC_RPCS: Record<string, ChainConfig> = {
|
|
254
|
+
base: {
|
|
255
|
+
rpc: 'https://mainnet.base.org',
|
|
256
|
+
chainId: 8453,
|
|
257
|
+
explorer: 'https://basescan.org',
|
|
258
|
+
nativeCurrency: 'ETH',
|
|
259
|
+
},
|
|
260
|
+
ethereum: {
|
|
261
|
+
rpc: 'https://eth.llamarpc.com',
|
|
262
|
+
chainId: 1,
|
|
263
|
+
explorer: 'https://etherscan.io',
|
|
264
|
+
nativeCurrency: 'ETH',
|
|
265
|
+
},
|
|
266
|
+
arbitrum: {
|
|
267
|
+
rpc: 'https://arb1.arbitrum.io/rpc',
|
|
268
|
+
chainId: 42161,
|
|
269
|
+
explorer: 'https://arbiscan.io',
|
|
270
|
+
nativeCurrency: 'ETH',
|
|
271
|
+
},
|
|
272
|
+
optimism: {
|
|
273
|
+
rpc: 'https://mainnet.optimism.io',
|
|
274
|
+
chainId: 10,
|
|
275
|
+
explorer: 'https://optimistic.etherscan.io',
|
|
276
|
+
nativeCurrency: 'ETH',
|
|
277
|
+
},
|
|
278
|
+
polygon: {
|
|
279
|
+
rpc: 'https://polygon-rpc.com',
|
|
280
|
+
chainId: 137,
|
|
281
|
+
explorer: 'https://polygonscan.com',
|
|
282
|
+
nativeCurrency: 'MATIC',
|
|
283
|
+
},
|
|
284
|
+
solana: {
|
|
285
|
+
rpc: 'https://api.mainnet-beta.solana.com',
|
|
286
|
+
chainId: 0,
|
|
287
|
+
explorer: 'https://solscan.io',
|
|
288
|
+
nativeCurrency: 'SOL',
|
|
289
|
+
},
|
|
290
|
+
'solana-devnet': {
|
|
291
|
+
rpc: 'https://api.devnet.solana.com',
|
|
292
|
+
chainId: 0,
|
|
293
|
+
explorer: 'https://solscan.io/?cluster=devnet',
|
|
294
|
+
nativeCurrency: 'SOL',
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// Alchemy paths for chains
|
|
299
|
+
export const ALCHEMY_PATHS: Record<string, { path: string; chainId: number; explorer: string }> = {
|
|
300
|
+
base: { path: 'base-mainnet', chainId: 8453, explorer: 'https://basescan.org' },
|
|
301
|
+
ethereum: { path: 'eth-mainnet', chainId: 1, explorer: 'https://etherscan.io' },
|
|
302
|
+
arbitrum: { path: 'arb-mainnet', chainId: 42161, explorer: 'https://arbiscan.io' },
|
|
303
|
+
optimism: { path: 'opt-mainnet', chainId: 10, explorer: 'https://optimistic.etherscan.io' },
|
|
304
|
+
polygon: { path: 'polygon-mainnet', chainId: 137, explorer: 'https://polygonscan.com' },
|
|
305
|
+
solana: { path: 'solana-mainnet', chainId: 0, explorer: 'https://solscan.io' },
|
|
306
|
+
'solana-devnet': { path: 'solana-devnet', chainId: 0, explorer: 'https://solscan.io/?cluster=devnet' },
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Import prisma lazily to avoid circular deps
|
|
310
|
+
let _prisma: typeof import('./db').prisma | null = null;
|
|
311
|
+
async function getPrisma() {
|
|
312
|
+
if (!_prisma) {
|
|
313
|
+
const { prisma } = await import('./db');
|
|
314
|
+
_prisma = prisma;
|
|
315
|
+
}
|
|
316
|
+
return _prisma;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get Alchemy API key from vault-backed API key credentials.
|
|
321
|
+
*/
|
|
322
|
+
export async function getAlchemyKey(): Promise<string | null> {
|
|
323
|
+
try {
|
|
324
|
+
const {
|
|
325
|
+
ensureApiKeysMigrated,
|
|
326
|
+
readApiKeyValueByService,
|
|
327
|
+
} = await import('./apikey-migration');
|
|
328
|
+
await ensureApiKeysMigrated();
|
|
329
|
+
return readApiKeyValueByService('alchemy');
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get chain overrides from database (AppConfig.chainConfig)
|
|
337
|
+
*/
|
|
338
|
+
async function getChainOverrides(): Promise<Record<string, ChainConfig>> {
|
|
339
|
+
try {
|
|
340
|
+
const prisma = await getPrisma();
|
|
341
|
+
const appConfig = await prisma.appConfig.findUnique({
|
|
342
|
+
where: { id: 'global' },
|
|
343
|
+
});
|
|
344
|
+
if (appConfig?.chainConfig) {
|
|
345
|
+
return JSON.parse(appConfig.chainConfig);
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
// Ignore errors
|
|
349
|
+
}
|
|
350
|
+
return {};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Get RPC URL for a chain with priority:
|
|
355
|
+
* 1. User-configured override (from DB)
|
|
356
|
+
* 2. Alchemy API key (if available)
|
|
357
|
+
* 3. File config
|
|
358
|
+
* 4. Public RPC fallback
|
|
359
|
+
*/
|
|
360
|
+
export async function getRpcUrl(chain: string = 'base'): Promise<string> {
|
|
361
|
+
// 1. Check DB overrides
|
|
362
|
+
const overrides = await getChainOverrides();
|
|
363
|
+
if (overrides[chain]?.rpc) {
|
|
364
|
+
return overrides[chain].rpc;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 2. Check for Alchemy key
|
|
368
|
+
const alchemyKey = await getAlchemyKey();
|
|
369
|
+
const alchemyConfig = ALCHEMY_PATHS[chain];
|
|
370
|
+
if (alchemyKey && alchemyConfig) {
|
|
371
|
+
return `https://${alchemyConfig.path}.g.alchemy.com/v2/${alchemyKey}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 3. Check file config
|
|
375
|
+
const config = loadConfig();
|
|
376
|
+
if (config.chains[chain]?.rpc) {
|
|
377
|
+
return config.chains[chain].rpc;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// 4. Fallback to public RPC
|
|
381
|
+
return PUBLIC_RPCS[chain]?.rpc || PUBLIC_RPCS.base.rpc;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Sync version of getRpcUrl - uses file config only (no DB)
|
|
386
|
+
* Use this when you can't await (e.g., in sync contexts)
|
|
387
|
+
*/
|
|
388
|
+
export function getRpcUrlSync(chain: string = 'base'): string {
|
|
389
|
+
const config = loadConfig();
|
|
390
|
+
if (config.chains[chain]?.rpc) {
|
|
391
|
+
return config.chains[chain].rpc;
|
|
392
|
+
}
|
|
393
|
+
return PUBLIC_RPCS[chain]?.rpc || PUBLIC_RPCS.base.rpc;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Resolve chain name to config. Uses defaultChain when chain is omitted.
|
|
398
|
+
* Throws HttpError(400) if the chain is unknown.
|
|
399
|
+
*/
|
|
400
|
+
export function resolveChain(chain?: string): { targetChain: string; chainConfig: ChainConfig } {
|
|
401
|
+
const config = loadConfig();
|
|
402
|
+
const targetChain = chain || config.defaultChain;
|
|
403
|
+
const chainConfig = config.chains[targetChain];
|
|
404
|
+
if (!chainConfig) {
|
|
405
|
+
throw new HttpError(400, `Unknown chain: ${targetChain}`);
|
|
406
|
+
}
|
|
407
|
+
return { targetChain, chainConfig };
|
|
408
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { prisma } from './db';
|
|
2
|
+
import { CredentialAccessAction, CredentialAccessReasonCode } from './credential-access-policy';
|
|
3
|
+
|
|
4
|
+
interface WriteCredentialAccessAuditInput {
|
|
5
|
+
credentialId: string;
|
|
6
|
+
vaultId: string;
|
|
7
|
+
action: CredentialAccessAction;
|
|
8
|
+
allowed: boolean;
|
|
9
|
+
reasonCode: CredentialAccessReasonCode;
|
|
10
|
+
httpStatus: number;
|
|
11
|
+
tokenHash?: string;
|
|
12
|
+
agentId?: string;
|
|
13
|
+
requestId?: string;
|
|
14
|
+
actorType: 'agent' | 'admin' | 'unknown';
|
|
15
|
+
projectScope?: string | null;
|
|
16
|
+
metadata?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function safeJson(value: Record<string, unknown> | undefined): string | null {
|
|
20
|
+
if (!value) return null;
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function writeCredentialAccessAudit(input: WriteCredentialAccessAuditInput): Promise<string> {
|
|
25
|
+
const row = await prisma.credentialAccessAudit.create({
|
|
26
|
+
data: {
|
|
27
|
+
credentialId: input.credentialId,
|
|
28
|
+
vaultId: input.vaultId,
|
|
29
|
+
action: input.action,
|
|
30
|
+
allowed: input.allowed,
|
|
31
|
+
result: input.allowed ? 'allow' : 'deny',
|
|
32
|
+
reasonCode: input.reasonCode,
|
|
33
|
+
httpStatus: input.httpStatus,
|
|
34
|
+
tokenHash: input.tokenHash,
|
|
35
|
+
agentId: input.agentId,
|
|
36
|
+
requestId: input.requestId,
|
|
37
|
+
actorType: input.actorType,
|
|
38
|
+
projectScope: input.projectScope ?? null,
|
|
39
|
+
sensitiveRead: true,
|
|
40
|
+
metadata: safeJson(input.metadata),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return row.id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function listRecentCredentialAccess(limit = 50) {
|
|
48
|
+
return prisma.credentialAccessAudit.findMany({
|
|
49
|
+
take: Math.min(Math.max(limit, 1), 200),
|
|
50
|
+
orderBy: { timestamp: 'desc' },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function listNoisyCredentials(windowMs = 60 * 60 * 1000, limit = 20) {
|
|
55
|
+
const windowStart = new Date(Date.now() - Math.max(windowMs, 60_000));
|
|
56
|
+
const rows = await prisma.credentialAccessAudit.groupBy({
|
|
57
|
+
by: ['credentialId'],
|
|
58
|
+
where: {
|
|
59
|
+
timestamp: { gte: windowStart },
|
|
60
|
+
OR: [{ allowed: false }, { reasonCode: 'CREDENTIAL_RATE_LIMIT_EXCEEDED' }],
|
|
61
|
+
},
|
|
62
|
+
_count: { _all: true },
|
|
63
|
+
orderBy: { _count: { credentialId: 'desc' } },
|
|
64
|
+
take: Math.min(Math.max(limit, 1), 100),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return rows.map((row) => ({ credentialId: row.credentialId, count: row._count._all }));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function listNoisyCredentialTokens(windowMs = 60 * 60 * 1000, limit = 20) {
|
|
71
|
+
const windowStart = new Date(Date.now() - Math.max(windowMs, 60_000));
|
|
72
|
+
const rows = await prisma.credentialAccessAudit.groupBy({
|
|
73
|
+
by: ['tokenHash'],
|
|
74
|
+
where: {
|
|
75
|
+
tokenHash: { not: null },
|
|
76
|
+
timestamp: { gte: windowStart },
|
|
77
|
+
OR: [{ allowed: false }, { reasonCode: 'CREDENTIAL_RATE_LIMIT_EXCEEDED' }],
|
|
78
|
+
},
|
|
79
|
+
_count: { _all: true },
|
|
80
|
+
orderBy: { _count: { tokenHash: 'desc' } },
|
|
81
|
+
take: Math.min(Math.max(limit, 1), 100),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return rows.map((row) => ({ tokenHash: row.tokenHash, count: row._count._all }));
|
|
85
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { AgentTokenPayload } from '../types';
|
|
2
|
+
import { checkCredentialAccess } from './sessions';
|
|
3
|
+
|
|
4
|
+
export type CredentialAccessAction = 'credentials.read' | 'credentials.totp';
|
|
5
|
+
|
|
6
|
+
export type CredentialAccessReasonCode =
|
|
7
|
+
| 'ALLOW'
|
|
8
|
+
| 'TOKEN_TTL_EXPIRED'
|
|
9
|
+
| 'TOKEN_MAX_READS_EXCEEDED'
|
|
10
|
+
| 'CREDENTIAL_RATE_LIMIT_EXCEEDED'
|
|
11
|
+
| 'DENY_EXCLUDED_FIELD'
|
|
12
|
+
| 'CREDENTIAL_SCOPE_DENIED'
|
|
13
|
+
| 'TOKEN_PERMISSION_DENIED'
|
|
14
|
+
| 'TOKEN_AGENT_PUBKEY_MISSING'
|
|
15
|
+
| 'CREDENTIAL_TOTP_NOT_CONFIGURED';
|
|
16
|
+
|
|
17
|
+
export interface CredentialAccessDecision {
|
|
18
|
+
allowed: boolean;
|
|
19
|
+
reasonCode: CredentialAccessReasonCode;
|
|
20
|
+
httpStatus: 200 | 403 | 429;
|
|
21
|
+
limiterWindowMs?: number;
|
|
22
|
+
limiterLimit?: number;
|
|
23
|
+
limiterCount?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface CredentialLimiterState {
|
|
27
|
+
timestamps: number[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const credentialAccessLimiter = new Map<string, CredentialLimiterState>();
|
|
31
|
+
|
|
32
|
+
const READ_LIMIT_PER_MIN = parsePositiveInt(process.env.AURA_CRED_READ_LIMIT_PER_MIN, 60);
|
|
33
|
+
const TOTP_LIMIT_PER_MIN = parsePositiveInt(process.env.AURA_CRED_TOTP_LIMIT_PER_MIN, 10);
|
|
34
|
+
const LIMIT_WINDOW_MS = 60_000;
|
|
35
|
+
|
|
36
|
+
function parsePositiveInt(value: string | undefined, fallback: number): number {
|
|
37
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
38
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function toSessionReasonCode(reason: string): 'TOKEN_TTL_EXPIRED' | 'TOKEN_MAX_READS_EXCEEDED' {
|
|
43
|
+
if (reason.includes('TTL')) return 'TOKEN_TTL_EXPIRED';
|
|
44
|
+
return 'TOKEN_MAX_READS_EXCEEDED';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getActionLimit(action: CredentialAccessAction): number {
|
|
48
|
+
return action === 'credentials.totp' ? TOTP_LIMIT_PER_MIN : READ_LIMIT_PER_MIN;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function checkPerCredentialRateLimit(
|
|
52
|
+
credentialId: string,
|
|
53
|
+
action: CredentialAccessAction,
|
|
54
|
+
nowMs: number,
|
|
55
|
+
): { allowed: true; count: number; limit: number } | { allowed: false; count: number; limit: number } {
|
|
56
|
+
const limit = getActionLimit(action);
|
|
57
|
+
const key = `${credentialId}:${action}`;
|
|
58
|
+
const state = credentialAccessLimiter.get(key) ?? { timestamps: [] };
|
|
59
|
+
state.timestamps = state.timestamps.filter((timestamp) => nowMs - timestamp < LIMIT_WINDOW_MS);
|
|
60
|
+
|
|
61
|
+
if (state.timestamps.length >= limit) {
|
|
62
|
+
credentialAccessLimiter.set(key, state);
|
|
63
|
+
return { allowed: false, count: state.timestamps.length, limit };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
state.timestamps.push(nowMs);
|
|
67
|
+
credentialAccessLimiter.set(key, state);
|
|
68
|
+
return { allowed: true, count: state.timestamps.length, limit };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function evaluateCredentialAccess(params: {
|
|
72
|
+
tokenHash: string;
|
|
73
|
+
token: AgentTokenPayload;
|
|
74
|
+
credentialId: string;
|
|
75
|
+
action: CredentialAccessAction;
|
|
76
|
+
nowMs?: number;
|
|
77
|
+
}): CredentialAccessDecision {
|
|
78
|
+
const sessionCheck = checkCredentialAccess(params.tokenHash, params.token);
|
|
79
|
+
if (!sessionCheck.ok) {
|
|
80
|
+
return {
|
|
81
|
+
allowed: false,
|
|
82
|
+
reasonCode: toSessionReasonCode(sessionCheck.reason),
|
|
83
|
+
httpStatus: 403,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const limiter = checkPerCredentialRateLimit(params.credentialId, params.action, params.nowMs ?? Date.now());
|
|
88
|
+
if (!limiter.allowed) {
|
|
89
|
+
return {
|
|
90
|
+
allowed: false,
|
|
91
|
+
reasonCode: 'CREDENTIAL_RATE_LIMIT_EXCEEDED',
|
|
92
|
+
httpStatus: 429,
|
|
93
|
+
limiterWindowMs: LIMIT_WINDOW_MS,
|
|
94
|
+
limiterLimit: limiter.limit,
|
|
95
|
+
limiterCount: limiter.count,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
allowed: true,
|
|
101
|
+
reasonCode: 'ALLOW',
|
|
102
|
+
httpStatus: 200,
|
|
103
|
+
limiterWindowMs: LIMIT_WINDOW_MS,
|
|
104
|
+
limiterLimit: limiter.limit,
|
|
105
|
+
limiterCount: limiter.count,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function resetCredentialAccessLimiterForTests(): void {
|
|
110
|
+
credentialAccessLimiter.clear();
|
|
111
|
+
}
|