Fix refresh token revoke bug (#363)

This commit is contained in:
Leendert de Borst
2024-11-21 19:26:16 +01:00
parent ea7aba4ff4
commit b4b2dc3fe7
3 changed files with 23 additions and 11 deletions

View File

@@ -137,8 +137,6 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
return Ok(new ValidateLoginResponse(true, string.Empty, null));
}
// If 2FA is not required, then it means the user is successfully authenticated at this point.
// Reset failed login attempts.
await userManager.ResetAccessFailedCountAsync(user);
@@ -284,6 +282,12 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
{
await using var context = await dbContextFactory.CreateDbContextAsync();
// If the token is not provided, return bad request.
if (string.IsNullOrWhiteSpace(model.RefreshToken))
{
return BadRequest("Refresh token is required.");
}
var principal = GetPrincipalFromToken(model.Token);
if (principal.FindFirst(ClaimTypes.NameIdentifier)?.Value == null)
{
@@ -297,16 +301,18 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
}
// Check if the refresh token is valid.
var deviceIdentifier = GenerateDeviceIdentifier(Request);
var existingToken = await context.AliasVaultUserRefreshTokens.FirstOrDefaultAsync(t => t.UserId == user.Id && t.DeviceIdentifier == deviceIdentifier);
if (existingToken == null || existingToken.Value != model.RefreshToken)
var providedTokenExists = await context.AliasVaultUserRefreshTokens.AnyAsync(t => t.UserId == user.Id && t.Value == model.RefreshToken);
if (!providedTokenExists)
{
await authLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.Logout, AuthFailureReason.InvalidRefreshToken);
return Unauthorized("Invalid refresh token");
}
// Remove the existing refresh token.
context.AliasVaultUserRefreshTokens.Remove(existingToken);
// Remove the provided refresh token and any other existing refresh tokens that are issued to the current device ID.
// This to make sure all tokens are revoked for this device that user is "logging out" from.
var deviceIdentifier = GenerateDeviceIdentifier(Request);
var allDeviceTokens = await context.AliasVaultUserRefreshTokens.Where(t => t.UserId == user.Id && (t.Value == model.RefreshToken || t.DeviceIdentifier == deviceIdentifier)).ToListAsync();
context.AliasVaultUserRefreshTokens.RemoveRange(allDeviceTokens);
await context.SaveChangesAsync();
await authLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.Logout);
@@ -705,8 +711,14 @@ public class AuthController(IDbContextFactory<AliasServerDbContext> dbContextFac
// New refresh token lifetime is the same as the existing one.
var existingTokenLifetime = existingToken.ExpireDate - existingToken.CreatedAt;
// Retrieve new refresh token.
var newRefreshToken = await GenerateRefreshToken(user, existingTokenLifetime, existingToken.Value);
// After successfully retrieving new refresh token, remove the existing one by saving changes.
await context.SaveChangesAsync();
// Return new refresh token.
return await GenerateRefreshToken(user, existingTokenLifetime, existingToken.Value);
return newRefreshToken;
}
finally
{

View File

@@ -85,7 +85,7 @@
/// </summary>
public async Task DisableWebAuthn()
{
await AuthService.SetWebAuthnEnabledAsync(false, string.Empty, string.Empty, string.Empty);
await AuthService.SetWebAuthnEnabledAsync(false);
GlobalNotificationService.AddSuccessMessage("Quick Vault Unlock is successfully disabled.", true);
await LoadData();
}

View File

@@ -194,7 +194,7 @@ public sealed class AuthService(HttpClient httpClient, ILocalStorageService loca
/// <param name="webauthSalt">WebAuthn salt.</param>
/// <param name="webauthCredentialDerivedKey">WebAuthn credential derived key.</param>
/// <returns>Task.</returns>
public async Task SetWebAuthnEnabledAsync(bool enabled, string? webauthCredentialId, string? webauthSalt, string? webauthCredentialDerivedKey)
public async Task SetWebAuthnEnabledAsync(bool enabled, string? webauthCredentialId = null, string? webauthSalt = null, string? webauthCredentialDerivedKey = null)
{
await localStorage.SetItemAsStringAsync("webAuthnEnabled", enabled.ToString().ToLower());
@@ -311,7 +311,7 @@ public sealed class AuthService(HttpClient httpClient, ILocalStorageService loca
private async Task RevokeTokenAsync()
{
// Remove webauthn enabled flag.
await SetWebAuthnEnabledAsync(false, null, null, null);
await SetWebAuthnEnabledAsync(false);
var tokenInput = new TokenModel
{