Add TOTP urldecode on import (#773)

This commit is contained in:
Leendert de Borst
2026-03-18 10:03:57 +01:00
parent 4ddd5512aa
commit 2a9bba79da
2 changed files with 27 additions and 5 deletions

View File

@@ -117,7 +117,7 @@ public class ImportExportTests
Assert.That(aliasVaultCredential.Password, Is.EqualTo("toor"));
});
// Test entry with multiple URLs (TutaNota3)
// Test entry with multiple URLs (TutaNota3) and URL-encoded TOTP URI
var multiUrlCredential = importedCredentials.First(c => c.ServiceName == "TutaNota3");
Assert.Multiple(() =>
{
@@ -127,6 +127,7 @@ public class ImportExportTests
Assert.That(multiUrlCredential.ServiceUrls[1], Is.EqualTo("https://app.aliasvault.net"));
Assert.That(multiUrlCredential.ServiceUrls[2], Is.EqualTo("https://downloads.aliasvault.net"));
Assert.That(multiUrlCredential.Username, Is.EqualTo("avtest3@tutamail.com"));
Assert.That(multiUrlCredential.TwoFactorSecret, Is.EqualTo("otpauth://totp/Test%20name%3Atest%40test.org?secret=PLW4SB3PQ7MKVXY2MXF4NEXS6Y&issuer=Alias%20Vault"));
});
// Verify multiple URLs get converted to multiple FieldValues
@@ -142,6 +143,15 @@ public class ImportExportTests
Assert.That(urlFieldValues[1].Weight, Is.EqualTo(1));
Assert.That(urlFieldValues[2].Weight, Is.EqualTo(2));
});
// Verify TOTP code name is properly URL-decoded when converting from otpauth URI
var multiUrlItemTotpCode = multiUrlItem.TotpCodes.FirstOrDefault();
Assert.That(multiUrlItemTotpCode, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(multiUrlItemTotpCode!.SecretKey, Is.EqualTo("PLW4SB3PQ7MKVXY2MXF4NEXS6Y"));
Assert.That(multiUrlItemTotpCode.Name, Is.EqualTo("Alias Vault: Test name:test@test.org"), "TOTP name should be URL-decoded from the otpauth URI");
});
}
/// <summary>

View File

@@ -35,13 +35,25 @@ public static class TotpHelper
if (string.IsNullOrWhiteSpace(name))
{
// The label is everything after 'totp/' and before '?'
var label = uri.AbsolutePath.TrimStart('/');
var label = System.Web.HttpUtility.UrlDecode(uri.AbsolutePath.TrimStart('/'));
// If the label contains ':', take the part after it
name = label.Contains(':') ? label.Split(':')[1] : label;
// Check if there's an issuer in the query params
var issuer = queryParams["issuer"];
// If the label contains ':', it might be in the format "issuer:account"
// Only split if there's no issuer in query params (to avoid splitting account names with colons)
if (label.Contains(':') && string.IsNullOrWhiteSpace(issuer))
{
// Split on the first colon only
var colonIndex = label.IndexOf(':');
name = label.Substring(colonIndex + 1);
}
else
{
name = label;
}
// If there's an issuer in the query params, use it as a prefix
var issuer = queryParams["issuer"];
if (!string.IsNullOrWhiteSpace(issuer))
{
name = $"{issuer}: {name}";