198 lines
6.1 KiB
Text
198 lines
6.1 KiB
Text
|
|
@page "/setup-2fa"
|
||
|
|
@inject IAuthService AuthService
|
||
|
|
@inject NavigationManager Navigation
|
||
|
|
@inject ISettingsService SettingsService
|
||
|
|
@inject IJSRuntime JSRuntime
|
||
|
|
|
||
|
|
@rendermode InteractiveServer
|
||
|
|
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
|
||
|
|
|
||
|
|
<PageTitle>@T("2FA einrichten - Author Buddy", "2FA setup - Author Buddy")</PageTitle>
|
||
|
|
|
||
|
|
<div class="auth-page">
|
||
|
|
<div class="auth-card">
|
||
|
|
<h2>@T("Zwei-Faktor-Authentifizierung", "Two-Factor Authentication")</h2>
|
||
|
|
|
||
|
|
@if (isEnabled)
|
||
|
|
{
|
||
|
|
<div class="auth-success">
|
||
|
|
<p>@T("2FA ist aktiviert.", "2FA is enabled.")</p>
|
||
|
|
<button @onclick="Disable2FA" disabled="@isBusy">@T("2FA deaktivieren", "Disable 2FA")</button>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
else if (showSecret)
|
||
|
|
{
|
||
|
|
<p>@T("Scanne den folgenden QR-Code mit deiner Authenticator-App (z.B. Google Authenticator, Microsoft Authenticator) oder gib den geheimen Schlüssel manuell ein:", "Scan the QR code below with your authenticator app (e.g. Google Authenticator, Microsoft Authenticator) or enter the secret key manually:")</p>
|
||
|
|
|
||
|
|
<div class="qr-container">
|
||
|
|
<img src="@qrCodeDataUri" alt="QR Code" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="form-group">
|
||
|
|
<label>@T("Geheimer Schlüssel:", "Secret key:")</label>
|
||
|
|
<div class="secret-display">
|
||
|
|
<code>@secret</code>
|
||
|
|
<button @onclick="CopySecret">@T("Kopieren", "Copy")</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="form-group">
|
||
|
|
<label for="code">@T("Code zur Bestätigung eingeben:", "Enter verification code:")</label>
|
||
|
|
<input id="code" @bind="verificationCode" maxlength="6" @onkeydown="HandleKeyDown" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="auth-actions">
|
||
|
|
<button @onclick="Enable2FA" disabled="@isBusy || string.IsNullOrWhiteSpace(verificationCode)">@T("2FA aktivieren", "Enable 2FA")</button>
|
||
|
|
<button @onclick="Cancel">@T("Abbrechen", "Cancel")</button>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
<p>@T("Richte die Zwei-Faktor-Authentifizierung ein, um dein Konto zusätzlich zu schützen.", "Set up two-factor authentication to add an extra layer of security to your account.")</p>
|
||
|
|
<div class="auth-actions">
|
||
|
|
<button @onclick="StartSetup">@T("2FA einrichten", "Set up 2FA")</button>
|
||
|
|
</div>
|
||
|
|
}
|
||
|
|
|
||
|
|
@if (!string.IsNullOrEmpty(errorMessage))
|
||
|
|
{
|
||
|
|
<div class="auth-error">@errorMessage</div>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
@code {
|
||
|
|
private string _language = "de";
|
||
|
|
private string secret = string.Empty;
|
||
|
|
private string verificationCode = string.Empty;
|
||
|
|
private string errorMessage = string.Empty;
|
||
|
|
private string? qrCodeDataUri;
|
||
|
|
private bool showSecret;
|
||
|
|
private bool isEnabled;
|
||
|
|
private bool isBusy;
|
||
|
|
private string? username;
|
||
|
|
|
||
|
|
private string T(string de, string en) => _language == "en" ? en : de;
|
||
|
|
|
||
|
|
protected override async Task OnInitializedAsync()
|
||
|
|
{
|
||
|
|
_language = await SettingsService.GetLanguageAsync() ?? "de";
|
||
|
|
isEnabled = await AuthService.Is2FAEnabledAsync();
|
||
|
|
var user = await AuthService.GetCurrentUserAsync();
|
||
|
|
username = user?.Name;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task HandleKeyDown(KeyboardEventArgs e)
|
||
|
|
{
|
||
|
|
if (e.Key == "Enter")
|
||
|
|
await Enable2FA();
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task StartSetup()
|
||
|
|
{
|
||
|
|
isBusy = true;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
secret = await AuthService.Generate2FASecretAsync();
|
||
|
|
var uri = AuthService.Get2FAProvisioningUri(secret, username ?? "user");
|
||
|
|
qrCodeDataUri = await GenerateQRCodeDataUri(uri);
|
||
|
|
showSecret = true;
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
errorMessage = $"{T("Fehler:", "Error:")} {ex.Message}";
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
isBusy = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task Enable2FA()
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(verificationCode) || verificationCode.Length != 6)
|
||
|
|
{
|
||
|
|
errorMessage = T("Bitte gib einen gültigen 6-stelligen Code ein.", "Please enter a valid 6-digit code.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!TotpHelper.VerifyCode(secret, verificationCode))
|
||
|
|
{
|
||
|
|
errorMessage = T("Code ungültig. Bitte versuche es erneut.", "Invalid code. Please try again.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
isBusy = true;
|
||
|
|
errorMessage = string.Empty;
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var result = await AuthService.Enable2FAAsync(secret);
|
||
|
|
if (result)
|
||
|
|
{
|
||
|
|
isEnabled = true;
|
||
|
|
showSecret = false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
errorMessage = T("Fehler beim Aktivieren von 2FA.", "Failed to enable 2FA.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
errorMessage = $"{T("Fehler:", "Error:")} {ex.Message}";
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
isBusy = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task Disable2FA()
|
||
|
|
{
|
||
|
|
isBusy = true;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var result = await AuthService.Disable2FAAsync();
|
||
|
|
if (result)
|
||
|
|
{
|
||
|
|
isEnabled = false;
|
||
|
|
showSecret = false;
|
||
|
|
secret = string.Empty;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
errorMessage = $"{T("Fehler:", "Error:")} {ex.Message}";
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
isBusy = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task<string?> GenerateQRCodeDataUri(string uri)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var encoded = System.Web.HttpUtility.UrlEncode(uri);
|
||
|
|
return $"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={encoded}";
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task CopySecret()
|
||
|
|
{
|
||
|
|
if (!string.IsNullOrEmpty(secret))
|
||
|
|
await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", secret);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void Cancel()
|
||
|
|
{
|
||
|
|
Navigation.NavigateTo("/");
|
||
|
|
}
|
||
|
|
}
|