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,190 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createHash, randomBytes, timingSafeEqual } from 'crypto';
|
|
4
|
+
import { DATA_PATHS } from './config';
|
|
5
|
+
|
|
6
|
+
export type ShareExpiresAfter = '15m' | '1h' | '24h' | '7d' | '30d';
|
|
7
|
+
export type ShareAccessMode = 'anyone' | 'password';
|
|
8
|
+
|
|
9
|
+
export interface CredentialShareFile {
|
|
10
|
+
token: string;
|
|
11
|
+
credentialId: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
createdBy: string;
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
accessMode: ShareAccessMode;
|
|
16
|
+
passwordSalt?: string;
|
|
17
|
+
passwordHash?: string;
|
|
18
|
+
oneTimeOnly: boolean;
|
|
19
|
+
viewCount: number;
|
|
20
|
+
lastViewedAt: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CreateCredentialShareInput {
|
|
24
|
+
credentialId: string;
|
|
25
|
+
createdBy: string;
|
|
26
|
+
expiresAfter: ShareExpiresAfter;
|
|
27
|
+
accessMode: ShareAccessMode;
|
|
28
|
+
password?: string;
|
|
29
|
+
oneTimeOnly: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type ShareAccessFailureReason =
|
|
33
|
+
| 'not_found'
|
|
34
|
+
| 'expired'
|
|
35
|
+
| 'already_viewed'
|
|
36
|
+
| 'password_required'
|
|
37
|
+
| 'invalid_password';
|
|
38
|
+
|
|
39
|
+
type ShareConsumeResult =
|
|
40
|
+
| { ok: true; share: CredentialShareFile }
|
|
41
|
+
| { ok: false; reason: ShareAccessFailureReason };
|
|
42
|
+
|
|
43
|
+
const SHARE_TOKEN_PATTERN = /^[a-f0-9]{32}$/i;
|
|
44
|
+
const EXPIRY_MS: Record<ShareExpiresAfter, number> = {
|
|
45
|
+
'15m': 15 * 60 * 1000,
|
|
46
|
+
'1h': 60 * 60 * 1000,
|
|
47
|
+
'24h': 24 * 60 * 60 * 1000,
|
|
48
|
+
'7d': 7 * 24 * 60 * 60 * 1000,
|
|
49
|
+
'30d': 30 * 24 * 60 * 60 * 1000,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function ensureShareDir(): void {
|
|
53
|
+
const dir = DATA_PATHS.credentialShares;
|
|
54
|
+
if (!fs.existsSync(dir)) {
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isValidToken(token: string): boolean {
|
|
60
|
+
return SHARE_TOKEN_PATTERN.test(token);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getSharePath(token: string): string {
|
|
64
|
+
return path.join(DATA_PATHS.credentialShares, `${token}.json`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readShare(token: string): CredentialShareFile | null {
|
|
68
|
+
if (!isValidToken(token)) return null;
|
|
69
|
+
const filePath = getSharePath(token);
|
|
70
|
+
if (!fs.existsSync(filePath)) return null;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8')) as CredentialShareFile;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function writeShare(share: CredentialShareFile): void {
|
|
79
|
+
ensureShareDir();
|
|
80
|
+
fs.writeFileSync(getSharePath(share.token), JSON.stringify(share, null, 2));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveExpiresAt(expiresAfter: ShareExpiresAfter): number {
|
|
84
|
+
return Date.now() + EXPIRY_MS[expiresAfter];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hashSharePassword(password: string, salt: string): string {
|
|
88
|
+
return createHash('sha256').update(`${salt}:${password}`, 'utf8').digest('hex');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function verifySharePassword(password: string, share: CredentialShareFile): boolean {
|
|
92
|
+
if (!share.passwordSalt || !share.passwordHash) return false;
|
|
93
|
+
const expected = Buffer.from(share.passwordHash, 'hex');
|
|
94
|
+
const actual = Buffer.from(hashSharePassword(password, share.passwordSalt), 'hex');
|
|
95
|
+
if (expected.length !== actual.length) return false;
|
|
96
|
+
return timingSafeEqual(expected, actual);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isExpired(share: CredentialShareFile): boolean {
|
|
100
|
+
return Date.now() > share.expiresAt;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isAlreadyViewed(share: CredentialShareFile): boolean {
|
|
104
|
+
return share.oneTimeOnly && share.viewCount >= 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function generateShareToken(): string {
|
|
108
|
+
ensureShareDir();
|
|
109
|
+
while (true) {
|
|
110
|
+
const token = randomBytes(16).toString('hex');
|
|
111
|
+
if (!fs.existsSync(getSharePath(token))) return token;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createCredentialShare(input: CreateCredentialShareInput): CredentialShareFile {
|
|
116
|
+
if (!(input.expiresAfter in EXPIRY_MS)) {
|
|
117
|
+
throw new Error('Invalid expiresAfter value');
|
|
118
|
+
}
|
|
119
|
+
if (input.accessMode !== 'anyone' && input.accessMode !== 'password') {
|
|
120
|
+
throw new Error('Invalid accessMode value');
|
|
121
|
+
}
|
|
122
|
+
if (input.accessMode === 'password' && (!input.password || input.password.length === 0)) {
|
|
123
|
+
throw new Error('Password is required when accessMode is password');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const token = generateShareToken();
|
|
127
|
+
const createdAt = new Date().toISOString();
|
|
128
|
+
|
|
129
|
+
let passwordSalt: string | undefined;
|
|
130
|
+
let passwordHash: string | undefined;
|
|
131
|
+
if (input.accessMode === 'password' && input.password) {
|
|
132
|
+
passwordSalt = randomBytes(16).toString('hex');
|
|
133
|
+
passwordHash = hashSharePassword(input.password, passwordSalt);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const share: CredentialShareFile = {
|
|
137
|
+
token,
|
|
138
|
+
credentialId: input.credentialId,
|
|
139
|
+
createdAt,
|
|
140
|
+
createdBy: input.createdBy,
|
|
141
|
+
expiresAt: resolveExpiresAt(input.expiresAfter),
|
|
142
|
+
accessMode: input.accessMode,
|
|
143
|
+
...(passwordSalt ? { passwordSalt } : {}),
|
|
144
|
+
...(passwordHash ? { passwordHash } : {}),
|
|
145
|
+
oneTimeOnly: input.oneTimeOnly,
|
|
146
|
+
viewCount: 0,
|
|
147
|
+
lastViewedAt: null,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
writeShare(share);
|
|
151
|
+
return share;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function getCredentialShare(token: string): CredentialShareFile | null {
|
|
155
|
+
return readShare(token);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getCredentialShareStatus(share: CredentialShareFile): {
|
|
159
|
+
isExpired: boolean;
|
|
160
|
+
isAlreadyViewed: boolean;
|
|
161
|
+
} {
|
|
162
|
+
return {
|
|
163
|
+
isExpired: isExpired(share),
|
|
164
|
+
isAlreadyViewed: isAlreadyViewed(share),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function consumeCredentialShare(token: string, password?: string): ShareConsumeResult {
|
|
169
|
+
const share = readShare(token);
|
|
170
|
+
if (!share) return { ok: false, reason: 'not_found' };
|
|
171
|
+
if (isExpired(share)) return { ok: false, reason: 'expired' };
|
|
172
|
+
if (isAlreadyViewed(share)) return { ok: false, reason: 'already_viewed' };
|
|
173
|
+
|
|
174
|
+
if (share.accessMode === 'password') {
|
|
175
|
+
if (!password || password.length === 0) {
|
|
176
|
+
return { ok: false, reason: 'password_required' };
|
|
177
|
+
}
|
|
178
|
+
if (!verifySharePassword(password, share)) {
|
|
179
|
+
return { ok: false, reason: 'invalid_password' };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const updated: CredentialShareFile = {
|
|
184
|
+
...share,
|
|
185
|
+
viewCount: share.viewCount + 1,
|
|
186
|
+
lastViewedAt: new Date().toISOString(),
|
|
187
|
+
};
|
|
188
|
+
writeShare(updated);
|
|
189
|
+
return { ok: true, share: updated };
|
|
190
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import {
|
|
2
|
+
constants,
|
|
3
|
+
createCipheriv,
|
|
4
|
+
createDecipheriv,
|
|
5
|
+
createPublicKey,
|
|
6
|
+
generateKeyPairSync,
|
|
7
|
+
KeyObject,
|
|
8
|
+
privateDecrypt,
|
|
9
|
+
publicEncrypt,
|
|
10
|
+
randomBytes,
|
|
11
|
+
} from 'crypto';
|
|
12
|
+
import * as net from 'net';
|
|
13
|
+
|
|
14
|
+
interface HybridEnvelope {
|
|
15
|
+
v: 1;
|
|
16
|
+
alg: 'RSA-OAEP/AES-256-GCM';
|
|
17
|
+
key: string;
|
|
18
|
+
iv: string;
|
|
19
|
+
tag: string;
|
|
20
|
+
data: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const OAEP_HASH = 'sha256';
|
|
24
|
+
|
|
25
|
+
function parseAgentPubkey(pubkey: string): KeyObject {
|
|
26
|
+
const value = pubkey.trim();
|
|
27
|
+
if (!value) {
|
|
28
|
+
throw new Error('Public key is required');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// PEM directly
|
|
32
|
+
if (value.includes('BEGIN PUBLIC KEY')) {
|
|
33
|
+
return createPublicKey(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Base64(PEM) or DER/SPKI
|
|
37
|
+
const decoded = Buffer.from(value, 'base64');
|
|
38
|
+
if (decoded.length === 0) {
|
|
39
|
+
throw new Error('Invalid public key encoding');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const decodedText = decoded.toString('utf8');
|
|
43
|
+
if (decodedText.includes('BEGIN PUBLIC KEY')) {
|
|
44
|
+
return createPublicKey(decodedText);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return createPublicKey({
|
|
48
|
+
key: decoded,
|
|
49
|
+
format: 'der',
|
|
50
|
+
type: 'spki',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function rsaEncrypt(data: Buffer, key: KeyObject): Buffer {
|
|
55
|
+
return publicEncrypt(
|
|
56
|
+
{
|
|
57
|
+
key,
|
|
58
|
+
padding: constants.RSA_PKCS1_OAEP_PADDING,
|
|
59
|
+
oaepHash: OAEP_HASH,
|
|
60
|
+
},
|
|
61
|
+
data,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function isValidAgentPubkey(pubkey: string): boolean {
|
|
66
|
+
try {
|
|
67
|
+
const key = parseAgentPubkey(pubkey);
|
|
68
|
+
return key.asymmetricKeyType === 'rsa';
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function normalizeAgentPubkey(pubkey: string): string {
|
|
75
|
+
const key = parseAgentPubkey(pubkey);
|
|
76
|
+
return key.export({ type: 'spki', format: 'pem' }).toString();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Encrypt credential data to an agent's RSA-OAEP public key.
|
|
81
|
+
*
|
|
82
|
+
* Always uses hybrid RSA-OAEP + AES-256-GCM envelope so that clients
|
|
83
|
+
* only need a single decryption code path.
|
|
84
|
+
*/
|
|
85
|
+
export function encryptToAgentPubkey(data: string, pubkeyBase64: string): string {
|
|
86
|
+
const key = parseAgentPubkey(pubkeyBase64);
|
|
87
|
+
if (key.asymmetricKeyType !== 'rsa') {
|
|
88
|
+
throw new Error('Public key must be RSA');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const payload = Buffer.from(data, 'utf8');
|
|
92
|
+
|
|
93
|
+
// Always hybrid RSA + AES-GCM
|
|
94
|
+
const sessionKey = randomBytes(32); // AES-256
|
|
95
|
+
const iv = randomBytes(12); // GCM nonce
|
|
96
|
+
const cipher = createCipheriv('aes-256-gcm', sessionKey, iv);
|
|
97
|
+
const encryptedData = Buffer.concat([cipher.update(payload), cipher.final()]);
|
|
98
|
+
const tag = cipher.getAuthTag();
|
|
99
|
+
const wrappedKey = rsaEncrypt(sessionKey, key);
|
|
100
|
+
|
|
101
|
+
const envelope: HybridEnvelope = {
|
|
102
|
+
v: 1,
|
|
103
|
+
alg: 'RSA-OAEP/AES-256-GCM',
|
|
104
|
+
key: wrappedKey.toString('base64'),
|
|
105
|
+
iv: iv.toString('base64'),
|
|
106
|
+
tag: tag.toString('base64'),
|
|
107
|
+
data: encryptedData.toString('base64'),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Client-side decryption ──
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Decrypt a hybrid RSA-OAEP/AES-256-GCM envelope (or raw RSA ciphertext).
|
|
117
|
+
* Used by CLI tools to decrypt credentials returned from the server.
|
|
118
|
+
*/
|
|
119
|
+
export function decryptWithPrivateKey(encryptedBase64: string, privateKeyPem: string): string {
|
|
120
|
+
const decoded = Buffer.from(encryptedBase64, 'base64');
|
|
121
|
+
let envelope: HybridEnvelope;
|
|
122
|
+
try {
|
|
123
|
+
envelope = JSON.parse(decoded.toString('utf8')) as HybridEnvelope;
|
|
124
|
+
} catch {
|
|
125
|
+
return privateDecrypt(
|
|
126
|
+
{ key: privateKeyPem, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: OAEP_HASH },
|
|
127
|
+
decoded,
|
|
128
|
+
).toString('utf8');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (envelope.v !== 1 || envelope.alg !== 'RSA-OAEP/AES-256-GCM') {
|
|
132
|
+
throw new Error(`Unexpected envelope: v=${envelope.v} alg=${envelope.alg}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sessionKey = privateDecrypt(
|
|
136
|
+
{ key: privateKeyPem, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: OAEP_HASH },
|
|
137
|
+
Buffer.from(envelope.key, 'base64'),
|
|
138
|
+
);
|
|
139
|
+
const decipher = createDecipheriv('aes-256-gcm', sessionKey, Buffer.from(envelope.iv, 'base64'));
|
|
140
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, 'base64'));
|
|
141
|
+
return Buffer.concat([
|
|
142
|
+
decipher.update(Buffer.from(envelope.data, 'base64')),
|
|
143
|
+
decipher.final(),
|
|
144
|
+
]).toString('utf8');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Ephemeral keypair generation ──
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
export interface ProfileIssuanceOverrides {
|
|
152
|
+
ttlSeconds?: number;
|
|
153
|
+
maxReads?: number;
|
|
154
|
+
readScopes?: string[];
|
|
155
|
+
writeScopes?: string[];
|
|
156
|
+
excludeFields?: string[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ProfileIssuanceSelection {
|
|
160
|
+
profile?: string;
|
|
161
|
+
profileVersion?: string;
|
|
162
|
+
profileOverrides?: ProfileIssuanceOverrides;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function buildScopedReadTokenIssueRequest(input: {
|
|
166
|
+
agentId?: string;
|
|
167
|
+
pubkey: string;
|
|
168
|
+
} & ProfileIssuanceSelection): Record<string, unknown> {
|
|
169
|
+
if (input.profile && input.profile.trim()) {
|
|
170
|
+
return {
|
|
171
|
+
agentId: input.agentId || 'cli-reader',
|
|
172
|
+
profile: input.profile,
|
|
173
|
+
...(input.profileVersion ? { profileVersion: input.profileVersion } : {}),
|
|
174
|
+
...(input.profileOverrides ? { profileOverrides: input.profileOverrides } : {}),
|
|
175
|
+
pubkey: input.pubkey,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
agentId: input.agentId || 'cli-reader',
|
|
181
|
+
permissions: ['secret:read'],
|
|
182
|
+
credentialAccess: { read: ['vault:*'], excludeFields: [] },
|
|
183
|
+
pubkey: input.pubkey,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function buildScopedWriteTokenIssueRequest(input: {
|
|
188
|
+
agentId?: string;
|
|
189
|
+
pubkey: string;
|
|
190
|
+
vaultId: string;
|
|
191
|
+
} & ProfileIssuanceSelection): Record<string, unknown> {
|
|
192
|
+
if (input.profile && input.profile.trim()) {
|
|
193
|
+
return {
|
|
194
|
+
agentId: input.agentId || 'cli-writer',
|
|
195
|
+
profile: input.profile,
|
|
196
|
+
...(input.profileVersion ? { profileVersion: input.profileVersion } : {}),
|
|
197
|
+
...(input.profileOverrides ? { profileOverrides: input.profileOverrides } : {}),
|
|
198
|
+
pubkey: input.pubkey,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
agentId: input.agentId || 'cli-writer',
|
|
204
|
+
permissions: ['secret:write'],
|
|
205
|
+
credentialAccess: { write: [`vault:${input.vaultId}`] },
|
|
206
|
+
pubkey: input.pubkey,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export interface EphemeralKeypair {
|
|
210
|
+
publicKeyPem: string;
|
|
211
|
+
privateKeyPem: string;
|
|
212
|
+
publicKeyBase64: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function generateEphemeralKeypair(): EphemeralKeypair {
|
|
216
|
+
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
|
217
|
+
modulusLength: 2048,
|
|
218
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
219
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
publicKeyPem: publicKey,
|
|
223
|
+
privateKeyPem: privateKey,
|
|
224
|
+
publicKeyBase64: Buffer.from(publicKey, 'utf8').toString('base64'),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Socket bootstrap (CLI auth via Unix socket) ──
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Authenticate with the AuraWallet server via Unix socket.
|
|
232
|
+
* Returns a bearer token for API access.
|
|
233
|
+
*/
|
|
234
|
+
export function bootstrapViaSocket(
|
|
235
|
+
agentId: string,
|
|
236
|
+
keypair: EphemeralKeypair,
|
|
237
|
+
): Promise<string> {
|
|
238
|
+
return new Promise((resolve, reject) => {
|
|
239
|
+
const uid = process.getuid?.() ?? 'unknown';
|
|
240
|
+
const socketPath = `/tmp/aura-cli-${uid}.sock`;
|
|
241
|
+
|
|
242
|
+
const socket = net.createConnection(socketPath);
|
|
243
|
+
let buffer = '';
|
|
244
|
+
let resolved = false;
|
|
245
|
+
|
|
246
|
+
const timeout = setTimeout(() => {
|
|
247
|
+
if (!resolved) {
|
|
248
|
+
resolved = true;
|
|
249
|
+
socket.destroy();
|
|
250
|
+
reject(new Error('Socket auth timed out. Is AuraWallet running? (npx aurawallet start)'));
|
|
251
|
+
}
|
|
252
|
+
}, 5000);
|
|
253
|
+
|
|
254
|
+
socket.on('error', (err) => {
|
|
255
|
+
if (!resolved) {
|
|
256
|
+
resolved = true;
|
|
257
|
+
clearTimeout(timeout);
|
|
258
|
+
reject(new Error(`Cannot connect to AuraWallet: ${err.message}\nRun 'npx aurawallet start' first.`));
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
socket.on('connect', () => {
|
|
263
|
+
socket.write(JSON.stringify({
|
|
264
|
+
type: 'auth',
|
|
265
|
+
agentId,
|
|
266
|
+
autoApprove: true,
|
|
267
|
+
pubkey: keypair.publicKeyPem,
|
|
268
|
+
}) + '\n');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
socket.on('data', (data) => {
|
|
272
|
+
buffer += data.toString();
|
|
273
|
+
let idx;
|
|
274
|
+
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
275
|
+
const line = buffer.substring(0, idx);
|
|
276
|
+
buffer = buffer.substring(idx + 1);
|
|
277
|
+
if (!line.trim()) continue;
|
|
278
|
+
try {
|
|
279
|
+
const msg = JSON.parse(line.trim()) as {
|
|
280
|
+
type: string;
|
|
281
|
+
encryptedToken?: string;
|
|
282
|
+
message?: string;
|
|
283
|
+
};
|
|
284
|
+
if (msg.type === 'auth_approved' && msg.encryptedToken) {
|
|
285
|
+
if (!resolved) {
|
|
286
|
+
resolved = true;
|
|
287
|
+
clearTimeout(timeout);
|
|
288
|
+
socket.destroy();
|
|
289
|
+
resolve(decryptWithPrivateKey(msg.encryptedToken, keypair.privateKeyPem));
|
|
290
|
+
}
|
|
291
|
+
} else if (msg.type === 'error') {
|
|
292
|
+
if (!resolved) {
|
|
293
|
+
resolved = true;
|
|
294
|
+
clearTimeout(timeout);
|
|
295
|
+
socket.destroy();
|
|
296
|
+
reject(new Error(`Auth error: ${msg.message}`));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch { /* ignore parse errors */ }
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Create a scoped credential-read token via the server API.
|
|
307
|
+
*/
|
|
308
|
+
export async function createReadToken(
|
|
309
|
+
baseUrl: string,
|
|
310
|
+
token: string,
|
|
311
|
+
keypair: EphemeralKeypair,
|
|
312
|
+
agentId: string = 'cli-reader',
|
|
313
|
+
profile?: ProfileIssuanceSelection,
|
|
314
|
+
): Promise<string> {
|
|
315
|
+
const res = await fetch(`${baseUrl}/actions/token`, {
|
|
316
|
+
method: 'POST',
|
|
317
|
+
headers: {
|
|
318
|
+
'Content-Type': 'application/json',
|
|
319
|
+
'Authorization': `Bearer ${token}`,
|
|
320
|
+
},
|
|
321
|
+
body: JSON.stringify(buildScopedReadTokenIssueRequest({
|
|
322
|
+
agentId,
|
|
323
|
+
pubkey: keypair.publicKeyBase64,
|
|
324
|
+
profile: profile?.profile,
|
|
325
|
+
profileVersion: profile?.profileVersion,
|
|
326
|
+
profileOverrides: profile?.profileOverrides,
|
|
327
|
+
})),
|
|
328
|
+
signal: AbortSignal.timeout(5000),
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (!res.ok) {
|
|
332
|
+
const text = await res.text();
|
|
333
|
+
throw new Error(`Failed to create read token (${res.status}): ${text}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const data = await res.json() as { encryptedToken?: string };
|
|
337
|
+
if (!data.encryptedToken) {
|
|
338
|
+
throw new Error('No encryptedToken in response');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return decryptWithPrivateKey(data.encryptedToken, keypair.privateKeyPem);
|
|
342
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Vault — Subkey Derivation & Session Management
|
|
3
|
+
* =========================================================
|
|
4
|
+
*
|
|
5
|
+
* Derives a credential-specific encryption key from the wallet mnemonic.
|
|
6
|
+
* The derived key is held in memory while the vault is unlocked and used
|
|
7
|
+
* to encrypt/decrypt credential files. Hooks into the vault lifecycle
|
|
8
|
+
* (unlock/lock) in cold.ts.
|
|
9
|
+
*
|
|
10
|
+
* Key derivation: SHA256("credential-v1:" + vaultId + ":" + mnemonic)
|
|
11
|
+
* This produces a deterministic 256-bit key unique to each vault.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import CryptoJS from 'crypto-js';
|
|
15
|
+
import { getVaultMnemonic } from './cold';
|
|
16
|
+
|
|
17
|
+
// In-memory storage of derived credential keys (vaultId → hex key)
|
|
18
|
+
const credentialVaultSessions = new Map<string, string>();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Derive a credential encryption key from a vault's mnemonic.
|
|
22
|
+
* Uses SHA-256 of a domain-separated string for deterministic derivation.
|
|
23
|
+
*/
|
|
24
|
+
export function deriveCredentialKey(vaultId: string, mnemonic: string): string {
|
|
25
|
+
const input = `credential-v1:${vaultId}:${mnemonic}`;
|
|
26
|
+
return CryptoJS.SHA256(input).toString(CryptoJS.enc.Hex);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Unlock the credential vault for a given vault ID.
|
|
31
|
+
* Reads the mnemonic from the already-unlocked wallet vault session
|
|
32
|
+
* and derives + stores the credential subkey.
|
|
33
|
+
*/
|
|
34
|
+
export function unlockCredentialVault(vaultId: string): boolean {
|
|
35
|
+
const mnemonic = getVaultMnemonic(vaultId);
|
|
36
|
+
if (!mnemonic) return false;
|
|
37
|
+
|
|
38
|
+
const key = deriveCredentialKey(vaultId, mnemonic);
|
|
39
|
+
credentialVaultSessions.set(vaultId, key);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lock the credential vault for a given vault ID.
|
|
45
|
+
* Removes the derived key from memory.
|
|
46
|
+
*/
|
|
47
|
+
export function lockCredentialVault(vaultId: string): void {
|
|
48
|
+
credentialVaultSessions.delete(vaultId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Lock all credential vaults.
|
|
53
|
+
*/
|
|
54
|
+
export function lockAllCredentialVaults(): void {
|
|
55
|
+
credentialVaultSessions.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the derived credential key for a vault (or null if locked).
|
|
60
|
+
*/
|
|
61
|
+
export function getCredentialVaultKey(vaultId: string): string | null {
|
|
62
|
+
return credentialVaultSessions.get(vaultId) ?? null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if a credential vault is unlocked.
|
|
67
|
+
*/
|
|
68
|
+
export function isCredentialVaultUnlocked(vaultId: string): boolean {
|
|
69
|
+
return credentialVaultSessions.has(vaultId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reset all credential vault state (for testing only).
|
|
74
|
+
*/
|
|
75
|
+
export function _resetCredentialVaultForTesting(): void {
|
|
76
|
+
credentialVaultSessions.clear();
|
|
77
|
+
}
|