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,608 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Import — Parse external password manager exports
|
|
3
|
+
* ============================================================
|
|
4
|
+
*
|
|
5
|
+
* Normalizes CSV exports from 1Password, Bitwarden, LastPass, Chrome,
|
|
6
|
+
* and generic CSV into ImportedCredential[], ready for vault import.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { parse } from 'csv-parse/sync';
|
|
10
|
+
import { CredentialType, CredentialField } from '../types';
|
|
11
|
+
import { listCredentials } from './credentials';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Types
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export interface ImportedField {
|
|
18
|
+
key: string;
|
|
19
|
+
value: string;
|
|
20
|
+
sensitive: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ImportedCredential {
|
|
24
|
+
name: string;
|
|
25
|
+
type: CredentialType;
|
|
26
|
+
url?: string;
|
|
27
|
+
fields: ImportedField[];
|
|
28
|
+
tags?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ColumnMapping {
|
|
32
|
+
title?: string;
|
|
33
|
+
url?: string;
|
|
34
|
+
username?: string;
|
|
35
|
+
password?: string;
|
|
36
|
+
notes?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SplitResult {
|
|
40
|
+
meta: Record<string, unknown>;
|
|
41
|
+
sensitiveFields: CredentialField[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ImportFormat =
|
|
45
|
+
| '1password-csv'
|
|
46
|
+
| '1password-json'
|
|
47
|
+
| '1password-1pux'
|
|
48
|
+
| 'bitwarden-csv'
|
|
49
|
+
| 'bitwarden-json'
|
|
50
|
+
| 'lastpass-csv'
|
|
51
|
+
| 'icloud-csv'
|
|
52
|
+
| 'chrome-csv'
|
|
53
|
+
| 'chrome-json'
|
|
54
|
+
| 'firefox-csv'
|
|
55
|
+
| 'firefox-json'
|
|
56
|
+
| 'generic-csv';
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Helpers
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
/** Strip UTF-8 BOM if present */
|
|
63
|
+
function stripBOM(text: string): string {
|
|
64
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseCsvRows(csv: string): Record<string, string>[] {
|
|
68
|
+
const cleaned = stripBOM(csv);
|
|
69
|
+
return parse(cleaned, {
|
|
70
|
+
columns: true,
|
|
71
|
+
skip_empty_lines: true,
|
|
72
|
+
trim: true,
|
|
73
|
+
relax_column_count: true,
|
|
74
|
+
}) as Record<string, string>[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeColumnName(value: string): string {
|
|
78
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getRowValue(row: Record<string, string>, aliases: string[]): string {
|
|
82
|
+
const lookup = new Map<string, string>();
|
|
83
|
+
for (const [key, value] of Object.entries(row)) {
|
|
84
|
+
lookup.set(normalizeColumnName(key), value);
|
|
85
|
+
}
|
|
86
|
+
for (const alias of aliases) {
|
|
87
|
+
const found = lookup.get(normalizeColumnName(alias));
|
|
88
|
+
if (found !== undefined && String(found).trim() !== '') {
|
|
89
|
+
return String(found).trim();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Normalize URL for duplicate comparison: strip protocol, www., trailing slash */
|
|
96
|
+
export function normalizeUrl(url: string): string {
|
|
97
|
+
if (!url) return '';
|
|
98
|
+
let u = url.trim().toLowerCase();
|
|
99
|
+
// strip protocol
|
|
100
|
+
u = u.replace(/^https?:\/\//, '');
|
|
101
|
+
// strip www.
|
|
102
|
+
u = u.replace(/^www\./, '');
|
|
103
|
+
// strip trailing slash
|
|
104
|
+
u = u.replace(/\/+$/, '');
|
|
105
|
+
return u;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Split ImportedField[] into meta (non-sensitive) + sensitiveFields (sensitive).
|
|
110
|
+
* The CredentialField objects get a default type based on key.
|
|
111
|
+
*/
|
|
112
|
+
export function splitFields(
|
|
113
|
+
fields: ImportedField[],
|
|
114
|
+
url?: string,
|
|
115
|
+
tags?: string[]
|
|
116
|
+
): SplitResult {
|
|
117
|
+
const meta: Record<string, unknown> = {};
|
|
118
|
+
const sensitiveFields: CredentialField[] = [];
|
|
119
|
+
|
|
120
|
+
if (url) meta.url = url;
|
|
121
|
+
if (tags && tags.length > 0) meta.tags = tags;
|
|
122
|
+
|
|
123
|
+
for (const f of fields) {
|
|
124
|
+
if (!f.value && f.value !== '') continue; // skip undefined/null
|
|
125
|
+
if (f.sensitive) {
|
|
126
|
+
sensitiveFields.push({
|
|
127
|
+
key: f.key,
|
|
128
|
+
value: f.value,
|
|
129
|
+
type: 'secret',
|
|
130
|
+
sensitive: true,
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
// Store non-sensitive fields in meta for searchability
|
|
134
|
+
meta[f.key] = f.value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { meta, sensitiveFields };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Duplicate detection
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
export interface DuplicateMatch {
|
|
146
|
+
index: number;
|
|
147
|
+
existingId: string;
|
|
148
|
+
existingName: string;
|
|
149
|
+
matchType: 'exact' | 'name-only';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check imported credentials against existing vault credentials for duplicates.
|
|
154
|
+
* Returns a map of import index → duplicate match info.
|
|
155
|
+
*/
|
|
156
|
+
export function detectDuplicates(
|
|
157
|
+
imported: ImportedCredential[],
|
|
158
|
+
vaultId: string
|
|
159
|
+
): Map<number, DuplicateMatch> {
|
|
160
|
+
const existing = listCredentials({ vaultId });
|
|
161
|
+
const duplicates = new Map<number, DuplicateMatch>();
|
|
162
|
+
|
|
163
|
+
// Build lookup maps from existing credentials
|
|
164
|
+
const existingByNameAndUrl = new Map<string, { id: string; name: string }>();
|
|
165
|
+
const existingByName = new Map<string, { id: string; name: string }>();
|
|
166
|
+
|
|
167
|
+
for (const cred of existing) {
|
|
168
|
+
const name = cred.name.trim().toLowerCase();
|
|
169
|
+
const url = normalizeUrl((cred.meta?.url as string) || '');
|
|
170
|
+
const key = `${name}|${url}`;
|
|
171
|
+
existingByNameAndUrl.set(key, { id: cred.id, name: cred.name });
|
|
172
|
+
existingByName.set(name, { id: cred.id, name: cred.name });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < imported.length; i++) {
|
|
176
|
+
const cred = imported[i];
|
|
177
|
+
const name = cred.name.trim().toLowerCase();
|
|
178
|
+
const url = normalizeUrl(cred.url || '');
|
|
179
|
+
|
|
180
|
+
// Check exact match (name + URL)
|
|
181
|
+
const exactKey = `${name}|${url}`;
|
|
182
|
+
const exactMatch = existingByNameAndUrl.get(exactKey);
|
|
183
|
+
if (exactMatch && url) {
|
|
184
|
+
duplicates.set(i, {
|
|
185
|
+
index: i,
|
|
186
|
+
existingId: exactMatch.id,
|
|
187
|
+
existingName: exactMatch.name,
|
|
188
|
+
matchType: 'exact',
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check name-only match
|
|
194
|
+
const nameMatch = existingByName.get(name);
|
|
195
|
+
if (nameMatch) {
|
|
196
|
+
duplicates.set(i, {
|
|
197
|
+
index: i,
|
|
198
|
+
existingId: nameMatch.id,
|
|
199
|
+
existingName: nameMatch.name,
|
|
200
|
+
matchType: 'name-only',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return duplicates;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
function parseJsonObject(input: string): any {
|
|
211
|
+
const cleaned = stripBOM(input).trim();
|
|
212
|
+
if (!cleaned) return [];
|
|
213
|
+
|
|
214
|
+
let parsed: any;
|
|
215
|
+
try {
|
|
216
|
+
parsed = JSON.parse(cleaned);
|
|
217
|
+
} catch {
|
|
218
|
+
throw new Error('Invalid JSON payload');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (Array.isArray(parsed)) return parsed;
|
|
222
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
223
|
+
throw new Error('Invalid JSON payload');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function coerceItems(root: any, keys: string[]): any[] {
|
|
227
|
+
if (Array.isArray(root)) return root;
|
|
228
|
+
for (const key of keys) {
|
|
229
|
+
if (Array.isArray(root?.[key])) return root[key];
|
|
230
|
+
}
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// 1Password CSV Parser
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Parse 1Password CSV export.
|
|
240
|
+
* Expected columns: Title, URL, Username, Password, Notes, Type, OTP
|
|
241
|
+
*/
|
|
242
|
+
export function parse1PasswordCSV(csv: string): ImportedCredential[] {
|
|
243
|
+
const cleaned = stripBOM(csv);
|
|
244
|
+
const records = parse(cleaned, {
|
|
245
|
+
columns: true,
|
|
246
|
+
skip_empty_lines: true,
|
|
247
|
+
trim: true,
|
|
248
|
+
relax_column_count: true,
|
|
249
|
+
}) as Record<string, string>[];
|
|
250
|
+
|
|
251
|
+
return records.map((row) => {
|
|
252
|
+
const fields: ImportedField[] = [];
|
|
253
|
+
|
|
254
|
+
if (row.Username) {
|
|
255
|
+
fields.push({ key: 'username', value: row.Username, sensitive: false });
|
|
256
|
+
}
|
|
257
|
+
if (row.Password) {
|
|
258
|
+
fields.push({ key: 'password', value: row.Password, sensitive: true });
|
|
259
|
+
}
|
|
260
|
+
if (row.Notes) {
|
|
261
|
+
fields.push({ key: 'notes', value: row.Notes, sensitive: false });
|
|
262
|
+
}
|
|
263
|
+
if (row.OTP) {
|
|
264
|
+
fields.push({ key: 'totp', value: row.OTP, sensitive: true });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Map 1Password types to AuraMaxx types
|
|
268
|
+
let type: CredentialType = 'login';
|
|
269
|
+
const rawType = (row.Type || '').toLowerCase();
|
|
270
|
+
if (rawType.includes('card') || rawType.includes('credit')) type = 'card';
|
|
271
|
+
else if (rawType.includes('note')) type = 'note';
|
|
272
|
+
else if (rawType.includes('api')) type = 'api';
|
|
273
|
+
else if (rawType.includes('identity')) type = 'custom';
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
name: row.Title || 'Untitled',
|
|
277
|
+
type,
|
|
278
|
+
url: row.URL || undefined,
|
|
279
|
+
fields,
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Bitwarden CSV Parser
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Parse Bitwarden CSV export.
|
|
290
|
+
* Expected columns: folder, favorite, type, name, notes, fields, reprompt,
|
|
291
|
+
* login_uri, login_username, login_password, login_totp
|
|
292
|
+
*/
|
|
293
|
+
export function parseBitwardenCSV(csv: string): ImportedCredential[] {
|
|
294
|
+
const cleaned = stripBOM(csv);
|
|
295
|
+
const records = parse(cleaned, {
|
|
296
|
+
columns: true,
|
|
297
|
+
skip_empty_lines: true,
|
|
298
|
+
trim: true,
|
|
299
|
+
relax_column_count: true,
|
|
300
|
+
}) as Record<string, string>[];
|
|
301
|
+
|
|
302
|
+
return records.map((row) => {
|
|
303
|
+
const fields: ImportedField[] = [];
|
|
304
|
+
|
|
305
|
+
if (row.login_username) {
|
|
306
|
+
fields.push({ key: 'username', value: row.login_username, sensitive: false });
|
|
307
|
+
}
|
|
308
|
+
if (row.login_password) {
|
|
309
|
+
fields.push({ key: 'password', value: row.login_password, sensitive: true });
|
|
310
|
+
}
|
|
311
|
+
if (row.notes) {
|
|
312
|
+
fields.push({ key: 'notes', value: row.notes, sensitive: false });
|
|
313
|
+
}
|
|
314
|
+
if (row.login_totp) {
|
|
315
|
+
fields.push({ key: 'totp', value: row.login_totp, sensitive: true });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let type: CredentialType = 'login';
|
|
319
|
+
const rawType = (row.type || '').toLowerCase();
|
|
320
|
+
if (rawType === 'card') type = 'card';
|
|
321
|
+
else if (rawType === 'securenote' || rawType === 'note') type = 'note';
|
|
322
|
+
else if (rawType === 'identity') type = 'custom';
|
|
323
|
+
|
|
324
|
+
const tags: string[] = [];
|
|
325
|
+
if (row.folder) tags.push(row.folder);
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
name: row.name || 'Untitled',
|
|
329
|
+
type,
|
|
330
|
+
url: row.login_uri || undefined,
|
|
331
|
+
fields,
|
|
332
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// Chrome CSV Parser
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Parse Chrome password CSV export.
|
|
343
|
+
* Expected columns: name, url, username, password, note
|
|
344
|
+
*/
|
|
345
|
+
export function parseChromeCSV(csv: string): ImportedCredential[] {
|
|
346
|
+
const cleaned = stripBOM(csv);
|
|
347
|
+
const records = parse(cleaned, {
|
|
348
|
+
columns: true,
|
|
349
|
+
skip_empty_lines: true,
|
|
350
|
+
trim: true,
|
|
351
|
+
relax_column_count: true,
|
|
352
|
+
}) as Record<string, string>[];
|
|
353
|
+
|
|
354
|
+
return records.map((row) => {
|
|
355
|
+
const fields: ImportedField[] = [];
|
|
356
|
+
|
|
357
|
+
if (row.username) {
|
|
358
|
+
fields.push({ key: 'username', value: row.username, sensitive: false });
|
|
359
|
+
}
|
|
360
|
+
if (row.password) {
|
|
361
|
+
fields.push({ key: 'password', value: row.password, sensitive: true });
|
|
362
|
+
}
|
|
363
|
+
if (row.note) {
|
|
364
|
+
fields.push({ key: 'notes', value: row.note, sensitive: false });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
name: row.name || 'Untitled',
|
|
369
|
+
type: 'login' as CredentialType,
|
|
370
|
+
url: row.url || undefined,
|
|
371
|
+
fields,
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
// Firefox CSV Parser
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Parse Firefox password CSV export.
|
|
382
|
+
* Expected columns: url, username, password, httpRealm, formActionOrigin,
|
|
383
|
+
* guid, timeCreated, timeLastUsed, timePasswordChanged
|
|
384
|
+
*/
|
|
385
|
+
export function parseFirefoxCSV(csv: string): ImportedCredential[] {
|
|
386
|
+
const cleaned = stripBOM(csv);
|
|
387
|
+
const records = parse(cleaned, {
|
|
388
|
+
columns: true,
|
|
389
|
+
skip_empty_lines: true,
|
|
390
|
+
trim: true,
|
|
391
|
+
relax_column_count: true,
|
|
392
|
+
}) as Record<string, string>[];
|
|
393
|
+
|
|
394
|
+
return records.map((row) => {
|
|
395
|
+
const fields: ImportedField[] = [];
|
|
396
|
+
|
|
397
|
+
if (row.username) {
|
|
398
|
+
fields.push({ key: 'username', value: row.username, sensitive: false });
|
|
399
|
+
}
|
|
400
|
+
if (row.password) {
|
|
401
|
+
fields.push({ key: 'password', value: row.password, sensitive: true });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Derive a name from the URL
|
|
405
|
+
let name = 'Untitled';
|
|
406
|
+
if (row.url) {
|
|
407
|
+
try {
|
|
408
|
+
name = new URL(row.url).hostname.replace(/^www\./, '');
|
|
409
|
+
} catch {
|
|
410
|
+
name = row.url;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
name,
|
|
416
|
+
type: 'login' as CredentialType,
|
|
417
|
+
url: row.url || undefined,
|
|
418
|
+
fields,
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
// iCloud Keychain CSV Parser
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Parse iCloud Keychain / Apple Passwords CSV export.
|
|
429
|
+
* Common columns include:
|
|
430
|
+
* Title, URL, Username, Password, Notes, OTPAuth
|
|
431
|
+
*/
|
|
432
|
+
export function parseICloudCSV(csv: string): ImportedCredential[] {
|
|
433
|
+
const records = parseCsvRows(csv);
|
|
434
|
+
|
|
435
|
+
return records.map((row) => {
|
|
436
|
+
const title = getRowValue(row, ['Title', 'Name', 'Site', 'Website']);
|
|
437
|
+
const url = getRowValue(row, ['URL', 'Website', 'Site URL', 'Site']);
|
|
438
|
+
const username = getRowValue(row, ['Username', 'User Name', 'Account', 'Login']);
|
|
439
|
+
const password = getRowValue(row, ['Password', 'Passcode']);
|
|
440
|
+
const notes = getRowValue(row, ['Notes', 'Note', 'Comments']);
|
|
441
|
+
const totp = getRowValue(row, ['OTPAuth', 'TOTP', 'One-Time Code', 'One-Time Password']);
|
|
442
|
+
|
|
443
|
+
const fields: ImportedField[] = [];
|
|
444
|
+
if (username) fields.push({ key: 'username', value: username, sensitive: false });
|
|
445
|
+
if (password) fields.push({ key: 'password', value: password, sensitive: true });
|
|
446
|
+
if (notes) fields.push({ key: 'notes', value: notes, sensitive: false });
|
|
447
|
+
if (totp) fields.push({ key: 'totp', value: totp, sensitive: true });
|
|
448
|
+
|
|
449
|
+
let name = title || 'Untitled';
|
|
450
|
+
if (!title && url) {
|
|
451
|
+
try {
|
|
452
|
+
name = new URL(url).hostname.replace(/^www\./, '');
|
|
453
|
+
} catch {
|
|
454
|
+
name = url;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
name,
|
|
460
|
+
type: 'login' as CredentialType,
|
|
461
|
+
url: url || undefined,
|
|
462
|
+
fields,
|
|
463
|
+
};
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
// LastPass CSV Parser
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Parse LastPass CSV export.
|
|
473
|
+
* Common columns include:
|
|
474
|
+
* url, username, password, totp, extra, name, grouping, fav
|
|
475
|
+
*/
|
|
476
|
+
export function parseLastPassCSV(csv: string): ImportedCredential[] {
|
|
477
|
+
const records = parseCsvRows(csv);
|
|
478
|
+
|
|
479
|
+
return records.map((row) => {
|
|
480
|
+
const nameFromRow = getRowValue(row, ['name', 'title']);
|
|
481
|
+
const url = getRowValue(row, ['url', 'uri', 'website']);
|
|
482
|
+
const username = getRowValue(row, ['username', 'user name', 'login']);
|
|
483
|
+
const password = getRowValue(row, ['password', 'passcode']);
|
|
484
|
+
const notes = getRowValue(row, ['extra', 'notes', 'note']);
|
|
485
|
+
const totp = getRowValue(row, ['totp', 'otp']);
|
|
486
|
+
const grouping = getRowValue(row, ['grouping', 'group', 'folder']);
|
|
487
|
+
|
|
488
|
+
const fields: ImportedField[] = [];
|
|
489
|
+
if (username) fields.push({ key: 'username', value: username, sensitive: false });
|
|
490
|
+
if (password) fields.push({ key: 'password', value: password, sensitive: true });
|
|
491
|
+
if (notes) fields.push({ key: 'notes', value: notes, sensitive: false });
|
|
492
|
+
if (totp) fields.push({ key: 'totp', value: totp, sensitive: true });
|
|
493
|
+
|
|
494
|
+
let name = nameFromRow || 'Untitled';
|
|
495
|
+
if (!nameFromRow && url) {
|
|
496
|
+
try {
|
|
497
|
+
name = new URL(url).hostname.replace(/^www\./, '');
|
|
498
|
+
} catch {
|
|
499
|
+
name = url;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const tags = grouping ? [grouping] : undefined;
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
name,
|
|
507
|
+
type: 'login' as CredentialType,
|
|
508
|
+
url: url || undefined,
|
|
509
|
+
fields,
|
|
510
|
+
tags,
|
|
511
|
+
};
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ---------------------------------------------------------------------------
|
|
516
|
+
// JSON Parsers
|
|
517
|
+
// ---------------------------------------------------------------------------
|
|
518
|
+
|
|
519
|
+
export function parse1PasswordJSON(json: string): ImportedCredential[] {
|
|
520
|
+
const root = parseJsonObject(json);
|
|
521
|
+
const items = coerceItems(root, ['items']);
|
|
522
|
+
|
|
523
|
+
return items.map((item: any) => {
|
|
524
|
+
const login = item?.login ?? {};
|
|
525
|
+
const fields: ImportedField[] = [];
|
|
526
|
+
|
|
527
|
+
if (login.username) fields.push({ key: 'username', value: String(login.username), sensitive: false });
|
|
528
|
+
if (login.password) fields.push({ key: 'password', value: String(login.password), sensitive: true });
|
|
529
|
+
if (item?.notesPlain || item?.notes) fields.push({ key: 'notes', value: String(item.notesPlain || item.notes), sensitive: false });
|
|
530
|
+
|
|
531
|
+
const url = login?.urls?.[0]?.href || login?.uris?.[0]?.uri || login?.uris?.[0]?.url || item?.url || undefined;
|
|
532
|
+
return {
|
|
533
|
+
name: String(item?.title || item?.name || 'Untitled'),
|
|
534
|
+
type: 'login' as CredentialType,
|
|
535
|
+
url,
|
|
536
|
+
fields,
|
|
537
|
+
};
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function parseBitwardenJSON(json: string): ImportedCredential[] {
|
|
542
|
+
const root = parseJsonObject(json);
|
|
543
|
+
const items = coerceItems(root, ['items']);
|
|
544
|
+
|
|
545
|
+
return items.map((item: any) => {
|
|
546
|
+
const login = item?.login ?? {};
|
|
547
|
+
const fields: ImportedField[] = [];
|
|
548
|
+
|
|
549
|
+
if (login.username) fields.push({ key: 'username', value: String(login.username), sensitive: false });
|
|
550
|
+
if (login.password) fields.push({ key: 'password', value: String(login.password), sensitive: true });
|
|
551
|
+
if (item?.notes) fields.push({ key: 'notes', value: String(item.notes), sensitive: false });
|
|
552
|
+
if (login?.totp) fields.push({ key: 'totp', value: String(login.totp), sensitive: true });
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
name: String(item?.name || 'Untitled'),
|
|
556
|
+
type: 'login' as CredentialType,
|
|
557
|
+
url: login?.uris?.[0]?.uri || undefined,
|
|
558
|
+
fields,
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function parseChromeJSON(json: string): ImportedCredential[] {
|
|
564
|
+
const root = parseJsonObject(json);
|
|
565
|
+
const items = coerceItems(root, ['items', 'logins', 'passwords', 'credentials']);
|
|
566
|
+
|
|
567
|
+
return items.map((item: any) => {
|
|
568
|
+
const fields: ImportedField[] = [];
|
|
569
|
+
if (item?.username) fields.push({ key: 'username', value: String(item.username), sensitive: false });
|
|
570
|
+
if (item?.password) fields.push({ key: 'password', value: String(item.password), sensitive: true });
|
|
571
|
+
if (item?.note || item?.notes) fields.push({ key: 'notes', value: String(item.note || item.notes), sensitive: false });
|
|
572
|
+
|
|
573
|
+
return {
|
|
574
|
+
name: String(item?.name || item?.title || 'Untitled'),
|
|
575
|
+
type: 'login' as CredentialType,
|
|
576
|
+
url: item?.url || item?.origin || item?.signon_realm || undefined,
|
|
577
|
+
fields,
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function parseFirefoxJSON(json: string): ImportedCredential[] {
|
|
583
|
+
const root = parseJsonObject(json);
|
|
584
|
+
const items = coerceItems(root, ['logins', 'items', 'credentials']);
|
|
585
|
+
|
|
586
|
+
return items.map((item: any) => {
|
|
587
|
+
const fields: ImportedField[] = [];
|
|
588
|
+
if (item?.username) fields.push({ key: 'username', value: String(item.username), sensitive: false });
|
|
589
|
+
if (item?.password) fields.push({ key: 'password', value: String(item.password), sensitive: true });
|
|
590
|
+
|
|
591
|
+
const url = item?.url || item?.hostname || undefined;
|
|
592
|
+
let name = 'Untitled';
|
|
593
|
+
if (url) {
|
|
594
|
+
try {
|
|
595
|
+
name = new URL(String(url)).hostname.replace(/^www\./, '');
|
|
596
|
+
} catch {
|
|
597
|
+
name = String(url);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
name,
|
|
603
|
+
type: 'login' as CredentialType,
|
|
604
|
+
url,
|
|
605
|
+
fields,
|
|
606
|
+
};
|
|
607
|
+
});
|
|
608
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Scope — Matching & Field Exclusion Resolution
|
|
3
|
+
* ========================================================
|
|
4
|
+
*
|
|
5
|
+
* Handles scope normalization, credential-to-scope matching for access control,
|
|
6
|
+
* and resolution of which sensitive fields to exclude from reads.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { CredentialFile } from '../types';
|
|
10
|
+
import { getDefaultSync } from './defaults';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a scope string for consistent matching.
|
|
14
|
+
* Trims whitespace, applies NFKC normalization, and lowercases.
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeScope(scope: string): string {
|
|
17
|
+
return scope.trim().normalize('NFKC').toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if a credential matches any of the given scopes.
|
|
22
|
+
*
|
|
23
|
+
* Scope types:
|
|
24
|
+
* - `*` — matches everything
|
|
25
|
+
* - `cred-xxxxx` — exact credential ID match
|
|
26
|
+
* - `tag:X` — matches if credential's meta.tags contains X
|
|
27
|
+
* - `vault:X` — matches if credential's vaultId equals X
|
|
28
|
+
*
|
|
29
|
+
* Selector wildcards:
|
|
30
|
+
* - `tag:*` / `vault:*` — match any tag / any vault
|
|
31
|
+
* - trailing `*` in tag/vault selectors — prefix wildcard
|
|
32
|
+
* (e.g. `tag:generated/*`, `vault:pri*`)
|
|
33
|
+
*
|
|
34
|
+
* All comparisons use normalized values.
|
|
35
|
+
* Empty scopes array matches nothing.
|
|
36
|
+
*/
|
|
37
|
+
function matchesSelector(value: string, selector: string): boolean {
|
|
38
|
+
if (selector === '*') return true;
|
|
39
|
+
if (selector.endsWith('*')) {
|
|
40
|
+
const prefix = selector.slice(0, -1);
|
|
41
|
+
return value.startsWith(prefix);
|
|
42
|
+
}
|
|
43
|
+
return value === selector;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function matchesScope(credential: CredentialFile, scopes: string[]): boolean {
|
|
47
|
+
if (scopes.length === 0) return false;
|
|
48
|
+
|
|
49
|
+
const normalizedScopes = scopes.map(normalizeScope);
|
|
50
|
+
|
|
51
|
+
for (const scope of normalizedScopes) {
|
|
52
|
+
// Wildcard
|
|
53
|
+
if (scope === '*') return true;
|
|
54
|
+
|
|
55
|
+
// Exact ID match
|
|
56
|
+
if (scope === normalizeScope(credential.id)) return true;
|
|
57
|
+
|
|
58
|
+
// Tag match
|
|
59
|
+
if (scope.startsWith('tag:')) {
|
|
60
|
+
const tagValue = scope.slice(4);
|
|
61
|
+
const tags = (credential.meta.tags as string[] | undefined) || [];
|
|
62
|
+
if (tagValue === '*' && tags.length > 0) return true;
|
|
63
|
+
if (tags.some(t => matchesSelector(normalizeScope(t), tagValue))) return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Vault match
|
|
67
|
+
if (scope.startsWith('vault:')) {
|
|
68
|
+
const vaultValue = scope.slice(6);
|
|
69
|
+
if (matchesSelector(normalizeScope(credential.vaultId), vaultValue)) return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolve which fields to exclude from a credential read.
|
|
78
|
+
*
|
|
79
|
+
* Resolution order:
|
|
80
|
+
* 1. Token's explicit excludeFields (even if empty array []) → use as-is
|
|
81
|
+
* 2. Type default from `defaults.credential.excludeFields.{type}` → use if found
|
|
82
|
+
* 3. Fall back to empty array (exclude nothing)
|
|
83
|
+
*/
|
|
84
|
+
export function resolveExcludeFields(
|
|
85
|
+
tokenExcludes: string[] | undefined,
|
|
86
|
+
credentialType: string
|
|
87
|
+
): string[] {
|
|
88
|
+
// Token explicitly set excludeFields (even [] means "show everything")
|
|
89
|
+
if (tokenExcludes !== undefined) {
|
|
90
|
+
if (Array.isArray(tokenExcludes) && tokenExcludes.length === 0) {
|
|
91
|
+
console.warn('[credential-scope] Token has excludeFields: [] — all sensitive fields will be exposed. Ensure this is intentional.');
|
|
92
|
+
}
|
|
93
|
+
return tokenExcludes;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Type default
|
|
97
|
+
const typeDefault = getDefaultSync<string[]>(
|
|
98
|
+
`defaults.credential.excludeFields.${credentialType}`,
|
|
99
|
+
[]
|
|
100
|
+
);
|
|
101
|
+
return typeDefault;
|
|
102
|
+
}
|