diff --git a/apps/server/AliasVault.Api/Controllers/AuthController.cs b/apps/server/AliasVault.Api/Controllers/AuthController.cs index 0132fa9d5..7f6a6ae23 100644 --- a/apps/server/AliasVault.Api/Controllers/AuthController.cs +++ b/apps/server/AliasVault.Api/Controllers/AuthController.cs @@ -582,25 +582,6 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM return username.ToLowerInvariant().Trim(); } - /// - /// Generate a device identifier based on request headers. This is used to associate refresh tokens - /// with a specific device for a specific user. - /// - /// NOTE: current implementation means that only one refresh token can be valid for a - /// specific user/device combo at a time. The identifier generation could be made more unique in the future - /// to prevent any unwanted conflicts. - /// - /// The HttpRequest instance for the request that the client used. - /// Unique device identifier as string. - private static string GenerateDeviceIdentifier(HttpRequest request) - { - var userAgent = request.Headers.UserAgent.ToString(); - var acceptLanguage = request.Headers.AcceptLanguage.ToString(); - - var rawIdentifier = $"{userAgent}|{acceptLanguage}"; - return rawIdentifier; - } - /// /// Generate a refresh token for a user. This token is used to request a new access token when the current /// access token expires. The refresh token is long-lived by design. @@ -888,7 +869,7 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM // Generate device identifier var accessToken = GenerateJwtToken(user); var refreshToken = GenerateRefreshToken(); - var deviceIdentifier = GenerateDeviceIdentifier(Request); + var deviceIdentifier = AuthHelper.GenerateDeviceIdentifier(Request); // Add new refresh token. context.AliasVaultUserRefreshTokens.Add(new AliasVaultUserRefreshToken diff --git a/apps/server/AliasVault.Api/Controllers/VaultController.cs b/apps/server/AliasVault.Api/Controllers/VaultController.cs index ed7320946..a89ccc7b9 100644 --- a/apps/server/AliasVault.Api/Controllers/VaultController.cs +++ b/apps/server/AliasVault.Api/Controllers/VaultController.cs @@ -362,6 +362,12 @@ public class VaultController(ILogger logger, IAliasServerDbCont await authLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.PasswordChange); + // Force revoke all user logged in sessions except current one. + // This means that other clients which have not already updated to the new password will be logged out. + // This ensures that all clients login again with the new password to refresh their encryption keys for future vault mutations. + var deviceIdentifier = AuthHelper.GenerateDeviceIdentifier(Request); + await context.AliasVaultUserRefreshTokens.Where(x => x.UserId == user.Id && x.DeviceIdentifier != deviceIdentifier).ExecuteDeleteAsync(); + return Ok(new VaultUpdateResponse { Status = VaultStatus.Ok, NewRevisionNumber = newRevisionNumber }); } diff --git a/apps/server/AliasVault.Api/Helpers/AuthHelper.cs b/apps/server/AliasVault.Api/Helpers/AuthHelper.cs index 6c4fb15df..1e6016ee1 100644 --- a/apps/server/AliasVault.Api/Helpers/AuthHelper.cs +++ b/apps/server/AliasVault.Api/Helpers/AuthHelper.cs @@ -72,4 +72,23 @@ public static class AuthHelper var latestVault = user.Vaults.OrderByDescending(x => x.RevisionNumber).Select(x => new { x.Salt, x.Verifier, x.EncryptionType, x.EncryptionSettings }).First(); return (latestVault.Salt, latestVault.Verifier, latestVault.EncryptionType, latestVault.EncryptionSettings); } + + /// + /// Generate a device identifier based on request headers. This is used to associate refresh tokens + /// with a specific device for a specific user. + /// + /// NOTE: current implementation means that only one refresh token can be valid for a + /// specific user/device combo at a time. The identifier generation could be made more unique in the future + /// to prevent any unwanted conflicts. + /// + /// The HttpRequest instance for the request that the client used. + /// Unique device identifier as string. + public static string GenerateDeviceIdentifier(HttpRequest request) + { + var userAgent = request.Headers.UserAgent.ToString(); + var acceptLanguage = request.Headers.AcceptLanguage.ToString(); + + var rawIdentifier = $"{userAgent}|{acceptLanguage}"; + return rawIdentifier; + } }