Files
aliasvault/apps/server/AliasVault.Admin/Auth/Pages/LoginWithRecoveryCode.razor
2025-11-13 21:14:30 +01:00

85 lines
4.0 KiB
Plaintext

@page "/user/loginWithRecoveryCode"
@using AliasVault.Shared.Models.Enums
<LayoutPageTitle>Recovery code verification</LayoutPageTitle>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Recovery code verification
</h2>
<ServerValidationErrors @ref="ServerValidationErrors" />
<p class="text-gray-700 dark:text-gray-300 mb-6">
You have requested to log in with a recovery code. A recovery code is a one-time code that can be used to log in to your account.
Note that if you don't manually disable 2FA after login, you will be asked for an authenticator code again at the next login.
</p>
<div class="w-full max-w-md">
<EditForm Model="Input" FormName="login-with-recovery-code" OnValidSubmit="OnValidSubmitAsync" method="post" class="space-y-6">
<DataAnnotationsValidator/>
<ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
<div>
<label for="recovery-code" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Recovery Code</label>
<InputText @bind-Value="Input.RecoveryCode" id="recovery-code" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" autocomplete="off" placeholder="Enter your recovery code"/>
<ValidationMessage For="() => Input.RecoveryCode" class="text-red-600 dark:text-red-400 text-sm mt-1"/>
</div>
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Log in</button>
</EditForm>
</div>
@code {
private AdminUser user = default!;
[SupplyParameterFromForm] private InputModel Input { get; set; } = default!;
[SupplyParameterFromQuery] private string? ReturnUrl { get; set; }
/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
Input ??= new();
// Ensure the user has gone through the username & password screen first
user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ??
throw new InvalidOperationException("Unable to load two-factor authentication user.");
}
/// <summary>
/// Submits the form.
/// </summary>
private async Task OnValidSubmitAsync()
{
ServerValidationErrors.Clear();
var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
var userId = await UserManager.GetUserIdAsync(user);
if (result.Succeeded)
{
await AuthLoggingService.LogAuthEventSuccessAsync(user.UserName!, AuthEventType.TwoFactorAuthentication);
Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId);
NavigationService.RedirectTo(ReturnUrl);
}
else if (result.IsLockedOut)
{
await AuthLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TwoFactorAuthentication, AuthFailureReason.AccountLocked);
Logger.LogWarning("User account locked out.");
NavigationService.RedirectTo("user/lockout");
}
else
{
await AuthLoggingService.LogAuthEventFailAsync(user.UserName!, AuthEventType.TwoFactorAuthentication, AuthFailureReason.InvalidRecoveryCode);
Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId);
ServerValidationErrors.AddError("Error: Invalid recovery code entered.");
}
}
private sealed class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Recovery Code")]
public string RecoveryCode { get; set; } = "";
}
}