From 437ce60cd36035c78eafcaa081f42063288f8a7b Mon Sep 17 00:00:00 2001 From: Daniel Ehrhardt Date: Wed, 28 Oct 2026 19:22:45 +0100 Subject: [PATCH] Add Dashlane password import Dashlane gets its own source in the import dialog with export steps. The zip from Export to CSV is read as it is, 2FA from the newer otpUrl column is linked to its login, username2/username3 fill an empty username, the category becomes a tag, and the older desktop app's JSON export is read too. A .dash secure archive explains to export a CSV instead. --- .../vault/password-import-dialog.tsx | 57 +++++++++++++++--- apps/desktop/src/pages/vault/logins-page.tsx | 2 +- .../components/sections/Capabilities.astro | 2 +- .../src/components/sections/HowItWorks.astro | 2 +- docs/FEATURES.md | 2 +- .../dashlane-import/1-dashlane-guide-dark.png | Bin 0 -> 168686 bytes .../1-dashlane-guide-light.png | Bin 0 -> 140940 bytes .../dashlane-import/2-dashlane-review.png | Bin 0 -> 158082 bytes .../dashlane-import/3-dashlane-done.png | Bin 0 -> 145660 bytes packages/core/src/vault/passwordImport.ts | 49 ++++++++++++--- packages/core/test/password-import.test.ts | 35 +++++++++++ 11 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 docs/screenshots/dashlane-import/1-dashlane-guide-dark.png create mode 100644 docs/screenshots/dashlane-import/1-dashlane-guide-light.png create mode 100644 docs/screenshots/dashlane-import/2-dashlane-review.png create mode 100644 docs/screenshots/dashlane-import/3-dashlane-done.png diff --git a/apps/desktop/src/components/vault/password-import-dialog.tsx b/apps/desktop/src/components/vault/password-import-dialog.tsx index 47f668d5..b35afa6a 100644 --- a/apps/desktop/src/components/vault/password-import-dialog.tsx +++ b/apps/desktop/src/components/vault/password-import-dialog.tsx @@ -21,7 +21,7 @@ import { isGrantCancelled, useVaultGrant, withGrant } from "./grant"; import { isVaultLocked, toastApiError } from "./vault-utils"; import { WorkspaceSelect } from "./workspace-select"; -type Source = "chrome" | "1password" | "other"; +type Source = "chrome" | "1password" | "dashlane" | "other"; type Filter = "all" | "new" | "update" | "unchanged" | "conflict"; const SOURCE_NAMES: Record = { @@ -38,7 +38,7 @@ const SOURCE_NAMES: Record = { }; const GRANT_REASON = "Enter your vault passphrase to compare the export with your saved logins."; -const isExportFile = (f: File) => /\.(csv|1pux|txt)$/i.test(f.name) || f.type === "text/csv"; +const isExportFile = (f: File) => /\.(csv|1pux|txt|zip|json)$/i.test(f.name) || f.type === "text/csv"; /** An export can be older than the vault, so replacing a saved password is always an explicit choice. */ const preselected = (r: PasswordImportRow) => r.action !== "unchanged" && !r.conflictKey && !r.replacesPassword; const plural = (n: number, one: string, many = `${one}s`) => `${n.toLocaleString()} ${n === 1 ? one : many}`; @@ -122,8 +122,12 @@ function ImportFlow({ defaultWorkspaceId, onClose }: { defaultWorkspaceId: strin const readFile = (file: File) => { if (preview.isPending) return; + if (/\.dash$/i.test(file.name)) { + setError("That's Dashlane's encrypted backup. In Dashlane, choose Export to CSV instead and drop the .zip here."); + return; + } if (!isExportFile(file)) { - setError("Choose the .csv or .1pux file your password manager exported."); + setError("Choose the .csv, .zip or .1pux file your password manager exported."); return; } preview.mutate({ file, scope: workspaceId }); @@ -169,7 +173,7 @@ function ImportFlow({ defaultWorkspaceId, onClose }: { defaultWorkspaceId: strin ? "Pick what to bring over. Logins you already saved are updated, not duplicated." : step.kind === "done" ? `Finished importing ${step.file.name}.` - : "Bring your logins over from Chrome, 1Password or another password manager. Nothing is saved until you confirm."} + : "Bring your logins over from Chrome, 1Password, Dashlane or another password manager. Nothing is saved until you confirm."} @@ -248,10 +252,11 @@ function SourcePicker({ value, onChange }: { value: Source; onChange: (s: Source const options: { id: Source; label: string; hint: string; mark: ReactNode }[] = [ { id: "chrome", label: "Chrome", hint: "Edge, Brave & Arc too", mark: }, { id: "1password", label: "1Password", hint: ".1pux or .csv", mark: }, + { id: "dashlane", label: "Dashlane", hint: ".zip or .csv", mark: }, { id: "other", label: "Other", - hint: "Bitwarden, Safari, Firefox…", + hint: "Bitwarden, Safari…", mark: ( @@ -260,7 +265,7 @@ function SourcePicker({ value, onChange }: { value: Source; onChange: (s: Source }, ]; return ( -
+
{options.map((o) => { const active = value === o.id; return ( @@ -347,6 +352,31 @@ function SourceGuide({ source }: { source: Source }) {

Archived items, credit cards and notes are left out. 2FA codes are saved and linked to their login.

)} + {source === "dashlane" && ( + <> +
    + + + Open + + app.dashlane.com + + + and sign in. + + + + Open the Vault menu at the top left, choose Settings → Export data and click Export to CSV. + + + Drop the downloaded .zip below as it is — no need to unpack it. + +
+

+ 2FA codes come along and are linked to their login. Notes, IDs and payment cards in the export are left out; Dashlane can't export passkeys. +

+ + )} {source === "other" && ( <>
@@ -354,7 +384,7 @@ function SourceGuide({ source }: { source: Source }) { ["Bitwarden", "Tools → Export vault → .csv"], ["Apple Passwords", "File → Export All Passwords"], ["Firefox", "about:logins → ⋯ → Export Logins"], - ["LastPass · Dashlane", "Advanced → Export → CSV"], + ["LastPass", "Advanced → Export → CSV"], ["Proton Pass", "Settings → Export → CSV"], ["KeePassXC", "Database → Export → CSV"], ].map(([name, where]) => ( @@ -419,13 +449,13 @@ function ExportDropZone({ busy, onFile }: { busy: boolean; onFile: (file: File)

{busy ? "Reading your export…" : dragging ? "Drop to read the file" : "Drop your export here"}

- .csv or .1pux · or browse files · you'll review everything first + .csv, .zip or .1pux · or browse files · you'll review everything first

{ @@ -802,6 +832,15 @@ function ChromeMark() { ); } +function DashlaneMark() { + return ( + + + + + ); +} + function OnePasswordMark() { return ( diff --git a/apps/desktop/src/pages/vault/logins-page.tsx b/apps/desktop/src/pages/vault/logins-page.tsx index 1d70fbdf..740c952a 100644 --- a/apps/desktop/src/pages/vault/logins-page.tsx +++ b/apps/desktop/src/pages/vault/logins-page.tsx @@ -201,7 +201,7 @@ export default function LoginsPage() {