Logout all user sessions after password change (#1118)

This commit is contained in:
Leendert de Borst
2025-08-14 11:37:01 +02:00
committed by Leendert de Borst
parent 293501405f
commit ec060d1392
3 changed files with 26 additions and 20 deletions

View File

@@ -582,25 +582,6 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
return username.ToLowerInvariant().Trim();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="request">The HttpRequest instance for the request that the client used.</param>
/// <returns>Unique device identifier as string.</returns>
private static string GenerateDeviceIdentifier(HttpRequest request)
{
var userAgent = request.Headers.UserAgent.ToString();
var acceptLanguage = request.Headers.AcceptLanguage.ToString();
var rawIdentifier = $"{userAgent}|{acceptLanguage}";
return rawIdentifier;
}
/// <summary>
/// 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

View File

@@ -362,6 +362,12 @@ public class VaultController(ILogger<VaultController> 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 });
}

View File

@@ -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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="request">The HttpRequest instance for the request that the client used.</param>
/// <returns>Unique device identifier as string.</returns>
public static string GenerateDeviceIdentifier(HttpRequest request)
{
var userAgent = request.Headers.UserAgent.ToString();
var acceptLanguage = request.Headers.AcceptLanguage.ToString();
var rawIdentifier = $"{userAgent}|{acceptLanguage}";
return rawIdentifier;
}
}