using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PlotLine.Data; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [Authorize] public sealed class ManageAccountController( ICurrentUserService currentUser, IUserRepository users, IAuthService authService, ITwoFactorService twoFactorService) : Controller { [HttpGet] public async Task Index() { var user = await GetCurrentUserAsync(); if (user is null) { return RedirectToAction("Login", "Account"); } return View(user); } [HttpGet] public async Task EnableTwoFactor() { var user = await GetCurrentUserAsync(); if (user is null) { return RedirectToAction("Login", "Account"); } var secret = twoFactorService.GenerateSecretKey(); var uri = twoFactorService.GenerateQrCodeUri(user.Email, secret); return View(new EnableTwoFactorViewModel { SecretKey = secret, QrCodeUri = uri, QrCodeImageDataUri = twoFactorService.GenerateQrCodeDataUri(uri) }); } [HttpPost] [ValidateAntiForgeryToken] public async Task EnableTwoFactor(EnableTwoFactorViewModel model) { if (!ModelState.IsValid) { model.QrCodeUri = twoFactorService.GenerateQrCodeUri(currentUser.Email ?? string.Empty, model.SecretKey); model.QrCodeImageDataUri = twoFactorService.GenerateQrCodeDataUri(model.QrCodeUri); return View(model); } var result = await authService.EnableTwoFactorAsync(currentUser.UserId!.Value, model.SecretKey, model.Code); if (!result.Success) { ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Two-factor authentication could not be enabled."); model.QrCodeUri = twoFactorService.GenerateQrCodeUri(currentUser.Email ?? string.Empty, model.SecretKey); model.QrCodeImageDataUri = twoFactorService.GenerateQrCodeDataUri(model.QrCodeUri); return View(model); } TempData["RecoveryCodes"] = JsonSerializer.Serialize(result.RecoveryCodes); TempData["AuthMessage"] = "Two-factor authentication has been enabled."; return RedirectToAction(nameof(RecoveryCodes)); } [HttpGet] public IActionResult DisableTwoFactor() { return View(new DisableTwoFactorViewModel()); } [HttpPost] [ValidateAntiForgeryToken] public async Task DisableTwoFactor(DisableTwoFactorViewModel model) { if (!ModelState.IsValid) { return View(model); } var result = await authService.DisableTwoFactorAsync(currentUser.UserId!.Value, model.CurrentPassword); if (!result.Success) { ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Two-factor authentication could not be disabled."); return View(model); } TempData["AuthMessage"] = "Two-factor authentication has been disabled."; return RedirectToAction(nameof(Index)); } [HttpGet] public IActionResult RecoveryCodes() { var codes = TempData["RecoveryCodes"] is string json ? JsonSerializer.Deserialize>(json) ?? Array.Empty() : Array.Empty(); return View(new RecoveryCodesViewModel { RecoveryCodes = codes }); } [HttpPost] [ValidateAntiForgeryToken] public async Task RegenerateRecoveryCodes() { var codes = await authService.GenerateRecoveryCodesAsync(currentUser.UserId!.Value); TempData["RecoveryCodes"] = JsonSerializer.Serialize(codes); TempData["AuthMessage"] = "New recovery codes have been generated."; return RedirectToAction(nameof(RecoveryCodes)); } private async Task GetCurrentUserAsync() { return currentUser.UserId is int userId ? await users.GetByIdAsync(userId) : null; } }