329 lines
9.4 KiB
C#
329 lines
9.4 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using PlotLine.Models;
|
|
using PlotLine.Services;
|
|
using PlotLine.ViewModels;
|
|
|
|
namespace PlotLine.Controllers;
|
|
|
|
public sealed class AccountController(IAuthService authService) : Controller
|
|
{
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult Register(string? returnUrl = null)
|
|
{
|
|
return View(new RegisterViewModel());
|
|
}
|
|
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Register(RegisterViewModel model)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
RegistrationResult result;
|
|
try
|
|
{
|
|
result = await authService.RegisterAsync(model);
|
|
}
|
|
catch
|
|
{
|
|
ModelState.AddModelError(string.Empty, "Your account could not be created because the verification email could not be queued. Please try again later.");
|
|
return View(model);
|
|
}
|
|
if (!result.Success)
|
|
{
|
|
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Registration could not be completed.");
|
|
return View(model);
|
|
}
|
|
|
|
TempData["AuthMessage"] = "Your account has been created. Please check your email to verify your account. If it does not arrive, use the resend verification email link below.";
|
|
return RedirectToAction(nameof(Login));
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult Login(string? returnUrl = null, bool companion = false)
|
|
{
|
|
return View(new LoginViewModel { ReturnUrl = returnUrl, IsCompanionLogin = IsCompanionLogin(returnUrl, companion) });
|
|
}
|
|
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Login(LoginViewModel model)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
model.IsCompanionLogin = IsCompanionLogin(model.ReturnUrl, model.IsCompanionLogin);
|
|
return View(model);
|
|
}
|
|
|
|
var result = await authService.LoginAsync(model);
|
|
if (result.RequiresTwoFactor)
|
|
{
|
|
return RedirectToAction(nameof(TwoFactor), new { returnUrl = model.ReturnUrl });
|
|
}
|
|
|
|
if (!result.Success)
|
|
{
|
|
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Login could not be completed.");
|
|
model.IsCompanionLogin = IsCompanionLogin(model.ReturnUrl, model.IsCompanionLogin);
|
|
return View(model);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
|
|
{
|
|
return LocalRedirect(model.ReturnUrl);
|
|
}
|
|
|
|
return RedirectToAction("Index", "Projects");
|
|
}
|
|
|
|
private static bool IsCompanionLogin(string? returnUrl, bool explicitCompanion)
|
|
{
|
|
if (explicitCompanion)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return !string.IsNullOrWhiteSpace(returnUrl)
|
|
&& returnUrl.StartsWith("/word-companion", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Logout()
|
|
{
|
|
await authService.LogoutAsync();
|
|
return RedirectToAction("Index", "Home");
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult AccessDenied()
|
|
{
|
|
return View();
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> VerifyEmail(Guid token)
|
|
{
|
|
if (token == Guid.Empty)
|
|
{
|
|
return View(new AccountStatusViewModel
|
|
{
|
|
Success = false,
|
|
Title = "Verification link problem",
|
|
Message = "This verification link is not valid."
|
|
});
|
|
}
|
|
|
|
var result = await authService.VerifyEmailAsync(token);
|
|
if (result.Success)
|
|
{
|
|
return RedirectToAction(nameof(VerifyEmailConfirmation));
|
|
}
|
|
|
|
return View(new AccountStatusViewModel
|
|
{
|
|
Success = false,
|
|
Title = "Verification link problem",
|
|
Message = result.ErrorMessage ?? "This verification link could not be used."
|
|
});
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult VerifyEmailConfirmation()
|
|
{
|
|
return View();
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult ForgotPassword()
|
|
{
|
|
return View(new ForgotPasswordViewModel());
|
|
}
|
|
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
try
|
|
{
|
|
var result = await authService.ForgotPasswordAsync(model);
|
|
if (!result.Success)
|
|
{
|
|
ModelState.AddModelError(string.Empty, "We could not queue a password reset email right now. Please try again later.");
|
|
return View(model);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
ModelState.AddModelError(string.Empty, "We could not queue a password reset email right now. Please try again later.");
|
|
return View(model);
|
|
}
|
|
|
|
TempData["AuthMessage"] = "If an account exists for that email address, a password reset email will be sent shortly.";
|
|
return RedirectToAction(nameof(Login));
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult ResetPassword(Guid token)
|
|
{
|
|
if (token == Guid.Empty)
|
|
{
|
|
return View(new ResetPasswordViewModel());
|
|
}
|
|
|
|
return View(new ResetPasswordViewModel { Token = token });
|
|
}
|
|
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
var result = await authService.ResetPasswordAsync(model);
|
|
if (!result.Success)
|
|
{
|
|
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Your password could not be reset.");
|
|
return View(model);
|
|
}
|
|
|
|
TempData["AuthMessage"] = "Your password has been reset. You can now log in.";
|
|
return RedirectToAction(nameof(Login));
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult ResendVerificationEmail()
|
|
{
|
|
return View(new ResendVerificationEmailViewModel());
|
|
}
|
|
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ResendVerificationEmail(ResendVerificationEmailViewModel model)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
var result = await authService.ResendVerificationEmailAsync(model.Email);
|
|
if (!result.Success)
|
|
{
|
|
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "We could not send the verification email right now. Please try again in a few minutes.");
|
|
return View(model);
|
|
}
|
|
|
|
TempData["AuthMessage"] = "If an unverified account exists for that email address, a verification email will be sent shortly.";
|
|
return View(new ResendVerificationEmailViewModel());
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult TwoFactor(string? returnUrl = null)
|
|
{
|
|
if (!authService.HasPendingTwoFactorLogin())
|
|
{
|
|
return RedirectToAction(nameof(Login));
|
|
}
|
|
|
|
return View(new TwoFactorCodeViewModel
|
|
{
|
|
ReturnUrl = returnUrl ?? authService.GetPendingTwoFactorReturnUrl()
|
|
});
|
|
}
|
|
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> TwoFactor(TwoFactorCodeViewModel model)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
var result = await authService.CompleteTwoFactorLoginAsync(model);
|
|
if (!result.Success)
|
|
{
|
|
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Two-factor login could not be completed.");
|
|
return View(model);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
|
|
{
|
|
return LocalRedirect(model.ReturnUrl);
|
|
}
|
|
|
|
return RedirectToAction("Index", "Projects");
|
|
}
|
|
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public IActionResult TwoFactorRecovery(string? returnUrl = null)
|
|
{
|
|
if (!authService.HasPendingTwoFactorLogin())
|
|
{
|
|
return RedirectToAction(nameof(Login));
|
|
}
|
|
|
|
return View(new TwoFactorRecoveryViewModel
|
|
{
|
|
ReturnUrl = returnUrl ?? authService.GetPendingTwoFactorReturnUrl()
|
|
});
|
|
}
|
|
|
|
[HttpPost]
|
|
[AllowAnonymous]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> TwoFactorRecovery(TwoFactorRecoveryViewModel model)
|
|
{
|
|
if (!authService.HasPendingTwoFactorLogin())
|
|
{
|
|
return RedirectToAction(nameof(Login));
|
|
}
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
var result = await authService.CompleteTwoFactorRecoveryLoginAsync(model);
|
|
if (!result.Success)
|
|
{
|
|
ModelState.AddModelError(string.Empty, result.ErrorMessage ?? "Recovery code login could not be completed.");
|
|
return View(model);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
|
|
{
|
|
return LocalRedirect(model.ReturnUrl);
|
|
}
|
|
|
|
return RedirectToAction("Index", "Projects");
|
|
}
|
|
}
|