diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor index c569fb0de..3b5c6fdb4 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor @@ -1,3 +1,4 @@ +@using AliasVault.ImportExport.Importers @inject ILogger Logger + @ref="_importServiceCard">
-

Upload your Bitwarden JSON export file:

+

Upload your Bitwarden CSV export file:

@code { + private ImportServiceCard _importServiceCard = null!; + private async Task HandleFileUpload(InputFileChangeEventArgs e) { // Bitwarden specific file upload and parsing logic - await Task.Delay(500); - Logger.LogInformation($"Processing Bitwarden file: {e.File.Name}"); + try + { + await using var stream = e.File.OpenReadStream(); + using var reader = new StreamReader(stream); + var fileContents = await reader.ReadToEndAsync(); + + var importCredentials = await BitwardenImporter.ImportFromCsvAsync(fileContents); + _importServiceCard.SetImportedCredentials(importCredentials); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error processing KeePass CSV file"); + throw; + } } private void RefreshVault() @@ -25,4 +40,4 @@ // Refresh the vault // ... } -} \ No newline at end of file +} diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor index 26a912e21..9bcef8d4d 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor @@ -22,7 +22,7 @@ try { - using var stream = e.File.OpenReadStream(); + await using var stream = e.File.OpenReadStream(); using var reader = new StreamReader(stream); var fileContents = await reader.ReadToEndAsync(); diff --git a/src/Utilities/AliasVault.ImportExport/Importers/BitwardenImporter.cs b/src/Utilities/AliasVault.ImportExport/Importers/BitwardenImporter.cs new file mode 100644 index 000000000..fb25cf700 --- /dev/null +++ b/src/Utilities/AliasVault.ImportExport/Importers/BitwardenImporter.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// +// 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.Importers; + +using AliasVault.ImportExport.Models; +using AliasVault.ImportExport.Models.Imports; +using CsvHelper; +using CsvHelper.Configuration; +using System.Globalization; + +/// +/// Imports credentials from Bitwarden. +/// +public class BitwardenImporter +{ + /// + /// Imports Bitwarden CSV file and converts contents to list of ImportedCredential model objects. + /// + /// The content of the CSV file. + /// The imported list of ImportedCredential objects. + public static async Task> ImportFromCsvAsync(string fileContent) + { + using var reader = new StringReader(fileContent); + using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture)); + + var credentials = new List(); + await foreach (var record in csv.GetRecordsAsync()) + { + // TODO: OTPAuth should be converted from URL to a secret key during import or during + // the actual creation of the imported credential object. + var credential = new ImportedCredential + { + ServiceName = record.Title, + ServiceUrl = record.URL, + Username = record.Username, + Password = record.Password, + TwoFactorSecret = record.OTPAuth, + Notes = record.Notes + }; + + credentials.Add(credential); + } + + if (credentials.Count == 0) + { + throw new InvalidOperationException("No records found in the CSV file."); + } + + return credentials; + } +} \ No newline at end of file diff --git a/src/Utilities/AliasVault.ImportExport/Models/Imports/BitwardenCsvRecord.cs b/src/Utilities/AliasVault.ImportExport/Models/Imports/BitwardenCsvRecord.cs new file mode 100644 index 000000000..519f8ef69 --- /dev/null +++ b/src/Utilities/AliasVault.ImportExport/Models/Imports/BitwardenCsvRecord.cs @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +using CsvHelper.Configuration.Attributes; + +namespace AliasVault.ImportExport.Models.Imports; + +/// +/// Represents a Bitwarden CSV record that is being imported from a Bitwarden CSV export file. +/// +public class BitwardenCsvRecord +{ + /// + /// Gets or sets the folder name. + /// + [Name("folder")] + public string Folder { get; set; } = string.Empty; + + /// + /// Gets or sets whether the item is favorited. + /// + [Name("favorite")] + public bool Favorite { get; set; } + + /// + /// Gets or sets the type of the item. + /// + [Name("type")] + public string Type { get; set; } = string.Empty; + + /// + /// Gets or sets the title/service name (e.g., "Facebook", "Gmail"). + /// + [Name("name")] + public string Title { get; set; } = string.Empty; + + /// + /// Gets or sets any additional notes. + /// + [Name("notes")] + public string? Notes { get; set; } + + /// + /// Gets or sets custom fields. + /// + [Name("fields")] + public string? Fields { get; set; } + + /// + /// Gets or sets whether to reprompt for the master password. + /// + [Name("reprompt")] + public bool Reprompt { get; set; } + + /// + /// Gets or sets the service URL. + /// + [Name("login_uri")] + public string? URL { get; set; } + + /// + /// Gets or sets the username. + /// + [Name("login_username")] + public string? Username { get; set; } + + /// + /// Gets or sets the password. + /// + [Name("login_password")] + public string? Password { get; set; } + + /// + /// Gets or sets the OTP (One-Time Password) authentication secret. + /// + [Name("login_totp")] + public string? OTPAuth { get; set; } +}