From 55ee3bfd4adeef83d802c8220712cd8aa9f4e072 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 24 Mar 2025 11:11:28 +0100 Subject: [PATCH] Add sample CSV import mapping logic (#542) --- .../Components/ImportServiceKeePass.razor | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) 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 d39bc89c6..03ed233b8 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor @@ -20,20 +20,71 @@ { Logger.LogInformation($"Processing KeePass file: {e.File.Name}"); - await Task.Delay(500); + var importCredentials = new List(); - // TODO: Implement actual KeePass file parsing - // For now, return a sample credential - var importCredentials = new List + try { - new() - { - ServiceName = "Sample Service", - Username = "sample_user", - Password = "sample_password", - Notes = "Imported from KeePass" + using var stream = e.File.OpenReadStream(); + using var reader = new StreamReader(stream); + + // Read and parse header row + var headerLine = await reader.ReadLineAsync(); + if (headerLine == null) { + throw new InvalidDataException("CSV file header not found"); } - }; + + var headers = headerLine.Split(',') + .Select((header, index) => (header.Trim('"'), index)) + .ToDictionary(h => h.Item1, h => h.index); + + // Validate required headers + var requiredHeaders = new[] { "Title", "Username", "Password" }; + var missingHeaders = requiredHeaders.Where(h => !headers.ContainsKey(h)).ToList(); + if (missingHeaders.Any()) + { + throw new InvalidDataException( + $"Missing required headers: {string.Join(", ", missingHeaders)}"); + } + + string? line; + while ((line = await reader.ReadLineAsync()) != null) + { + if (string.IsNullOrWhiteSpace(line)) { + continue; + } + + var values = line.Split(',').Select(v => v.Trim('"')).ToArray(); + if (values.Length < headers.Count) { + continue; + } + + // TODO: check how TOTP code should be parsed: can we store the full otpauth:// string + // or do we need to extract the secret key only? + // TODO: currently the TOTP auth component errors with full otpauth:// string. + var credential = new ImportCredential + { + ServiceName = values[headers["Title"]], + ServiceUrl = headers.ContainsKey("URL") && !string.IsNullOrEmpty(values[headers["URL"]]) + ? values[headers["URL"]] + : string.Empty, + Username = headers.ContainsKey("Email") && !string.IsNullOrEmpty(values[headers["Email"]]) + ? values[headers["Email"]] + : values[headers["Username"]], + Password = values[headers["Password"]], + TwoFactorSecret = headers.ContainsKey("OTPAuth") && !string.IsNullOrEmpty(values[headers["OTPAuth"]]) + ? values[headers["OTPAuth"]] + : string.Empty, + Notes = headers.ContainsKey("Notes") ? values[headers["Notes"]] : string.Empty + }; + + importCredentials.Add(credential); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error processing KeePass CSV file"); + throw; + } _importServiceCard.SetImportedCredentials(importCredentials); }