From 8bd05b5c2ece88ea5fe08cc75ffd24602339ebc4 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 13 Dec 2024 11:50:18 +0100 Subject: [PATCH] Fix account registration username capitalization login bug (#460) --- .../Tests/Client/Shard4/AuthTests.cs | 81 ++++++++++++++++++- .../AliasVault.Cryptography.Client/Srp.cs | 13 ++- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs index c316f097..bd88a206 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs @@ -68,11 +68,49 @@ public async Task LogoutAndLoginTest() } /// - /// Test if logging out and logging in works. + /// Test if logging in with different case variations of username works. /// /// Async task. [Test] [Order(3)] + public async Task CapitalizedUsernameTest() + { + // Logout current user + await Logout(); + + // Create a new user with capital letters in username + var capitalUsername = "TestUser@Example.com"; + await Register(checkForSuccess: true, username: capitalUsername); + await Logout(); + + // Test Case 1: Try to login with lowercase version of the username + var lowercaseUsername = capitalUsername.ToLower(); + await LoginWithUsername(lowercaseUsername); + await VerifySuccessfulLogin(); + + // Test Case 2: Try to login with exact capitalized username + await Logout(); + await LoginWithUsername(capitalUsername); + await VerifySuccessfulLogin(); + + // Test Case 3: Create new user with lowercase + await Logout(); + var lowercaseUser = "testuser2@example.com"; + await Register(checkForSuccess: true, username: lowercaseUser); + await Logout(); + + // Try logging in with uppercase version + var uppercaseVersion = lowercaseUser.ToUpper(); + await LoginWithUsername(uppercaseVersion); + await VerifySuccessfulLogin(); + } + + /// + /// Test if logging out and logging in works. + /// + /// Async task. + [Test] + [Order(4)] public async Task LogoutAndLoginRememberMeTest() { await Logout(); @@ -101,7 +139,7 @@ public async Task LogoutAndLoginRememberMeTest() /// /// Async task. [Test] - [Order(4)] + [Order(5)] public async Task RegisterFormWarningTest() { await Logout(); @@ -116,7 +154,7 @@ public async Task RegisterFormWarningTest() /// /// Async task. [Test] - [Order(5)] + [Order(6)] public async Task PasswordAuthLockoutTest() { await Logout(); @@ -152,4 +190,41 @@ public async Task PasswordAuthLockoutTest() var pageContent = await Page.TextContentAsync("body"); Assert.That(pageContent, Does.Contain("locked out"), "No account lockout message."); } + + /// + /// Login with a given username. + /// + /// The username to login with. + /// Async task. + private async Task LoginWithUsername(string username) + { + await NavigateToLogin(); + + var emailField = await WaitForAndGetElement("input[id='email']"); + var passwordField = await WaitForAndGetElement("input[id='password']"); + await emailField.FillAsync(username); + await passwordField.FillAsync(TestUserPassword); + + var loginButton = await WaitForAndGetElement("button[type='submit']"); + await loginButton.ClickAsync(); + } + + /// + /// Verify that a login was successful. + /// + /// Async task. + private async Task VerifySuccessfulLogin() + { + // Wait for the index page to load which should show "Credentials" in the top menu. + await WaitForUrlAsync("**", "Credentials"); + + // Check if the login was successful by verifying content. + var pageContent = await Page.TextContentAsync("body"); + Assert.That(pageContent, Does.Contain(WelcomeMessage), "No index content after logging in."); + + // Check if login has created an auth log entry. + var authLogEntry = await ApiDbContext.AuthLogs.FirstOrDefaultAsync(x => + x.EventType == AuthEventType.Login); + Assert.That(authLogEntry, Is.Not.Null, "Auth log entry not found in database after login."); + } } diff --git a/src/Utilities/Cryptography/AliasVault.Cryptography.Client/Srp.cs b/src/Utilities/Cryptography/AliasVault.Cryptography.Client/Srp.cs index 6f0b6646..1e92711f 100644 --- a/src/Utilities/Cryptography/AliasVault.Cryptography.Client/Srp.cs +++ b/src/Utilities/Cryptography/AliasVault.Cryptography.Client/Srp.cs @@ -26,7 +26,9 @@ public static class Srp /// SrpSignup model. public static SrpPasswordChange PasswordChangeAsync(SrpClient client, string salt, string username, string passwordHashString) { - // Derive a key from the password using Argon2id + // Derive a key from the password using Argon2id. + // Make sure the username is lowercase as the SRP protocol is case sensitive. + username = username.ToLowerInvariant(); // Signup or password change: client generates a salt and verifier. var privateKey = DerivePrivateKey(salt, username, passwordHashString); @@ -44,6 +46,9 @@ public static SrpPasswordChange PasswordChangeAsync(SrpClient client, string sal /// Private key as string. public static string DerivePrivateKey(string salt, string username, string passwordHashString) { + // Make sure the username is lowercase as the SRP protocol is case sensitive. + username = username.ToLowerInvariant(); + var client = new SrpClient(); return client.DerivePrivateKey(salt, username, passwordHashString); } @@ -80,6 +85,9 @@ public static SrpEphemeral GenerateEphemeralServer(string verifier) /// session. public static SrpSession DeriveSessionClient(string privateKey, string clientSecretEphemeral, string serverEphemeralPublic, string salt, string username) { + // Make sure the username is lowercase as the SRP protocol is case sensitive. + username = username.ToLowerInvariant(); + var client = new SrpClient(); return client.DeriveSession( clientSecretEphemeral, @@ -101,6 +109,9 @@ public static SrpSession DeriveSessionClient(string privateKey, string clientSec /// SrpSession. public static SrpSession? DeriveSessionServer(string serverEphemeralSecret, string clientEphemeralPublic, string salt, string username, string verifier, string clientSessionProof) { + // Make sure the username is lowercase as the SRP protocol is case sensitive. + username = username.ToLowerInvariant(); + try { var server = new SrpServer();