using Isopoh.Cryptography.Argon2; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Mizuki.Dtos; using Mizuki.Services; using Mizuki.Validators; namespace Mizuki.Controllers; /// /// The login controller for Mizuki. /// [ApiController] [Route("/api/user")] public class LoginController( UserService userService, LoginService loginService, LoginDataValidator loginDataValidator, PasswordChangeValidator passwordChangeValidator) : ControllerBase { /// /// Logs into Mizuki as a given user. /// /// The login data dto. /// Redirect. [HttpPost] [Route("login")] public async Task Login( [FromForm] LoginDataDto dto) { if (!await userService.CheckPasswordForUser(dto.Username, dto.Password)) { return Redirect("/login?error=Invalid username or password."); } var user = await userService.GetUserForUsername(dto.Username); await loginService.LoginAsUser(user); Console.WriteLine($"Logged in as {user.Username}"); return Redirect("/"); } /// /// Logs out of Mizuki. /// /// Redirect. [Authorize] [Route("logout")] public async Task Logout() { await loginService.Logout(); return Redirect("/"); } /// /// Registers a new user in Mizuki. /// /// The login data dto. /// Redirect. [Route("register")] [HttpPost] public async Task Register( [FromForm] LoginDataDto dto) { if (await userService.UsernameTaken(dto.Username)) return Redirect("/register?error=This user already exists."); var result = await loginDataValidator.ValidateAsync(dto); if (!result.IsValid) { if (result.Errors.Any(e => e.PropertyName == "Username")) return Redirect("/register?error=Invalid username."); if (result.Errors.Any(e => e.PropertyName == "Password")) return Redirect("/register?error=Invalid password."); return Redirect("/register?error=An unexpected error has occurred."); } var user = await userService.CreateUser( dto.Username, dto.Password); if (user is null) { return Redirect("/register?error=There was an issue registering the user."); } await loginService.LoginAsUser(user); return Redirect("/"); } /// /// Checks whether we have logged in. /// /// Either an OK or a forbidden result. [Route("check")] [HttpGet] public async Task> Check() { try { var user = await loginService.GetActiveUser(); Console.WriteLine($"Get active user returned {user.Username}"); return TypedResults.Ok(); } catch { Console.WriteLine($"Get active user returned FORBID"); return TypedResults.Forbid(); } } /// /// Checks whether we have logged in. /// /// Either an OK or a forbidden result. [Route("change_password")] [HttpPost] [Authorize] public async Task> ChangePassword( [FromBody] PasswordChangeDto dto) { try { var user = await loginService.GetActiveUser(); if (!await userService.CheckPasswordForUser(user.Username, dto.OldPassword)) { return TypedResults.Conflict(); } var validation = await passwordChangeValidator.ValidateAsync(dto); if (!validation.IsValid) { return TypedResults.BadRequest(); } await userService.UpdatePasswordFor(user, dto.NewPassword); return TypedResults.Ok(); } catch { return TypedResults.Unauthorized(); } } /// /// Gets the info for this user. /// /// The user's info. [Route("info")] [HttpGet] [Authorize] public async Task Info() { var user = await loginService.GetActiveUser(); return new UserInfoDto( user.Username, null); } }