Make AliasVault export/import work again (#542)

This commit is contained in:
Leendert de Borst
2025-03-28 14:47:24 +01:00
committed by Leendert de Borst
parent 14ee466bec
commit 70b7ac6f9f
7 changed files with 130 additions and 90 deletions

View File

@@ -15,8 +15,11 @@
@code {
private async Task<List<ImportedCredential>> ProcessFile(string fileContents)
{
// TODO: Implement AliasVault import.
await Task.Delay(50);
return new List<ImportedCredential>();
var importedCredentials = await Task.Run(() =>
{
return AliasVault.ImportExport.CredentialCsvService.ImportCredentialsFromCsv(fileContents);
});
return importedCredentials;
}
}

View File

@@ -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)

View File

@@ -21,7 +21,7 @@ public class ImportExportTests
/// Test case for importing credentials from CSV and ensuring all values are present.
/// </summary>
[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));
});
}

View File

@@ -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;
/// </summary>
public static class CredentialCsvService
{
private const string CsvVersionIdentifier = "1.0.0";
private const string CsvVersionIdentifier = "1.5.0";
/// <summary>
/// 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.
/// </summary>
/// <param name="fileContent">The content of the CSV file.</param>
/// <returns>The imported list of Credential objects.</returns>
public static List<Credential> ImportCredentialsFromCsv(string fileContent)
/// <returns>The imported list of ImportedCredential objects.</returns>
public static async Task<List<ImportedCredential>> ImportCredentialsFromCsv(string fileContent)
{
using var reader = new StringReader(fileContent);
using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture));
var records = csv.GetRecords<CredentialCsvRecord>().ToList();
var records = new List<CredentialCsvRecord>();
await foreach (var record in csv.GetRecordsAsync<CredentialCsvRecord>())
{
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<Credential>();
var credentials = new List<ImportedCredential>();
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
/// </summary>
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;
}

View File

@@ -31,28 +31,35 @@ public class BaseImporter
{
Service = new Service { Name = importedCredential.ServiceName, Url = importedCredential.ServiceUrl },
Username = importedCredential.Username,
Passwords = new List<Password> { 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<TotpCode>
{
new() { SecretKey = importedCredential.TwoFactorSecret }
new()
{
Name = "Authenticator",
SecretKey = importedCredential.TwoFactorSecret,
CreatedAt = importedCredential.CreatedAt ?? DateTime.UtcNow,
UpdatedAt = importedCredential.UpdatedAt ?? DateTime.UtcNow,
}
};
}

View File

@@ -0,0 +1,51 @@
//-----------------------------------------------------------------------
// <copyright file="ImportedAlias.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.Models;
/// <summary>
/// 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.
/// </summary>
public class ImportedAlias
{
/// <summary>
/// Gets or sets the gender.
/// </summary>
public string? Gender { get; set; }
/// <summary>
/// Gets or sets the first name.
/// </summary>
public string? FirstName { get; set; }
/// <summary>
/// Gets or sets the last name.
/// </summary>
public string? LastName { get; set; }
/// <summary>
/// Gets or sets the nickname.
/// </summary>
public string? NickName { get; set; }
/// <summary>
/// Gets or sets the birth date.
/// </summary>
public DateTime? BirthDate { get; set; }
/// <summary>
/// Gets or sets the creation date.
/// </summary>
public DateTime? CreatedAt { get; set; }
/// <summary>
/// Gets or sets the last update date.
/// </summary>
public DateTime? UpdatedAt { get; set; }
}

View File

@@ -57,10 +57,15 @@ public class ImportedCredential
/// <summary>
/// Gets or sets the last modified date of the credential.
/// </summary>
public DateTime? ModifiedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
/// <summary>
/// Gets or sets the favicon bytes.
/// </summary>
public byte[]? FaviconBytes { get; set; }
/// <summary>
/// Gets or sets the alias information.
/// </summary>
public ImportedAlias? Alias { get; set; }
}