Compare commits

...

10 Commits

Author SHA1 Message Date
Leendert de Borst
ab82a63a0a Bump version to 0.16.2 (#818) 2025-05-01 08:57:09 +02:00
dependabot[bot]
82376b696c Bump vite
Bumps the npm_and_yarn group with 1 update in the /browser-extension directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 6.2.6 to 6.3.4
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-01 08:45:37 +02:00
Leendert de Borst
0c8fc191a6 Update date format in RecentEmails.razor (#815) 2025-04-30 14:41:26 +02:00
Leendert de Borst
b71f0dd2c3 Tweak Login.razor margins (#809) 2025-04-28 18:44:15 +02:00
Leendert de Borst
3617c551e3 Refresh password salt and ephemeral after changing it (#809) 2025-04-28 18:44:15 +02:00
Leendert de Borst
901caa896b Add dashlane importer and unittest (#811) 2025-04-28 18:44:08 +02:00
dependabot[bot]
89534bf78e Bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the /browser-extension directory: [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) and [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom).


Updates `react-router` from 7.2.0 to 7.5.2
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.5.2/packages/react-router)

Updates `react-router-dom` from 7.2.0 to 7.5.2
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.5.2/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.5.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: react-router-dom
  dependency-version: 7.5.2
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-25 17:18:01 +02:00
dependabot[bot]
e82595162f Bump nokogiri in /docs in the bundler group across 1 directory
Bumps the bundler group with 1 update in the /docs directory: [nokogiri](https://github.com/sparklemotion/nokogiri).


Updates `nokogiri` from 1.18.4 to 1.18.8
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.18.4...v1.18.8)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.18.8
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 12:37:19 +02:00
Leendert de Borst
93c439e852 Fix nullability warning in FaviconExtractor.cs (#805) 2025-04-21 16:01:22 +02:00
dependabot[bot]
ff08fae579 Bump HtmlAgilityPack from 1.12.0 to 1.12.1
Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/zzzprojects/html-agility-pack/releases)
- [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: HtmlAgilityPack
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 16:01:22 +02:00
22 changed files with 381 additions and 63 deletions

View File

@@ -16,7 +16,7 @@
"otpauth": "^9.3.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.4",
"react-router-dom": "^7.5.2",
"secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0",
"sql.js": "^1.12.0",
"vitest": "^3.0.8",
@@ -1973,12 +1973,6 @@
"@types/har-format": "*"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"license": "MIT"
},
"node_modules/@types/emscripten": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.40.0.tgz",
@@ -10292,12 +10286,11 @@
}
},
"node_modules/react-router": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.2.0.tgz",
"integrity": "sha512-fXyqzPgCPZbqhrk7k3hPcCpYIlQ2ugIXDboHUzhJISFVy2DEPsmHgN588MyGmkIOv3jDgNfUE3kJi83L28s/LQ==",
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz",
"integrity": "sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
@@ -10316,12 +10309,12 @@
}
},
"node_modules/react-router-dom": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.2.0.tgz",
"integrity": "sha512-cU7lTxETGtQRQbafJubvZKHEn5izNABxZhBY0Jlzdv0gqQhCPQt2J8aN5ZPjS6mQOXn5NnirWNh+FpE8TTYN0Q==",
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.2.tgz",
"integrity": "sha512-yk1XW8Fj7gK7flpYBXF3yzd2NbX6P7Kxjvs2b5nu1M04rb5pg/Zc4fGdBNTeT4eDYL2bvzWNyKaIMJX/RKHTTg==",
"license": "MIT",
"dependencies": {
"react-router": "7.2.0"
"react-router": "7.5.2"
},
"engines": {
"node": ">=20.0.0"
@@ -11898,6 +11891,48 @@
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tinypool": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
@@ -12449,14 +12484,17 @@
}
},
"node_modules/vite": {
"version": "6.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
"version": "6.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
"picomatch": "^4.0.2",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
"rollup": "^4.34.9",
"tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"
@@ -12599,6 +12637,32 @@
"node": ">=8.10.0"
}
},
"node_modules/vite/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/vitest": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz",

View File

@@ -32,7 +32,7 @@
"otpauth": "^9.3.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.4",
"react-router-dom": "^7.5.2",
"secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0",
"sql.js": "^1.12.0",
"vitest": "^3.0.8",

View File

