Add Bitwarden importer scaffolding (#542)

This commit is contained in:
Leendert de Borst
2025-03-24 17:03:02 +01:00
committed by Leendert de Borst
parent e93b0575ff
commit 634fc281a2
4 changed files with 159 additions and 6 deletions

View File

@@ -1,3 +1,4 @@
@using AliasVault.ImportExport.Importers
@inject ILogger<ImportServiceBitwarden> Logger
<ImportServiceCard
@@ -5,19 +6,33 @@
Description="Import passwords from your Bitwarden vault"
LogoUrl="img/importers/bitwarden.svg"
OnImportComplete="RefreshVault"
>
@ref="_importServiceCard">
<div class="mb-4">
<p class="mb-4 text-gray-700 dark:text-gray-300">Upload your Bitwarden JSON export file:</p>
<p class="mb-4 text-gray-700 dark:text-gray-300">Upload your Bitwarden CSV export file:</p>
</div>
<InputFile OnChange="HandleFileUpload" />
</ImportServiceCard>
@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
// ...
}
}
}

View File

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

View File

@@ -0,0 +1,56 @@
//-----------------------------------------------------------------------
// <copyright file="BitwardenImporter.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 Bitwarden.
/// </summary>
public class BitwardenImporter
{
/// <summary>
/// Imports Bitwarden 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<BitwardenCsvRecord>())
{
// 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;
}
}

View File

@@ -0,0 +1,82 @@
//-----------------------------------------------------------------------
// <copyright file="BitwardenCsvRecord.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 CsvHelper.Configuration.Attributes;
namespace AliasVault.ImportExport.Models.Imports;
/// <summary>
/// Represents a Bitwarden CSV record that is being imported from a Bitwarden CSV export file.
/// </summary>
public class BitwardenCsvRecord
{
/// <summary>
/// Gets or sets the folder name.
/// </summary>
[Name("folder")]
public string Folder { get; set; } = string.Empty;
/// <summary>
/// Gets or sets whether the item is favorited.
/// </summary>
[Name("favorite")]
public bool Favorite { get; set; }
/// <summary>
/// Gets or sets the type of the item.
/// </summary>
[Name("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the title/service name (e.g., "Facebook", "Gmail").
/// </summary>
[Name("name")]
public string Title { get; set; } = string.Empty;
/// <summary>
/// Gets or sets any additional notes.
/// </summary>
[Name("notes")]
public string? Notes { get; set; }
/// <summary>
/// Gets or sets custom fields.
/// </summary>
[Name("fields")]
public string? Fields { get; set; }
/// <summary>
/// Gets or sets whether to reprompt for the master password.
/// </summary>
[Name("reprompt")]
public bool Reprompt { get; set; }
/// <summary>
/// Gets or sets the service URL.
/// </summary>
[Name("login_uri")]
public string? URL { get; set; }
/// <summary>
/// Gets or sets the username.
/// </summary>
[Name("login_username")]
public string? Username { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
[Name("login_password")]
public string? Password { get; set; }
/// <summary>
/// Gets or sets the OTP (One-Time Password) authentication secret.
/// </summary>
[Name("login_totp")]
public string? OTPAuth { get; set; }
}