From 70b7ac6f9ffb34183f7cae62a832d3a281dbf7ec Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 28 Mar 2025 14:47:24 +0100 Subject: [PATCH] Make AliasVault export/import work again (#542) --- .../Components/ImportServiceAliasVault.razor | 9 ++- .../Settings/ImportExport/ImportExport.razor | 7 +- .../Utilities/ImportExportTests.cs | 42 ++++------- .../CredentialCsvService.cs | 69 +++++++------------ .../Importers/BaseImporter.cs | 35 ++++++---- .../Models/ImportedAlias.cs | 51 ++++++++++++++ .../Models/ImportedCredential.cs | 7 +- 7 files changed, 130 insertions(+), 90 deletions(-) create mode 100644 src/Utilities/AliasVault.ImportExport/Models/ImportedAlias.cs diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor index 91e0b6559..f777a1a57 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor @@ -15,8 +15,11 @@ @code { private async Task> ProcessFile(string fileContents) { - // TODO: Implement AliasVault import. - await Task.Delay(50); - return new List(); + var importedCredentials = await Task.Run(() => + { + return AliasVault.ImportExport.CredentialCsvService.ImportCredentialsFromCsv(fileContents); + }); + + return importedCredentials; } } \ No newline at end of file diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor index 894d098a1..c30af8375 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor @@ -84,8 +84,11 @@ // Create a memory stream from the byte array. using (MemoryStream memoryStream = new MemoryStream(csvBytes)) { - // Invoke JavaScript to initiate the download. - await JsInteropService.DownloadFileFromStream("aliasvault-client.csv", memoryStream.ToArray()); + var dateStr = DateTime.UtcNow.ToString("yyyy-MM-dd"); + var username = await GetUsernameAsync(); + + // Invoke JavaScript to initiate the download with date and username in filename + await JsInteropService.DownloadFileFromStream($"aliasvault-export-{username}-{dateStr}.csv", memoryStream.ToArray()); } } catch (Exception ex) diff --git a/src/Tests/AliasVault.UnitTests/Utilities/ImportExportTests.cs b/src/Tests/AliasVault.UnitTests/Utilities/ImportExportTests.cs index edd317b3d..444fd0555 100644 --- a/src/Tests/AliasVault.UnitTests/Utilities/ImportExportTests.cs +++ b/src/Tests/AliasVault.UnitTests/Utilities/ImportExportTests.cs @@ -21,7 +21,7 @@ public class ImportExportTests /// Test case for importing credentials from CSV and ensuring all values are present. /// [Test] - public void ImportCredentialsFromCsv() + public async Task ImportCredentialsFromCsv() { // Arrange var credential = new Credential @@ -66,7 +66,7 @@ public class ImportExportTests var csvString = System.Text.Encoding.Default.GetString(csvContent); // Act - var importedCredentials = CredentialCsvService.ImportCredentialsFromCsv(csvString); + var importedCredentials = await CredentialCsvService.ImportCredentialsFromCsv(csvString); // Assert Assert.That(importedCredentials, Has.Count.EqualTo(1)); @@ -75,33 +75,21 @@ public class ImportExportTests Assert.Multiple(() => { - Assert.That(importedCredential.Id, Is.EqualTo(credential.Id)); + Assert.That(importedCredential.ServiceName, Is.EqualTo(credential.Service.Name)); + Assert.That(importedCredential.ServiceUrl, Is.EqualTo(credential.Service.Url)); Assert.That(importedCredential.Username, Is.EqualTo(credential.Username)); Assert.That(importedCredential.Notes, Is.EqualTo(credential.Notes)); - Assert.That(importedCredential.CreatedAt.ToString("yyyy-MM-dd"), Is.EqualTo(credential.CreatedAt.ToString("yyyy-MM-dd"))); - Assert.That(importedCredential.UpdatedAt.ToString("yyyy-MM-dd"), Is.EqualTo(credential.UpdatedAt.ToString("yyyy-MM-dd"))); - Assert.That(importedCredential.AliasId, Is.EqualTo(credential.AliasId)); - Assert.That(importedCredential.Alias.Id, Is.EqualTo(credential.Alias.Id)); - Assert.That(importedCredential.Alias.Gender, Is.EqualTo(credential.Alias.Gender)); - Assert.That(importedCredential.Alias.FirstName, Is.EqualTo(credential.Alias.FirstName)); - Assert.That(importedCredential.Alias.LastName, Is.EqualTo(credential.Alias.LastName)); - Assert.That(importedCredential.Alias.NickName, Is.EqualTo(credential.Alias.NickName)); - Assert.That(importedCredential.Alias.BirthDate, Is.EqualTo(credential.Alias.BirthDate)); - Assert.That(importedCredential.Alias.Email, Is.EqualTo(credential.Alias.Email)); - Assert.That(importedCredential.Alias.CreatedAt.ToString("yyyy-MM-dd"), Is.EqualTo(credential.Alias.CreatedAt.ToString("yyyy-MM-dd"))); - Assert.That(importedCredential.Alias.UpdatedAt.ToString("yyyy-MM-dd"), Is.EqualTo(credential.Alias.UpdatedAt.ToString("yyyy-MM-dd"))); - Assert.That(importedCredential.ServiceId, Is.EqualTo(credential.ServiceId)); - Assert.That(importedCredential.Service.Id, Is.EqualTo(credential.Service.Id)); - Assert.That(importedCredential.Service.Name, Is.EqualTo(credential.Service.Name)); - Assert.That(importedCredential.Service.Url, Is.EqualTo(credential.Service.Url)); - Assert.That(importedCredential.Passwords, Has.Count.EqualTo(1)); - - var importedPassword = importedCredential.Passwords.First(); - var originalPassword = credential.Passwords.First(); - - Assert.That(importedPassword.Value, Is.EqualTo(originalPassword.Value)); - Assert.That(importedPassword.CreatedAt.ToString("yyyy-MM-dd"), Is.EqualTo(originalPassword.CreatedAt.ToString("yyyy-MM-dd"))); - Assert.That(importedPassword.UpdatedAt.ToString("yyyy-MM-dd"), Is.EqualTo(originalPassword.UpdatedAt.ToString("yyyy-MM-dd"))); + Assert.That(importedCredential.CreatedAt, Is.EqualTo(credential.CreatedAt)); + Assert.That(importedCredential.UpdatedAt, Is.EqualTo(credential.UpdatedAt)); + Assert.That(importedCredential.Alias!.Gender, Is.EqualTo(credential.Alias!.Gender)); + Assert.That(importedCredential.Alias!.FirstName, Is.EqualTo(credential.Alias!.FirstName)); + Assert.That(importedCredential.Alias!.LastName, Is.EqualTo(credential.Alias!.LastName)); + Assert.That(importedCredential.Alias!.NickName, Is.EqualTo(credential.Alias!.NickName)); + Assert.That(importedCredential.Alias!.BirthDate, Is.EqualTo(credential.Alias!.BirthDate)); + Assert.That(importedCredential.Alias!.Email, Is.EqualTo(credential.Alias!.Email)); + Assert.That(importedCredential.Alias!.CreatedAt, Is.EqualTo(credential.Alias!.CreatedAt)); + Assert.That(importedCredential.Alias!.UpdatedAt, Is.EqualTo(credential.Alias!.UpdatedAt)); + Assert.That(importedCredential.Password, Is.EqualTo(credential.Passwords.First().Value)); }); } diff --git a/src/Utilities/AliasVault.ImportExport/CredentialCsvService.cs b/src/Utilities/AliasVault.ImportExport/CredentialCsvService.cs index 2d4c2ae40..69804f8f3 100644 --- a/src/Utilities/AliasVault.ImportExport/CredentialCsvService.cs +++ b/src/Utilities/AliasVault.ImportExport/CredentialCsvService.cs @@ -8,6 +8,7 @@ namespace AliasVault.ImportExport; using AliasClientDb; +using AliasVault.ImportExport.Models; using CsvHelper; using CsvHelper.Configuration; using System.Globalization; @@ -17,7 +18,7 @@ using System.Globalization; /// public static class CredentialCsvService { - private const string CsvVersionIdentifier = "1.0.0"; + private const string CsvVersionIdentifier = "1.5.0"; /// /// Export list of credentials to CSV file. @@ -33,24 +34,20 @@ public static class CredentialCsvService var record = new CredentialCsvRecord { Version = CsvVersionIdentifier, - Id = credential.Id, Username = credential.Username ?? string.Empty, Notes = credential.Notes ?? string.Empty, CreatedAt = credential.CreatedAt, UpdatedAt = credential.UpdatedAt, - AliasId = credential.AliasId, AliasGender = credential.Alias?.Gender ?? string.Empty, AliasFirstName = credential.Alias?.FirstName ?? string.Empty, AliasLastName = credential.Alias?.LastName ?? string.Empty, AliasNickName = credential.Alias?.NickName ?? string.Empty, AliasBirthDate = credential.Alias?.BirthDate, AliasEmail = credential.Alias?.Email ?? string.Empty, - AliasCreatedAt = credential.Alias?.CreatedAt, - AliasUpdatedAt = credential.Alias?.UpdatedAt, - ServiceId = credential.ServiceId, ServiceName = credential.Service?.Name ?? string.Empty, ServiceUrl = credential.Service?.Url ?? string.Empty, - CurrentPassword = credential.Passwords.OrderByDescending(p => p.CreatedAt).FirstOrDefault()?.Value ?? string.Empty + CurrentPassword = credential.Passwords.OrderByDescending(p => p.CreatedAt).FirstOrDefault()?.Value ?? string.Empty, + TwoFactorSecret = credential.TotpCodes.FirstOrDefault()?.SecretKey ?? string.Empty }; records.Add(record); @@ -69,13 +66,17 @@ public static class CredentialCsvService /// Imports Credential objects from a CSV file. /// /// The content of the CSV file. - /// The imported list of Credential objects. - public static List ImportCredentialsFromCsv(string fileContent) + /// The imported list of ImportedCredential objects. + public static async Task> ImportCredentialsFromCsv(string fileContent) { using var reader = new StringReader(fileContent); using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture)); - var records = csv.GetRecords().ToList(); + var records = new List(); + await foreach (var record in csv.GetRecordsAsync()) + { + records.Add(record); + } if (records.Count == 0) { @@ -87,45 +88,31 @@ public static class CredentialCsvService throw new InvalidOperationException("Invalid CSV file version."); } - var credentials = new List(); + var credentials = new List(); foreach (var record in records) { - var credential = new Credential + var credential = new ImportedCredential { - Id = record.Id, + ServiceName = record.ServiceName, + ServiceUrl = record.ServiceUrl, Username = record.Username, + Password = record.CurrentPassword, + Email = record.AliasEmail, Notes = record.Notes, - CreatedAt = record.CreatedAt, - UpdatedAt = record.UpdatedAt, - AliasId = record.AliasId, - Alias = new Alias + Alias = new ImportedAlias { - Id = record.AliasId, Gender = record.AliasGender, FirstName = record.AliasFirstName, LastName = record.AliasLastName, NickName = record.AliasNickName, - BirthDate = record.AliasBirthDate ?? DateTime.MinValue, - Email = record.AliasEmail, - CreatedAt = record.AliasCreatedAt ?? DateTime.UtcNow, - UpdatedAt = record.AliasUpdatedAt ?? DateTime.UtcNow + BirthDate = record.AliasBirthDate, + CreatedAt = record.CreatedAt, + UpdatedAt = record.UpdatedAt }, - ServiceId = record.ServiceId, - Service = new Service - { - Id = record.ServiceId, - Name = record.ServiceName, - Url = record.ServiceUrl - }, - Passwords = [ - new Password - { - Value = record.CurrentPassword, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow - } - ] + TwoFactorSecret = record.TwoFactorSecret, + CreatedAt = record.CreatedAt, + UpdatedAt = record.UpdatedAt, }; credentials.Add(credential); @@ -140,23 +127,19 @@ public static class CredentialCsvService /// public class CredentialCsvRecord { - public string Version { get; set; } = "1.0.0"; - public Guid Id { get; set; } = Guid.Empty; + public string Version { get; set; } = "1.5.0"; public string Username { get; set; } = string.Empty; public string Notes { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } = DateTime.MinValue; public DateTime UpdatedAt { get; set; } = DateTime.MinValue; - public Guid AliasId { get; set; } = Guid.Empty; public string AliasGender { get; set; } = string.Empty; public string AliasFirstName { get; set; } = string.Empty; public string AliasLastName { get; set; } = string.Empty; public string AliasNickName { get; set; } = string.Empty; public DateTime? AliasBirthDate { get; set; } = null; public string AliasEmail { get; set; } = string.Empty; - public DateTime? AliasCreatedAt { get; set; } = null; - public DateTime? AliasUpdatedAt { get; set; } = null; - public Guid ServiceId { get; set; } = Guid.Empty; public string ServiceName { get; set; } = string.Empty; public string ServiceUrl { get; set; } = string.Empty; public string CurrentPassword { get; set; } = string.Empty; + public string TwoFactorSecret { get; set; } = string.Empty; } diff --git a/src/Utilities/AliasVault.ImportExport/Importers/BaseImporter.cs b/src/Utilities/AliasVault.ImportExport/Importers/BaseImporter.cs index 90e40400f..8459fe6d0 100644 --- a/src/Utilities/AliasVault.ImportExport/Importers/BaseImporter.cs +++ b/src/Utilities/AliasVault.ImportExport/Importers/BaseImporter.cs @@ -31,28 +31,35 @@ public class BaseImporter { Service = new Service { Name = importedCredential.ServiceName, Url = importedCredential.ServiceUrl }, Username = importedCredential.Username, - Passwords = new List { new() { Value = importedCredential.Password } }, + Passwords = [new() { Value = importedCredential.Password }], Notes = importedCredential.Notes, CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow, - UpdatedAt = importedCredential.ModifiedAt ?? DateTime.UtcNow + UpdatedAt = importedCredential.UpdatedAt ?? DateTime.UtcNow, + Alias = new Alias + { + FirstName = importedCredential.Alias?.FirstName, + LastName = importedCredential.Alias?.LastName, + Gender = importedCredential.Alias?.Gender, + // TODO: birth date should be made nullable in client DB as it's not always available. + BirthDate = importedCredential.Alias?.BirthDate ?? DateTime.MinValue, + Email = importedCredential.Email, + NickName = importedCredential.Alias?.NickName, + CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow, + UpdatedAt = importedCredential.UpdatedAt ?? DateTime.UtcNow, + } }; - credential.Alias = new Alias - { - CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow, - UpdatedAt = importedCredential.ModifiedAt ?? DateTime.UtcNow, - }; - - if (!string.IsNullOrEmpty(importedCredential.Email)) - { - credential.Alias = new Alias { Email = importedCredential.Email }; - } - if (!string.IsNullOrEmpty(importedCredential.TwoFactorSecret)) { credential.TotpCodes = new List { - new() { SecretKey = importedCredential.TwoFactorSecret } + new() + { + Name = "Authenticator", + SecretKey = importedCredential.TwoFactorSecret, + CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow, + UpdatedAt = importedCredential.UpdatedAt ?? DateTime.UtcNow, + } }; } diff --git a/src/Utilities/AliasVault.ImportExport/Models/ImportedAlias.cs b/src/Utilities/AliasVault.ImportExport/Models/ImportedAlias.cs new file mode 100644 index 000000000..de27e3fef --- /dev/null +++ b/src/Utilities/AliasVault.ImportExport/Models/ImportedAlias.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.ImportExport.Models; + +/// +/// Represents an alias in an intermediary format that is imported from various sources. +/// This model is designed to be flexible enough to handle different import formats while +/// maintaining all the essential fields needed for AliasVault aliases. +/// +public class ImportedAlias +{ + /// + /// Gets or sets the gender. + /// + public string? Gender { get; set; } + + /// + /// Gets or sets the first name. + /// + public string? FirstName { get; set; } + + /// + /// Gets or sets the last name. + /// + public string? LastName { get; set; } + + /// + /// Gets or sets the nickname. + /// + public string? NickName { get; set; } + + /// + /// Gets or sets the birth date. + /// + public DateTime? BirthDate { get; set; } + + /// + /// Gets or sets the creation date. + /// + public DateTime? CreatedAt { get; set; } + + /// + /// Gets or sets the last update date. + /// + public DateTime? UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Utilities/AliasVault.ImportExport/Models/ImportedCredential.cs b/src/Utilities/AliasVault.ImportExport/Models/ImportedCredential.cs index 96d240f31..c9807d1e4 100644 --- a/src/Utilities/AliasVault.ImportExport/Models/ImportedCredential.cs +++ b/src/Utilities/AliasVault.ImportExport/Models/ImportedCredential.cs @@ -57,10 +57,15 @@ public class ImportedCredential /// /// Gets or sets the last modified date of the credential. /// - public DateTime? ModifiedAt { get; set; } + public DateTime? UpdatedAt { get; set; } /// /// Gets or sets the favicon bytes. /// public byte[]? FaviconBytes { get; set; } + + /// + /// Gets or sets the alias information. + /// + public ImportedAlias? Alias { get; set; } }