mirror of
https://github.com/aliasvault/aliasvault.git
synced 2025-12-31 01:58:36 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab82a63a0a | ||
|
|
82376b696c | ||
|
|
0c8fc191a6 | ||
|
|
b71f0dd2c3 | ||
|
|
3617c551e3 | ||
|
|
901caa896b | ||
|
|
89534bf78e | ||
|
|
e82595162f | ||
|
|
93c439e852 | ||
|
|
ff08fae579 |
102
browser-extension/package-lock.json
generated
102
browser-extension/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
<ImportService1Password />
|
||||
<ImportServiceBitwarden />
|
||||
<ImportServiceChrome />
|
||||
<ImportServiceDashlane />
|
||||
<ImportServiceFirefox />
|
||||
<ImportServiceKeePass />
|
||||
<ImportServiceKeePassXC />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
5
src/AliasVault.Client/wwwroot/img/importers/dashlane.svg
Normal file
5
src/AliasVault.Client/wwwroot/img/importers/dashlane.svg
Normal 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 |
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,,
|
||||
|
@@ -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"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
Reference in New Issue
Block a user