Files
Aaru.Server/Aaru.Server.New/Components/Account/Pages/Manage/EnableAuthenticator.razor

167 lines
6.6 KiB
Plaintext
Raw Normal View History

2024-05-02 07:43:47 +01:00
@page "/Account/Manage/EnableAuthenticator"
@using System.ComponentModel.DataAnnotations
@using System.Globalization
@using System.Text
@using System.Text.Encodings.Web
2024-05-03 03:24:40 +01:00
@using Microsoft.AspNetCore.Identity
2024-05-02 07:43:47 +01:00
@inject UserManager<IdentityUser> UserManager
2024-05-02 07:43:47 +01:00
@inject IdentityUserAccessor UserAccessor
@inject UrlEncoder UrlEncoder
@inject IdentityRedirectManager RedirectManager
@inject ILogger<EnableAuthenticator> Logger
<PageTitle>Configure authenticator app</PageTitle>
@if(recoveryCodes is not null)
{
<ShowRecoveryCodes RecoveryCodes="recoveryCodes.ToArray()" StatusMessage="@message"/>
}
else
{
<StatusMessage Message="@message"/>
2024-05-03 03:24:40 +01:00
2024-05-02 07:43:47 +01:00
<h3>Configure authenticator app</h3>
<div>
<p>To use an authenticator app go through the following steps:</p>
<ol class="list">
<li>
<p>
Download a two-factor authenticator app like Microsoft Authenticator for
<a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
<a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
Google Authenticator for
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en">Android</a> and
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
</p>
</li>
<li>
<p>Scan the QR Code or enter this key <kbd>@sharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div>
<div></div>
<div data-url="@authenticatorUri"></div>
</li>
<li>
<p>
Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
with a unique code. Enter the code in the confirmation box below.
</p>
<div class="row">
<div class="col-md-6">
<EditForm FormName="send-code" method="post" Model="Input" OnValidSubmit="OnValidSubmitAsync">
<DataAnnotationsValidator/>
<div class="form-floating mb-3">
<InputText autocomplete="off" @bind-Value="Input.Code" class="form-control" placeholder="Please enter the code."/>
<label class="control-label form-label" for="code">Verification Code</label>
<ValidationMessage class="text-danger" For="() => Input.Code"/>
</div>
<button class="btn btn-lg btn-primary w-100" type="submit">Verify</button>
<ValidationSummary class="text-danger" role="alert"/>
</EditForm>
</div>
</div>
</li>
</ol>
</div>
}
@code {
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
private string? message;
private IdentityUser user = default!;
2024-05-02 07:43:47 +01:00
private string? sharedKey;
private string? authenticatorUri;
private IEnumerable<string>? recoveryCodes;
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
[SupplyParameterFromForm]
private InputModel Input { get; set; } = new();
protected override async Task OnInitializedAsync()
{
user = await UserAccessor.GetRequiredUserAsync(HttpContext);
await LoadSharedKeyAndQrCodeUriAsync(user);
}
private async Task OnValidSubmitAsync()
{
// Strip spaces and hyphens
2024-05-03 03:24:40 +01:00
string verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
2024-05-02 07:43:47 +01:00
2024-05-03 03:24:40 +01:00
bool is2faTokenValid = await UserManager.VerifyTwoFactorTokenAsync(user, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
2024-05-02 07:43:47 +01:00
if(!is2faTokenValid)
{
message = "Error: Verification code is invalid.";
return;
}
await UserManager.SetTwoFactorEnabledAsync(user, true);
2024-05-03 03:24:40 +01:00
string userId = await UserManager.GetUserIdAsync(user);
2024-05-02 07:43:47 +01:00
Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
message = "Your authenticator app has been verified.";
if(await UserManager.CountRecoveryCodesAsync(user) == 0)
{
recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
}
else
{
RedirectManager.RedirectToWithStatus("Account/Manage/TwoFactorAuthentication", message, HttpContext);
}
}
private async ValueTask LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
2024-05-02 07:43:47 +01:00
{
// Load the authenticator key & QR code URI to display on the form
2024-05-03 03:24:40 +01:00
string? unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
2024-05-02 07:43:47 +01:00
if(string.IsNullOrEmpty(unformattedKey))
{
await UserManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
}
sharedKey = FormatKey(unformattedKey!);
2024-05-03 03:24:40 +01:00
string? email = await UserManager.GetEmailAsync(user);
2024-05-02 07:43:47 +01:00
authenticatorUri = GenerateQrCodeUri(email!, unformattedKey!);
}
private string FormatKey(string unformattedKey)
{
var result = new StringBuilder();
2024-05-03 03:24:40 +01:00
var currentPosition = 0;
2024-05-02 07:43:47 +01:00
while(currentPosition + 4 < unformattedKey.Length)
{
result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
currentPosition += 4;
}
if(currentPosition < unformattedKey.Length)
{
result.Append(unformattedKey.AsSpan(currentPosition));
}
return result.ToString().ToLowerInvariant();
}
2024-05-03 03:24:40 +01:00
private string GenerateQrCodeUri(string email, string unformattedKey) => string.Format(CultureInfo.InvariantCulture, AuthenticatorUriFormat, UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"), UrlEncoder.Encode(email), unformattedKey);
2024-05-02 07:43:47 +01:00
private sealed class InputModel
{
[Required]
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Text)]
[Display(Name = "Verification Code")]
public string Code { get; set; } = "";
}
}