diff --git a/WebApi/Controllers/AuthController.cs b/WebApi/Controllers/AuthController.cs index 20bce76..dca1a1d 100644 --- a/WebApi/Controllers/AuthController.cs +++ b/WebApi/Controllers/AuthController.cs @@ -42,12 +42,15 @@ public AuthController( ISecurityService securityService, IJwtTokenService jwtTokenService, ITurnstileService turnstileService, - IEmailService emailService) + IEmailService emailService + ) { _oAuthOptions = oAuthOptions.Value; _jwtOptions = jwtOptions.Value; _jwtDataProtector = dataProtectionProvider.CreateProtector(_jwtOptions.DataProtectionPurpose); - _dataProtector = dataProtectionProvider.CreateProtector(dataProtectionOptions.Value.GeneralPurposeKey); + _dataProtector = dataProtectionProvider.CreateProtector( + dataProtectionOptions.Value.GeneralPurposeKey + ); _userService = userService; _securityService = securityService; _jwtTokenService = jwtTokenService; @@ -56,57 +59,55 @@ public AuthController( } [HttpPost("login")] - public async Task>> Login(LoginUserViewModel loginUser) + public async Task>> Login( + LoginUserViewModel loginUser + ) { var turnstileServiceResult = await _turnstileService.Verify(loginUser.TurnstileToken); if (turnstileServiceResult == false) { return BadRequest( - new ApiResponseViewModel - { - Success = false, - Message = "captcha_verify_failed" - }); + new ApiResponseViewModel { Success = false, Message = "captcha_verify_failed" } + ); } - User user = await _userService.FindUserByLoginAsync(loginUser.Email, Provider.Password, loginUser.Password); + User user = await _userService.FindUserByLoginAsync( + loginUser.Email, + Provider.Password, + loginUser.Password + ); if (user == null) { - return NotFound( - new ApiResponseViewModel - { - Success = false, - Message = "user_not_found" - }); + return NotFound(new ApiResponseViewModel { Success = false, Message = "user_not_found" }); } if (user.IsActive == false) { - return StatusCode((int)HttpStatusCode.Forbidden, - new ApiResponseViewModel - { - Success = false, - Message = "inactive_user" - }); + return StatusCode( + (int)HttpStatusCode.Forbidden, + new ApiResponseViewModel { Success = false, Message = "inactive_user" } + ); } if (user.IsEmailVerified == false) { - return StatusCode((int)HttpStatusCode.Forbidden, - new ApiResponseViewModel - { - Success = false, - Message = "email_not_verified" - }); + return StatusCode( + (int)HttpStatusCode.Forbidden, + new ApiResponseViewModel { Success = false, Message = "email_not_verified" } + ); } JwtTokensData jwtToken = _jwtTokenService.CreateJwtTokens(user); - await _jwtTokenService.AddUserTokenAsync(user, jwtToken.RefreshTokenSerial, jwtToken.AccessToken, null); + await _jwtTokenService.AddUserTokenAsync( + user, + jwtToken.RefreshTokenSerial, + jwtToken.AccessToken, + null + ); - AppendCookie(Response, new AuthCookie - { - AccessToken = jwtToken.AccessToken, - RefreshToken = jwtToken.RefreshToken, - }); + AppendCookie( + Response, + new AuthCookie { AccessToken = jwtToken.AccessToken, RefreshToken = jwtToken.RefreshToken } + ); return Ok( new ApiResponseViewModel @@ -117,9 +118,10 @@ public async Task>> Log Email = user.Email, Name = user.Name, Provider = user.Provider.ToString(), - IsEmailVerified = user.IsEmailVerified - } - }); + IsEmailVerified = user.IsEmailVerified, + }, + } + ); } [HttpGet("verify-email")] @@ -127,10 +129,14 @@ public async Task> VerifyEmail(string token) { try { - var emailVerificationCode = JsonSerializer.Deserialize(_dataProtector.Unprotect(Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(token)))); + var emailVerificationCode = JsonSerializer.Deserialize( + _dataProtector.Unprotect(Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(token))) + ); if (emailVerificationCode == null || emailVerificationCode.ExpiredAt < DateTime.UtcNow) { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" } + ); } var user = await _userService.FindUserByEmailAsync(emailVerificationCode.Email); @@ -141,38 +147,49 @@ public async Task> VerifyEmail(string token) if (user.IsEmailVerified) { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "email_already_verified" }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "email_already_verified" } + ); } await _userService.SetEmailVerifiedAsync(user.Id); - return Ok(new ApiResponseViewModel { Success = true, Message = "email_verified_successfully" }); + return Ok( + new ApiResponseViewModel { Success = true, Message = "email_verified_successfully" } + ); } catch { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" } + ); } } [HttpPost("reset-password")] - public async Task> ResetPassword(ResetPasswordViewModel model) + public async Task> ResetPassword( + ResetPasswordViewModel model + ) { var turnstileServiceResult = await _turnstileService.Verify(model.TurnstileToken); if (turnstileServiceResult == false) { return BadRequest( - new ApiResponseViewModel - { - Success = false, - Message = "captcha_verify_failed" - }); + new ApiResponseViewModel { Success = false, Message = "captcha_verify_failed" } + ); } try { - var resetPasswordCode = JsonSerializer.Deserialize(_dataProtector.Unprotect(Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(model.Token)))); + var resetPasswordCode = JsonSerializer.Deserialize( + _dataProtector.Unprotect( + Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(model.Token)) + ) + ); if (resetPasswordCode == null || resetPasswordCode.ExpiredAt < DateTime.UtcNow) { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" } + ); } var user = await _userService.FindUserByEmailAsync(resetPasswordCode.Email); @@ -183,49 +200,72 @@ public async Task> ResetPassword(ResetPasswor await _userService.ChangePasswordAsync(user.Id, model.NewPassword); - return Ok(new ApiResponseViewModel { Success = true, Message = "password_reset_successfully" }); + return Ok( + new ApiResponseViewModel { Success = true, Message = "password_reset_successfully" } + ); } catch { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "invalid_or_expired_code" } + ); } } [HttpPost("resend-verification-email")] - public async Task> ResendVerificationEmail(EmailViewModel model) + public async Task> ResendVerificationEmail( + EmailViewModel model + ) { var turnstileServiceResult = await _turnstileService.Verify(model.TurnstileToken); if (turnstileServiceResult == false) { return BadRequest( - new ApiResponseViewModel - { - Success = false, - Message = "captcha_verify_failed" - }); + new ApiResponseViewModel { Success = false, Message = "captcha_verify_failed" } + ); } var user = await _userService.FindUserByEmailAsync(model.Email); if (user == null) { - return Ok(new ApiResponseViewModel { Success = true, Message = "verification_email_sent_successfully" }); + return Ok( + new ApiResponseViewModel + { + Success = true, + Message = "verification_email_sent_successfully", + } + ); } if (user.IsEmailVerified) { - return Ok(new ApiResponseViewModel { Success = true, Message = "verification_email_sent_successfully" }); + return Ok( + new ApiResponseViewModel + { + Success = true, + Message = "verification_email_sent_successfully", + } + ); } var emailVerificationCode = new EmailVerificationCode { Email = user.Email, - ExpiredAt = DateTime.UtcNow.AddHours(24) + ExpiredAt = DateTime.UtcNow.AddHours(24), }; - var encodedVerificationCode = _dataProtector.Protect(JsonSerializer.Serialize(emailVerificationCode)); + var encodedVerificationCode = _dataProtector.Protect( + JsonSerializer.Serialize(emailVerificationCode) + ); var token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(encodedVerificationCode)); _ = Task.Run(() => _emailService.SendVerificationEmailAsync(user.Email, token)); - return Ok(new ApiResponseViewModel { Success = true, Message = "verification_email_sent_successfully" }); + return Ok( + new ApiResponseViewModel + { + Success = true, + Message = "verification_email_sent_successfully", + } + ); } [HttpPost("forgot-password")] @@ -235,49 +275,65 @@ public async Task> ForgotPassword(EmailViewMo if (turnstileServiceResult == false) { return BadRequest( - new ApiResponseViewModel - { - Success = false, - Message = "captcha_verify_failed" - }); + new ApiResponseViewModel { Success = false, Message = "captcha_verify_failed" } + ); } var user = await _userService.FindUserByEmailAsync(model.Email); if (user == null) { - return Ok(new ApiResponseViewModel { Success = true, Message = "reset_password_email_sent_successfully" }); + return Ok( + new ApiResponseViewModel + { + Success = true, + Message = "reset_password_email_sent_successfully", + } + ); } if (user.Provider != Provider.Password) { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "password_reset_not_allowed_for_oauth_users" }); + return BadRequest( + new ApiResponseViewModel + { + Success = false, + Message = "password_reset_not_allowed_for_oauth_users", + } + ); } var resetPasswordCode = new ResetPasswordCode { Email = user.Email, - ExpiredAt = DateTime.UtcNow.AddHours(24) + ExpiredAt = DateTime.UtcNow.AddHours(24), }; - var encodedResetPasswordCode = _dataProtector.Protect(JsonSerializer.Serialize(resetPasswordCode)); + var encodedResetPasswordCode = _dataProtector.Protect( + JsonSerializer.Serialize(resetPasswordCode) + ); var token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(encodedResetPasswordCode)); _ = Task.Run(() => _emailService.SendResetPasswordEmailAsync(user.Email, token)); - return Ok(new ApiResponseViewModel { Success = true, Message = "reset_password_email_sent_successfully" }); + return Ok( + new ApiResponseViewModel + { + Success = true, + Message = "reset_password_email_sent_successfully", + } + ); } [HttpPost("register")] - public async Task>> Register(RegisterUserViewModel registerUser) + public async Task>> Register( + RegisterUserViewModel registerUser + ) { var turnstileServiceResult = await _turnstileService.Verify(registerUser.TurnstileToken); if (turnstileServiceResult == false) { return BadRequest( - new ApiResponseViewModel - { - Success = false, - Message = "captcha_verify_failed" - }); + new ApiResponseViewModel { Success = false, Message = "captcha_verify_failed" } + ); } if (await _userService.FindUserByEmailAsync(registerUser.Email) == null) { @@ -289,7 +345,10 @@ public async Task>> Reg Provider = Provider.Password, IsActive = true, Roles = [new Role { Name = "User" }], - SerialNumber = _securityService.CreateCryptographicallySecureGuid().ToString().Replace("-", "") + SerialNumber = _securityService + .CreateCryptographicallySecureGuid() + .ToString() + .Replace("-", ""), }; await _userService.AddUserAsync(newUser); @@ -297,10 +356,12 @@ public async Task>> Reg var emailVerificationCode = new EmailVerificationCode { Email = newUser.Email, - ExpiredAt = DateTime.UtcNow.AddHours(24) + ExpiredAt = DateTime.UtcNow.AddHours(24), }; - var encodedVerificationCode = _dataProtector.Protect(JsonSerializer.Serialize(emailVerificationCode)); + var encodedVerificationCode = _dataProtector.Protect( + JsonSerializer.Serialize(emailVerificationCode) + ); var token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(encodedVerificationCode)); _ = Task.Run(() => _emailService.SendVerificationEmailAsync(newUser.Email, token)); @@ -314,73 +375,97 @@ public async Task>> Reg Email = newUser.Email, Name = newUser.Name, Provider = newUser.Provider.ToString(), - IsEmailVerified = newUser.IsEmailVerified - } + IsEmailVerified = newUser.IsEmailVerified, + }, } ); } else { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "email_already_exists" }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "email_already_exists" } + ); } } [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [HttpPost("change-password")] - public async Task> ChangePassword(ChangePasswordViewModel model) + public async Task> ChangePassword( + ChangePasswordViewModel model + ) { User user = await _userService.GetCurrentUserDataAsync(); if (user.ProviderKey != _securityService.GetSha256Hash(model.OldPassword)) { - return BadRequest(new ApiResponseViewModel - { - Success = false, - Message = "incorrect_old_password" - }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "incorrect_old_password" } + ); } if (await _userService.ChangePasswordAsync(user.Id, model.NewPassword)) { - return Ok(new ApiResponseViewModel - { - Success = true, - Message = "password_changed_successfully" - }); + return Ok( + new ApiResponseViewModel { Success = true, Message = "password_changed_successfully" } + ); } return BadRequest( - new ApiResponseViewModel - { - Success = false, - Message = "failed_to_change_password" - }); + new ApiResponseViewModel { Success = false, Message = "failed_to_change_password" } + ); } + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [HttpGet("user")] + public async Task>> GetUser() + { + User user = await _userService.GetCurrentUserDataAsync(); + + if (user is null) + { + return NotFound(new ApiResponseViewModel { Success = false, Message = "user_not_found" }); + } + + return Ok( + new ApiResponseViewModel + { + Success = true, + Data = new AuthResponseViewModel + { + Email = user.Email, + Name = user.Name, + Provider = user.Provider.ToString(), + IsEmailVerified = user.IsEmailVerified, + }, + } + ); + } [HttpGet("google-login")] public IActionResult GoogleLogin() { var properties = new AuthenticationProperties { - RedirectUri = _oAuthOptions.GoogleCallbackURL + RedirectUri = _oAuthOptions.GoogleCallbackURL, }; properties.Parameters.Add("prompt", "consent"); return Challenge(properties, GoogleDefaults.AuthenticationScheme); } [HttpGet("google-callback")] - public async Task>> GoogleCallbackAsync() + public async Task< + ActionResult> + > GoogleCallbackAsync() { - var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var authenticateResult = await HttpContext.AuthenticateAsync( + CookieAuthenticationDefaults.AuthenticationScheme + ); if (!authenticateResult.Succeeded) { - return BadRequest(new ApiResponseViewModel - { - Success = false, - Message = "google_authentication_failed." - }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "google_authentication_failed." } + ); } var refreshToken = authenticateResult.Properties.GetTokenValue("refresh_token"); @@ -412,25 +497,34 @@ public async Task>> Goo IsActive = true, Roles = [new Role { Name = "User" }], IsEmailVerified = true, - SerialNumber = _securityService.CreateCryptographicallySecureGuid().ToString().Replace("-", "") + SerialNumber = _securityService + .CreateCryptographicallySecureGuid() + .ToString() + .Replace("-", ""), }; await _userService.AddUserAsync(user); } if (user.IsActive == false) { - return Unauthorized(new ApiResponseViewModel { Success = false, Message = "inactive_user" }); + return Unauthorized( + new ApiResponseViewModel { Success = false, Message = "inactive_user" } + ); } JwtTokensData jwtToken = _jwtTokenService.CreateJwtTokens(user); - await _jwtTokenService.AddUserTokenAsync(user, jwtToken.RefreshTokenSerial, jwtToken.AccessToken, null); + await _jwtTokenService.AddUserTokenAsync( + user, + jwtToken.RefreshTokenSerial, + jwtToken.AccessToken, + null + ); - AppendCookie(Response, new AuthCookie - { - AccessToken = jwtToken.AccessToken, - RefreshToken = jwtToken.RefreshToken, - }); + AppendCookie( + Response, + new AuthCookie { AccessToken = jwtToken.AccessToken, RefreshToken = jwtToken.RefreshToken } + ); return Ok( new ApiResponseViewModel @@ -442,9 +536,10 @@ public async Task>> Goo Name = user.Name, ProfilePicture = profilePicture, Provider = user.Provider.ToString(), - IsEmailVerified = user.IsEmailVerified - } - }); + IsEmailVerified = user.IsEmailVerified, + }, + } + ); } [HttpGet("refresh-token")] @@ -453,47 +548,62 @@ public async Task>> Ref AuthCookie? authResponse = ReadCookie(Request); if (authResponse == null) { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "cookie_not_found." }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "cookie_not_found." } + ); } string refreshToken = authResponse.RefreshToken; if (string.IsNullOrWhiteSpace(refreshToken)) { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "refresh_token_not_found" }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "refresh_token_not_found" } + ); } try { - (Token token, User user) = await _jwtTokenService.FindUserAndTokenByRefreshTokenAsync(refreshToken); + (Token token, User user) = await _jwtTokenService.FindUserAndTokenByRefreshTokenAsync( + refreshToken + ); if (token == null) { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "invalid_refresh_token." }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "invalid_refresh_token." } + ); } var result = _jwtTokenService.CreateJwtTokens(user); - await _jwtTokenService.AddUserTokenAsync(user, result.RefreshTokenSerial, result.AccessToken, _jwtTokenService.GetRefreshTokenSerial(refreshToken)); + await _jwtTokenService.AddUserTokenAsync( + user, + result.RefreshTokenSerial, + result.AccessToken, + _jwtTokenService.GetRefreshTokenSerial(refreshToken) + ); - AppendCookie(Response, new AuthCookie - { - AccessToken = result.AccessToken, - RefreshToken = result.RefreshToken, - }); + AppendCookie( + Response, + new AuthCookie { AccessToken = result.AccessToken, RefreshToken = result.RefreshToken } + ); return Ok( - new ApiResponseViewModel - { - Success = true, - Data = new AuthResponseViewModel + new ApiResponseViewModel { - Email = user.Email, - Name = user.Name, - Provider = user.Provider.ToString(), - IsEmailVerified = user.IsEmailVerified + Success = true, + Data = new AuthResponseViewModel + { + Email = user.Email, + Name = user.Name, + Provider = user.Provider.ToString(), + IsEmailVerified = user.IsEmailVerified, + }, } - }); + ); } catch { - return BadRequest(new ApiResponseViewModel { Success = false, Message = "invalid_refresh_token." }); + return BadRequest( + new ApiResponseViewModel { Success = false, Message = "invalid_refresh_token." } + ); } } @@ -503,10 +613,14 @@ public async Task> Logout() AuthCookie? authResponse = ReadCookie(Request); if (authResponse == null) { - return Ok(new ApiResponseViewModel { Success = true, Message = "logged_out_successfully." }); + return Ok( + new ApiResponseViewModel { Success = true, Message = "logged_out_successfully." } + ); } string refreshToken = authResponse.RefreshToken; - (Token token, User user) = await _jwtTokenService.FindUserAndTokenByRefreshTokenAsync(refreshToken); + (Token token, User user) = await _jwtTokenService.FindUserAndTokenByRefreshTokenAsync( + refreshToken + ); if (token != null) { @@ -519,20 +633,25 @@ public async Task> Logout() private void AppendCookie(HttpResponse response, AuthCookie authCookie) { - response.Cookies.Append(_jwtOptions.CookieName, _jwtDataProtector.Protect(JsonSerializer.Serialize(authCookie)), new CookieOptions - { - HttpOnly = true, - Secure = true, - SameSite = SameSiteMode.Strict, - Expires = DateTimeOffset.Now.AddMinutes(_jwtOptions.RefreshTokenExpirationMinutes) - }); + response.Cookies.Append( + _jwtOptions.CookieName, + _jwtDataProtector.Protect(JsonSerializer.Serialize(authCookie)), + new CookieOptions + { + HttpOnly = true, + Secure = true, + SameSite = SameSiteMode.Strict, + Expires = DateTimeOffset.Now.AddMinutes(_jwtOptions.RefreshTokenExpirationMinutes), + } + ); } private AuthCookie? ReadCookie(HttpRequest request) { if (request.Cookies.TryGetValue(_jwtOptions.CookieName, out string? cookieValue)) { - return JsonSerializer.Deserialize(_jwtDataProtector.Unprotect(cookieValue)) ?? null; + return JsonSerializer.Deserialize(_jwtDataProtector.Unprotect(cookieValue)) + ?? null; } return null; }