@@ -515,7 +515,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 14;
CURRENT_PROJECT_VERSION = 15;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -530,7 +530,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.16.1;
MARKETING_VERSION = 0.16.2;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -554,7 +554,7 @@
CODE_SIGN_ENTITLEMENTS = AliasVault/AliasVault.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 14;
CURRENT_PROJECT_VERSION = 15;
DEVELOPMENT_TEAM = 8PHW4HN3F7;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -569,7 +569,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 0.16.1;
MARKETING_VERSION = 0.16.2;
OTHER_LDFLAGS = (
"-framework",
SafariServices,

View File

@@ -6,7 +6,7 @@ export class AppInfo {
/**
* The current extension version. This should be updated with each release of the extension.
*/
public static readonly VERSION = '0.16.1';
public static readonly VERSION = '0.16.2';
/**
* The minimum supported AliasVault server (API) version. If the server version is below this, the

View File

@@ -7,7 +7,7 @@ export default defineConfig({
manifest: {
name: "AliasVault",
description: "AliasVault Browser AutoFill Extension. Keeping your personal information private.",
version: "0.16.1",
version: "0.16.2",
content_security_policy: {
extension_pages: "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
},

View File

@@ -239,9 +239,9 @@ GEM
minitest (5.25.1)
net-http (0.5.0)
uri
nokogiri (1.18.4-x86_64-linux-gnu)
nokogiri (1.18.8-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.4-x86_64-linux-musl)
nokogiri (1.18.8-x86_64-linux-musl)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)

View File

@@ -82,9 +82,9 @@ else
</h2>
<FullScreenLoadingIndicator @ref="_loadingIndicator"/>
<ServerValidationErrors @ref="_serverValidationErrors"/>
<EditForm Model="_loginModel" OnValidSubmit="HandleLogin" class="mt-8 space-y-6">
<EditForm Model="_loginModel" OnValidSubmit="HandleLogin" class="mt-4 space-y-6">
<ServerValidationErrors @ref="_serverValidationErrors"/>
<DataAnnotationsValidator/>
<div>
<label asp-for="Input.Email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your username or email</label>

View File

@@ -80,7 +80,7 @@
}
</td>
<td class="p-4 text-sm font-normal text-gray-500 whitespace-nowrap dark:text-gray-400">
<span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@mail.DateSystem</span>
<span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@mail.DateSystem.ToString("yyyy-MM-dd")</span>
</td>
</tr>
}
@@ -125,7 +125,7 @@
public void OnVisibilityChange(bool isVisible)
{
_isPageVisible = isVisible;
if (isVisible && DbService.Settings.AutoEmailRefresh)
{
// Start polling if visible and auto-refresh is enabled
@@ -136,7 +136,7 @@
// Stop polling if hidden
StopPolling();
}
// If becoming visible, do an immediate refresh
if (isVisible)
{
@@ -150,13 +150,13 @@
if (_pollingCts != null) {
return;
}
_pollingCts = new CancellationTokenSource();
// Start polling task
_ = PollForEmails(_pollingCts.Token);
}
private void StopPolling()
{
if (_pollingCts != null)
@@ -166,7 +166,7 @@
_pollingCts = null;
}
}
private async Task PollForEmails(CancellationToken cancellationToken)
{
try
@@ -217,7 +217,7 @@
{
// Stop polling
StopPolling();
// Unregister the visibility callback using the same reference
if (_dotNetRef != null)
{

View File

@@ -87,7 +87,7 @@
<p>@DuplicateCredentialsCount duplicate credential(s) were found and will not be imported.</p>
</div>
}
@if (ImportedCredentials.Count == 0)
{
<div class="p-4 mb-4 text-amber-700 bg-amber-100 rounded-lg dark:bg-amber-800/30 dark:text-amber-300" role="alert">
@@ -369,7 +369,7 @@
ImportError = null;
ImportSuccessMessage = null;
StateHasChanged();
// Let UI update to start showing the loading indicator
await Task.Delay(50);
}
@@ -479,6 +479,12 @@
return;
}
if (e.File.Name.EndsWith(".zip"))
{
ImportError = $"Please unzip the {ServiceName} export file before importing, please read the instructions below for more information.";
return;
}
try
{
IsImporting = true;
@@ -500,10 +506,10 @@
await Task.WhenAll(processingTask, delayTask);
ImportedCredentials = await processingTask;
// Detect and remove duplicates before showing the preview
await DetectAndRemoveDuplicates();
CurrentStep = ImportStep.Preview;
}
catch (Exception ex)
@@ -532,7 +538,7 @@
)).ToList();
DuplicateCredentialsCount = duplicates.Count;
// Remove duplicates from the import list
ImportedCredentials = ImportedCredentials.Except(duplicates).ToList();
}

View File

@@ -0,0 +1,21 @@
@using AliasVault.ImportExport.Models
@using AliasVault.ImportExport.Importers
@inject NavigationManager NavigationManager
@inject GlobalNotificationService GlobalNotificationService
@inject ILogger<ImportServiceBitwarden> Logger
<ImportServiceCard
ServiceName="Dashlane"
Description="Import passwords from your Dashlane account"
LogoUrl="img/importers/dashlane.svg"
ProcessFileCallback="ProcessFile">
<p class="text-gray-700 dark:text-gray-300 mb-4">In order to import your Dashlane passwords, you need to export it as a CSV file. You can do this by logging into your Dashlane account, going to the 'Account' > 'Settings' menu and selecting 'Export to CSV'.</p>
<p class="text-gray-700 dark:text-gray-300 mb-4">Note: the .zip file you download will contain a "credentials.csv" file. You need to unzip the archive first, and then upload the "credentials.csv" CSV file below.</p>
</ImportServiceCard>
@code {
private static async Task<List<ImportedCredential>> ProcessFile(string fileContents)
{
return await DashlaneImporter.ImportFromCsvAsync(fileContents);
}
}

View File

@@ -24,6 +24,7 @@
<ImportService1Password />
<ImportServiceBitwarden />
<ImportServiceChrome />
<ImportServiceDashlane />
<ImportServiceFirefox />
<ImportServiceKeePass />
<ImportServiceKeePassXC />

View File

@@ -104,30 +104,39 @@ else
{
await base.OnAfterRenderAsync(firstRender);
// Check on server if 2FA is enabled
// Get the current password ephemeral and salt from the server
// which is required to confirm the current password.
if (firstRender)
{
// Get the QR code and secret for the authenticator app.
var response = await Http.GetFromJsonAsync<PasswordChangeInitiateResponse>("v1/Auth/change-password/initiate");
if (response == null)
{
GlobalNotificationService.AddErrorMessage("Failed to initiate the password change process.", true);
IsLoading = false;
StateHasChanged();
return;
}
CurrentServerEphemeral = response.ServerEphemeral;
CurrentSalt = response.Salt;
CurrentEncryptionType = response.EncryptionType;
CurrentEncryptionSettings = response.EncryptionSettings;
await GetCurrentPasswordEphemeralAndSalt();
IsLoading = false;
StateHasChanged();
}
}
/// <summary>
/// Gets the current password ephemeral and salt from the server which
/// is required to confirm the current password.
/// </summary>
private async Task GetCurrentPasswordEphemeralAndSalt()
{
var response = await Http.GetFromJsonAsync<PasswordChangeInitiateResponse>("v1/Auth/change-password/initiate");
if (response == null)
{
GlobalNotificationService.AddErrorMessage("Failed to initiate the password change process.", true);
IsLoading = false;
StateHasChanged();
return;
}
CurrentServerEphemeral = response.ServerEphemeral;
CurrentSalt = response.Salt;
CurrentEncryptionType = response.EncryptionType;
CurrentEncryptionSettings = response.EncryptionSettings;
}
/// <summary>
/// Initiates the password change process.
/// </summary>
@@ -215,6 +224,10 @@ else
// Set success message.
GlobalNotificationService.AddSuccessMessage("Password changed successfully.", true);
// Get the new password ephemeral and salt from the server, which is required if the usre
// wants to change the password again.
await GetCurrentPasswordEphemeralAndSalt();
GlobalLoadingSpinner.Hide();
StateHasChanged();
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<circle cx="512" cy="512" r="512" style="fill:#10353e"/>
<path d="m544.7 458.9 53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V334.9c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v136.4c0 3.1 2.6 5.9 6.8 7.4m0 244.8 53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V579.7c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v136.4c0 3.1 2.6 6 6.8 7.4M445 413.3l53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V277.3c0-3.1-2.6-6-6.8-7.5L457.5 250c-8.7-3.1-19.3 1-19.3 7.5v148.4c0 3.1 2.6 5.9 6.8 7.4m0 326.9 53.8 19.8c8.7 3.1 19.3-1 19.3-7.5v-148c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v148c0 3.1 2.6 6 6.8 7.5m-26.6-457.4c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v464.1c0 3.1 2.6 6 6.8 7.5l53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V282.8zM710.7 406l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v224c0 3.1 2.6 6 6.8 7.5l53.8 19.8c8.7 3.1 19.3-1 19.3-7.5v-224c0-3.1-2.6-6-6.8-7.5" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -30,7 +30,7 @@ public static class AppInfo
/// <summary>
/// Gets the patch version number.
/// </summary>
public const int VersionPatch = 1;
public const int VersionPatch = 2;
/// <summary>
/// Gets the minimum supported AliasVault client version. Normally the minimum client version is the same

View File

@@ -22,7 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -69,6 +69,7 @@
<EmbeddedResource Include="TestData\Exports\keepassxc.csv" />
<EmbeddedResource Include="TestData\Exports\1password_8.csv" />
<EmbeddedResource Include="TestData\Exports\protonpass.csv" />
<EmbeddedResource Include="TestData\Exports\dashlane.csv" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,4 @@
username,username2,username3,title,password,note,url,category,otpUrl
Test username,,,Test,password123,,https://Test,,
googleuser,,,Google,googlepassword,,https://www.google.com,,
testusername,testusernamealternative,,Local,testpassword,testnote,https://www.testwebsite.local,,
1 username username2 username3 title password note url category otpUrl
2 Test username Test password123 https://Test
3 googleuser Google googlepassword https://www.google.com
4 testusername testusernamealternative Local testpassword testnote https://www.testwebsite.local

View File

@@ -406,4 +406,52 @@ public class ImportExportTests
Assert.That(testWithEmailCredential.Email, Is.EqualTo("testalias.gating981@passinbox.com"));
});
}
/// <summary>
/// Test case for importing credentials from Dashlane CSV and ensuring all values are present.
/// </summary>
/// <returns>Async task.</returns>
[Test]
public async Task ImportCredentialsFromDashlaneCsv()
{
// Arrange
var fileContent = await ResourceReaderUtility.ReadEmbeddedResourceStringAsync("AliasVault.UnitTests.TestData.Exports.dashlane.csv");
// Act
var importedCredentials = await DashlaneImporter.ImportFromCsvAsync(fileContent);
// Assert
Assert.That(importedCredentials, Has.Count.EqualTo(3));
// Test specific entries
var testCredential = importedCredentials.First(c => c.ServiceName == "Test");
Assert.Multiple(() =>
{
Assert.That(testCredential.ServiceName, Is.EqualTo("Test"));
Assert.That(testCredential.ServiceUrl, Is.EqualTo("https://Test"));
Assert.That(testCredential.Username, Is.EqualTo("Test username"));
Assert.That(testCredential.Password, Is.EqualTo("password123"));
Assert.That(testCredential.Notes, Is.Null);
});
var googleCredential = importedCredentials.First(c => c.ServiceName == "Google");
Assert.Multiple(() =>
{
Assert.That(googleCredential.ServiceName, Is.EqualTo("Google"));
Assert.That(googleCredential.ServiceUrl, Is.EqualTo("https://www.google.com"));
Assert.That(googleCredential.Username, Is.EqualTo("googleuser"));
Assert.That(googleCredential.Password, Is.EqualTo("googlepassword"));
Assert.That(googleCredential.Notes, Is.Null);
});
var localCredential = importedCredentials.First(c => c.ServiceName == "Local");
Assert.Multiple(() =>
{
Assert.That(localCredential.ServiceName, Is.EqualTo("Local"));
Assert.That(localCredential.ServiceUrl, Is.EqualTo("https://www.testwebsite.local"));
Assert.That(localCredential.Username, Is.EqualTo("testusername"));
Assert.That(localCredential.Password, Is.EqualTo("testpassword"));
Assert.That(localCredential.Notes, Is.EqualTo("testnote\nAlternative username 1: testusernamealternative"));
});
}
}

View File

@@ -22,7 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.12.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
<PackageReference Include="SkiaSharp" Version="3.116.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="3.116.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">

View File

@@ -114,7 +114,7 @@ public static class FaviconExtractor
var defaultFavicon = new HtmlNode(HtmlNodeType.Element, htmlDoc, 0);
defaultFavicon.Attributes.Add("href", $"{uri.GetLeftPart(UriPartial.Authority)}/favicon.ico");
return
HtmlNodeCollection?[] nodeArray =
[
htmlDoc.DocumentNode.SelectNodes("//link[@rel='icon' and @type='image/svg+xml']"),
htmlDoc.DocumentNode.SelectNodes("//link[@rel='icon' and @sizes='96x96']"),
@@ -126,6 +126,9 @@ public static class FaviconExtractor
htmlDoc.DocumentNode.SelectNodes("//link[@rel='icon' or @rel='shortcut icon']"),
new HtmlNodeCollection(htmlDoc.DocumentNode) { defaultFavicon },
];
// Filter node array to only return non-null values and cast to non-nullable array
return nodeArray.Where(x => x != null).Cast<HtmlNodeCollection>().ToArray();
}
private static async Task<byte[]?> TryGetFaviconAsync(HttpClient client, Uri uri)

View File

@@ -0,0 +1,81 @@
//-----------------------------------------------------------------------
// <copyright file="DashlaneImporter.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.ImportExport.Importers;
using AliasVault.ImportExport.Models;
using AliasVault.ImportExport.Models.Imports;
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
/// <summary>
/// Imports credentials from Dashlane.
/// </summary>
public static class DashlaneImporter
{
/// <summary>
/// Imports Dashlane CSV file and converts contents to list of ImportedCredential model objects.
/// </summary>
/// <param name="fileContent">The content of the CSV file.</param>
/// <returns>The imported list of ImportedCredential objects.</returns>
public static async Task<List<ImportedCredential>> ImportFromCsvAsync(string fileContent)
{
using var reader = new StringReader(fileContent);
using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture));
var credentials = new List<ImportedCredential>();
await foreach (var record in csv.GetRecordsAsync<DashlaneCsvRecord>())
{
var credential = new ImportedCredential
{
ServiceName = record.Title,
ServiceUrl = record.URL,
Username = record.Username,
Password = record.Password,
TwoFactorSecret = record.OTPUrl,
Notes = BuildNotes(record)
};
credentials.Add(credential);
}
if (credentials.Count == 0)
{
throw new InvalidOperationException("No records found in the CSV file.");
}
return credentials;
}
private static string? BuildNotes(DashlaneCsvRecord record)
{
var notes = new List<string>();
if (!string.IsNullOrEmpty(record.Note))
{
notes.Add(record.Note);
}
if (!string.IsNullOrEmpty(record.Username2))
{
notes.Add($"Alternative username 1: {record.Username2}");
}
if (!string.IsNullOrEmpty(record.Username3))
{
notes.Add($"Alternative username 2: {record.Username3}");
}
if (!string.IsNullOrEmpty(record.Category))
{
notes.Add($"Category: {record.Category}");
}
return notes.Count > 0 ? string.Join(Environment.NewLine, notes) : null;
}
}

View File

@@ -0,0 +1,71 @@
//-----------------------------------------------------------------------
// <copyright file="DashlaneCsvRecord.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
using AliasVault.ImportExport.Converters;
using CsvHelper.Configuration.Attributes;
namespace AliasVault.ImportExport.Models.Imports;
/// <summary>
/// Represents a Dashlane CSV record that is being imported from a Dashlane CSV export file.
/// </summary>
public class DashlaneCsvRecord
{
/// <summary>
/// Gets or sets the primary username.
/// </summary>
[Name("username")]
public string Username { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the second username.
/// </summary>
[Name("username2")]
public string? Username2 { get; set; }
/// <summary>
/// Gets or sets the third username.
/// </summary>
[Name("username3")]
public string? Username3 { get; set; }
/// <summary>
/// Gets or sets the title/service name.
/// </summary>
[Name("title")]
public string Title { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the password.
/// </summary>
[Name("password")]
public string? Password { get; set; }
/// <summary>
/// Gets or sets any additional notes.
/// </summary>
[Name("note")]
public string? Note { get; set; }
/// <summary>
/// Gets or sets the service URL.
/// </summary>
[Name("url")]
public string? URL { get; set; }
/// <summary>
/// Gets or sets the category.
/// </summary>
[Name("category")]
public string? Category { get; set; }
/// <summary>
/// Gets or sets the OTP URL.
/// </summary>
[Name("otpUrl")]
public string? OTPUrl { get; set; }
}