From ea0681f78a287b1918e5a60e0a562dd70500970a Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 4 Jun 2023 17:38:55 +0300 Subject: [PATCH] refactor: add functionality to require confirmed Email and PhoneNumber to te able to authenticate --- .../Controllers/AuthenticationController.cs | 34 ++++++++++-- Server/Services/AuthenticationService.cs | 52 +++++++++++++++---- Server/Services/IAuthenticationService.cs | 11 ++-- .../ConfirmRegistrationEmailRequest.cs | 6 +-- .../ConfirmRegistrationPhoneNumberRequest.cs | 6 +-- ...endConfirmationRegistrationEmailRequest.cs | 10 ++++ ...firmationRegistrationPhoneNumberRequest.cs | 10 ++++ 7 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 SharedModels/Requests/Authentication/SendConfirmationRegistrationEmailRequest.cs create mode 100644 SharedModels/Requests/Authentication/SendConfirmationRegistrationPhoneNumberRequest.cs diff --git a/Server/Controllers/AuthenticationController.cs b/Server/Controllers/AuthenticationController.cs index 8cc06be..b8ca41f 100644 --- a/Server/Controllers/AuthenticationController.cs +++ b/Server/Controllers/AuthenticationController.cs @@ -23,7 +23,7 @@ public class AuthenticationController : ControllerBase } [HttpPost("register")] - public async Task RegisterAsync([FromBody] RegistrationRequest request) + public async Task Register([FromBody] RegistrationRequest request) { var result = await _authService.Register(request); @@ -34,6 +34,19 @@ public class AuthenticationController : ControllerBase return Ok(); } + + [HttpPost("sendEmailConfirmationCode")] + public async Task SendEmailConfirmationCode([FromBody] SendConfirmationRegistrationEmailRequest request) + { + var result = await _authService.SendEmailConfirmationCode(request); + + if (!result.succeeded) + { + return result.actionResult; + } + + return Ok(); + } [HttpPost("confirmEmail")] public async Task ConfirmEmail([FromBody] ConfirmRegistrationEmailRequest request) @@ -48,6 +61,19 @@ public class AuthenticationController : ControllerBase return Ok(); } + [HttpPost("sendPhoneNumberConfirmationCode")] + public async Task SendPhoneNumberConfirmationCode([FromBody] SendConfirmationRegistrationPhoneNumberRequest request) + { + var result = await _authService.SendPhoneNumberConfirmationCode(request); + + if (!result.succeeded) + { + return result.actionResult; + } + + return Ok(); + } + [HttpPost("confirmPhoneNumber")] public async Task ConfirmPhoneNumber([FromBody] ConfirmRegistrationPhoneNumberRequest request) { @@ -65,7 +91,7 @@ public class AuthenticationController : ControllerBase public async Task Authenticate(AuthenticationRequest authRequest) { var (succeeded, authResponse, refreshToken) = - await _authService.AuthenticateAsync(authRequest); + await _authService.Authenticate(authRequest); if (!succeeded) { @@ -81,7 +107,7 @@ public class AuthenticationController : ControllerBase public async Task AuthenticateWithGoogle(GoogleAuthenticationRequest authRequest) { var (succeeded, authResponse, refreshToken) = - await _authService.AuthenticateWithGoogleAsync(authRequest); + await _authService.AuthenticateWithGoogle(authRequest); if (!succeeded) { @@ -99,7 +125,7 @@ public class AuthenticationController : ControllerBase var refreshToken = Request.Cookies["refreshToken"]; var (succeeded, authResponse, newRefreshToken) = - await _authService.RenewRefreshTokenAsync(refreshToken); + await _authService.RenewRefreshToken(refreshToken); if (!succeeded) { diff --git a/Server/Services/AuthenticationService.cs b/Server/Services/AuthenticationService.cs index 214bbb1..9088dc8 100644 --- a/Server/Services/AuthenticationService.cs +++ b/Server/Services/AuthenticationService.cs @@ -70,16 +70,30 @@ public class AuthenticationService : IAuthenticationService await _userManager.AddToRoleAsync(user, Identity.DefaultRole.ToString()); - var emailConfirmationToken = await _userManager.GenerateEmailConfirmationTokenAsync(user); - var confirmationMessage = + var emailMessage = "Someone registered an account on our service with your email.\n" + - $"Here is your confirmation code: {emailConfirmationToken}\n\n" + "If this was not you, please ignore this message."; - try { await _emailSender.SendMail(user.Email, "Email confirmation", confirmationMessage); } + try { await _emailSender.SendMail(user.Email, "Account Registration", emailMessage); } catch (Exception e) { /* ignored */ } - // TODO: Add phone number confirmation + return (true, null!); + } + + public async Task<(bool succeeded, IActionResult actionResult)> SendEmailConfirmationCode(SendConfirmationRegistrationEmailRequest request) + { + var dbUser = await _userManager.FindByEmailAsync(request.Email); + + if (dbUser == null) + { + return (false, new BadRequestObjectResult("Email not registered")); + } + + var emailConfirmationToken = await _userManager.GenerateEmailConfirmationTokenAsync(dbUser); + var confirmationMessage = $"Confirmation code: {emailConfirmationToken}"; + + try { await _emailSender.SendMail(dbUser.Email, "Email Confirmation", confirmationMessage); } + catch (Exception e) { /* ignored */ } return (true, null!); } @@ -97,17 +111,25 @@ public class AuthenticationService : IAuthenticationService return (true, null!); } + public Task<(bool succeeded, IActionResult actionResult)> SendPhoneNumberConfirmationCode(SendConfirmationRegistrationPhoneNumberRequest request) + { + throw new NotImplementedException(); + } + public async Task<(bool succeeded, IActionResult actionResult)> ConfirmRegistrationPhoneNumber(ConfirmRegistrationPhoneNumberRequest numberRequest) { var dbUser = await _userManager.Users.FirstAsync(u => u.PhoneNumber == numberRequest.PhoneNumber); // TODO: Add phone number confirmation token validation + dbUser.PhoneNumberConfirmed = true; + await _userManager.UpdateAsync(dbUser); + return (true, null!); } public async Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateAsync(AuthenticationRequest request) + Authenticate(AuthenticationRequest request) { var authResponse = new AuthenticationResponse(); @@ -118,10 +140,22 @@ public class AuthenticationService : IAuthenticationService authResponse.Message = $"No accounts registered with {request.Email}."; return (false, authResponse, null); } + + if (!user.EmailConfirmed) + { + authResponse.Message = "You must confirm your email before logging in"; + return (false, authResponse, null); + } + + if (!user.PhoneNumberConfirmed) + { + authResponse.Message = "You must confirm your phone number before logging in"; + return (false, authResponse, null); + } if (!await _userManager.CheckPasswordAsync(user, request.Password)) { - authResponse.Message = $"Incorrect email or password."; + authResponse.Message = "Incorrect email or password."; return (false, authResponse, null); } @@ -150,7 +184,7 @@ public class AuthenticationService : IAuthenticationService } public async Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateWithGoogleAsync(GoogleAuthenticationRequest request) + AuthenticateWithGoogle(GoogleAuthenticationRequest request) { GoogleJsonWebSignature.ValidationSettings settings = new GoogleJsonWebSignature.ValidationSettings(); @@ -200,7 +234,7 @@ public class AuthenticationService : IAuthenticationService } public async Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - RenewRefreshTokenAsync(string? token) + RenewRefreshToken(string? token) { var authResponse = new AuthenticationResponse(); diff --git a/Server/Services/IAuthenticationService.cs b/Server/Services/IAuthenticationService.cs index 11dc38d..c251aba 100644 --- a/Server/Services/IAuthenticationService.cs +++ b/Server/Services/IAuthenticationService.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using SharedModels.Requests; using SharedModels.Requests.Authentication; using SharedModels.Responses; @@ -9,18 +8,22 @@ public interface IAuthenticationService { Task<(bool succeeded, IActionResult actionResult)> Register(RegistrationRequest request); + Task<(bool succeeded, IActionResult actionResult)> SendEmailConfirmationCode(SendConfirmationRegistrationEmailRequest request); + Task<(bool succeeded, IActionResult actionResult)> ConfirmRegistrationEmail(ConfirmRegistrationEmailRequest request); + Task<(bool succeeded, IActionResult actionResult)> SendPhoneNumberConfirmationCode(SendConfirmationRegistrationPhoneNumberRequest request); + Task<(bool succeeded, IActionResult actionResult)> ConfirmRegistrationPhoneNumber(ConfirmRegistrationPhoneNumberRequest numberRequest); Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateAsync(AuthenticationRequest request); + Authenticate(AuthenticationRequest request); Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateWithGoogleAsync(GoogleAuthenticationRequest request); + AuthenticateWithGoogle(GoogleAuthenticationRequest request); Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - RenewRefreshTokenAsync(string? token); + RenewRefreshToken(string? token); Task RevokeRefreshToken(string? token); } diff --git a/SharedModels/Requests/Authentication/ConfirmRegistrationEmailRequest.cs b/SharedModels/Requests/Authentication/ConfirmRegistrationEmailRequest.cs index e2157dc..af4446a 100644 --- a/SharedModels/Requests/Authentication/ConfirmRegistrationEmailRequest.cs +++ b/SharedModels/Requests/Authentication/ConfirmRegistrationEmailRequest.cs @@ -2,12 +2,8 @@ using System.ComponentModel.DataAnnotations; namespace SharedModels.Requests.Authentication; -public class ConfirmRegistrationEmailRequest +public class ConfirmRegistrationEmailRequest : SendConfirmationRegistrationEmailRequest { - [Required] - [DataType(DataType.EmailAddress)] - public string Email { get; set; } = null!; - [Required] public string Token { get; set; } = null!; } \ No newline at end of file diff --git a/SharedModels/Requests/Authentication/ConfirmRegistrationPhoneNumberRequest.cs b/SharedModels/Requests/Authentication/ConfirmRegistrationPhoneNumberRequest.cs index 267d932..57576c4 100644 --- a/SharedModels/Requests/Authentication/ConfirmRegistrationPhoneNumberRequest.cs +++ b/SharedModels/Requests/Authentication/ConfirmRegistrationPhoneNumberRequest.cs @@ -2,12 +2,8 @@ using System.ComponentModel.DataAnnotations; namespace SharedModels.Requests.Authentication; -public class ConfirmRegistrationPhoneNumberRequest +public class ConfirmRegistrationPhoneNumberRequest : SendConfirmationRegistrationPhoneNumberRequest { - [Required] - [DataType(DataType.PhoneNumber)] - public string PhoneNumber { get; set; } = null!; - [Required] public string Token { get; set; } = null!; } \ No newline at end of file diff --git a/SharedModels/Requests/Authentication/SendConfirmationRegistrationEmailRequest.cs b/SharedModels/Requests/Authentication/SendConfirmationRegistrationEmailRequest.cs new file mode 100644 index 0000000..fc1fa5a --- /dev/null +++ b/SharedModels/Requests/Authentication/SendConfirmationRegistrationEmailRequest.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests.Authentication; + +public class SendConfirmationRegistrationEmailRequest +{ + [Required] + [DataType(DataType.EmailAddress)] + public string Email { get; set; } = null!; +} \ No newline at end of file diff --git a/SharedModels/Requests/Authentication/SendConfirmationRegistrationPhoneNumberRequest.cs b/SharedModels/Requests/Authentication/SendConfirmationRegistrationPhoneNumberRequest.cs new file mode 100644 index 0000000..ce83dcc --- /dev/null +++ b/SharedModels/Requests/Authentication/SendConfirmationRegistrationPhoneNumberRequest.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests.Authentication; + +public class SendConfirmationRegistrationPhoneNumberRequest +{ + [Required] + [DataType(DataType.PhoneNumber)] + public string PhoneNumber { get; set; } = null!; +} \ No newline at end of file