diff --git a/.gitignore b/.gitignore index 8afdcb6..24319b2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.user *.userosscache *.sln.docstates +*.Development.json # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/Server/Configurations/MapperInitializer.cs b/Server/Configurations/MapperInitializer.cs index 3be0b46..9005edc 100644 --- a/Server/Configurations/MapperInitializer.cs +++ b/Server/Configurations/MapperInitializer.cs @@ -35,6 +35,7 @@ public class MapperInitializer : Profile CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap().ReverseMap(); CreateMap() .ForMember(d => d.AddressName, opt => opt.MapFrom(src => src.Address.Name)) @@ -107,9 +108,6 @@ public class MapperInitializer : Profile CreateMap().ReverseMap(); CreateMap().ForMember(d => d.CompanyId, o => o.MapFrom(s => s.Employer.CompanyId)); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); diff --git a/Server/Controllers/AccountController.cs b/Server/Controllers/AccountController.cs new file mode 100644 index 0000000..08ca6d8 --- /dev/null +++ b/Server/Controllers/AccountController.cs @@ -0,0 +1,95 @@ +using Microsoft.AspNetCore.Mvc; +using Server.Services; +using SharedModels.Requests; + +namespace Server.Controllers; + +[Route("api/account")] +[ApiController] +public class AccountController : ControllerBase +{ + private readonly IAccountManagementService _accountManagementService; + + public AccountController(IAccountManagementService accountManagementService) + { + _accountManagementService = accountManagementService; + } + + [HttpPost("changeInformation")] + public async Task ChangeInformation([FromBody] ChangeInformationRequest request) + { + var result = await _accountManagementService.ChangeInformation(request); + + if (!result.isSucceed) + { + return result.actionResult; + } + + return Ok(); + } + + [HttpPost("changeEmail")] + public async Task ChangeEmail([FromBody] ChangeEmailRequest request) + { + var result = await _accountManagementService.ChangeEmail(request); + + if (!result.isSucceed) + { + return result.actionResult; + } + + return Ok(); + } + + [HttpPost("confirmationEmailCallback")] + public async Task ConfirmChangeEmail([FromBody] ConfirmChangeEmailRequest request) + { + var result = await _accountManagementService.ConfirmChangeEmail(request); + + if (!result.isSucceed) + { + return result.actionResult; + } + + return Ok(); + } + + [HttpPost("changePhoneNumber")] + public async Task ChangePhoneNumber([FromBody] ChangePhoneNumberRequest request) + { + var result = await _accountManagementService.ChangePhoneNumber(request); + + if (!result.isSucceed) + { + return result.actionResult; + } + + return Ok(); + } + + [HttpPost("confirmPhoneNumber")] + public async Task ConfirmPhoneNumber([FromBody] ConfirmChangePhoneNumberRequest request) + { + var result = await _accountManagementService.ConfirmPhoneNumberChange(request); + + if (!result.isSucceed) + { + return result.actionResult; + } + + return Ok(); + } + + [HttpPost("changePassword")] + public async Task ChangePassword([FromBody] ChangePasswordRequest request) + { + var result = await _accountManagementService.ChangePassword(request); + + if (!result.isSucceed) + { + return result.actionResult; + } + + return Ok(); + } +} \ No newline at end of file diff --git a/Server/Controllers/AuthenticationController.cs b/Server/Controllers/AuthenticationController.cs index f997aee..31f8d67 100644 --- a/Server/Controllers/AuthenticationController.cs +++ b/Server/Controllers/AuthenticationController.cs @@ -22,30 +22,42 @@ public class AuthenticationController : ControllerBase } [HttpPost("register")] - public async Task RegisterAsync([FromBody] RegistrationRequest registerRequest) + public async Task RegisterAsync([FromBody] RegistrationRequest request) { - var (succeeded, message) = await _authService.RegisterAsync(registerRequest); + var result = await _authService.Register(request); - if (!succeeded) + if (!result.succeeded) { - return BadRequest(new ResponseBase {Message = message}); + return result.actionResult; } - return Ok(new ResponseBase{ Message = message }); + return Ok(); } - [HttpGet("confirmEmail")] - public async Task ConfirmEmailAsync([FromQuery] string email, [FromQuery] string token, - [FromQuery] string redirectionUrl) + [HttpPost("confirmEmail")] + public async Task ConfirmEmail([FromBody] ConfirmRegistrationEmailRequest request) { - var (succeeded, message) = await _authService.ConfirmEmailAsync(email, token); + var result = await _authService.ConfirmRegistrationEmail(request); - if (!succeeded) + if (!result.succeeded) { - return BadRequest(new ResponseBase {Message = message}); + return result.actionResult; } + + return Ok(); + } + + [HttpPost("confirmPhoneNumber")] + public async Task ConfirmPhoneNumber([FromBody] ConfirmRegistrationPhoneNumberRequest request) + { + var result = await _authService.ConfirmRegistrationPhoneNumber(request); - return Redirect(redirectionUrl); + if (!result.succeeded) + { + return result.actionResult; + } + + return Ok(); } [HttpPost("authenticate")] diff --git a/Server/Data/SeedData.cs b/Server/Data/SeedData.cs index 3a0ab8c..4833402 100644 --- a/Server/Data/SeedData.cs +++ b/Server/Data/SeedData.cs @@ -30,8 +30,7 @@ public class SeedData var adminUser = new User { FirstName = "user", LastName = "user", Patronymic = "user", - Email = "admin@autobus.com", - EmailConfirmed = true, + Email = "admin@autobus.com", EmailConfirmed = true }; await userManager.CreateAsync(adminUser, Identity.DefaultPassword); @@ -41,7 +40,7 @@ public class SeedData companyUser = new User { - FirstName = "user", LastName = "user", Patronymic = "user", + FirstName = "Firstname", LastName = "Lastname", Patronymic = "Patronymic", Email = "company@autobus.com", EmailConfirmed = true }; @@ -54,8 +53,11 @@ public class SeedData driverUser = new User { FirstName = "user", LastName = "user", Patronymic = "user", - Email = "driver@autobus.com", - EmailConfirmed = true + Email = "driver@autobus.com", EmailConfirmed = true, + PhoneNumber = "+380951935723", PhoneNumberConfirmed = true, + BirthDate = DateTime.Now.ToUniversalTime(), + Document = Identity.Document.Passport, DocumentDetails = "Some details", + Gender = Identity.Gender.Male }; await userManager.CreateAsync(driverUser, Identity.DefaultPassword); @@ -66,8 +68,7 @@ public class SeedData var defaultUser = new User { FirstName = "user", LastName = "user", Patronymic = "user", - Email = "user@autobus.com", - EmailConfirmed = true + Email = "user@autobus.com", EmailConfirmed = true }; await userManager.CreateAsync(defaultUser, Identity.DefaultPassword); diff --git a/Server/Program.cs b/Server/Program.cs index a3894ad..5a14a9f 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -107,6 +107,7 @@ services.AddAutoMapper(typeof(MapperInitializer)); services.AddScoped(); services.AddScoped(); +services.AddScoped(); services.AddScoped(); diff --git a/Server/Services/AccountManagementService.cs b/Server/Services/AccountManagementService.cs new file mode 100644 index 0000000..4051132 --- /dev/null +++ b/Server/Services/AccountManagementService.cs @@ -0,0 +1,130 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Server.Models; +using SharedModels.Requests; + +namespace Server.Services; + +public class AccountManagementService : IAccountManagementService +{ + private readonly IEmailSenderService _emailSender; + private readonly UserManager _userManager; + private readonly ISessionUserService _sessionUserService; + + public AccountManagementService(IEmailSenderService emailSender, UserManager userManager, + ISessionUserService sessionUserService) + { + _emailSender = emailSender; + _userManager = userManager; + _sessionUserService = sessionUserService; + + _userManager.UserValidators.Clear(); + } + + public async Task<(bool isSucceed, IActionResult actionResult)> ChangeInformation(ChangeInformationRequest request) + { + var dbUser = await _userManager.FindByIdAsync(_sessionUserService.GetAuthUserId()); + + dbUser.FirstName = request.FistName; + dbUser.LastName = request.LastName; + dbUser.Patronymic = request.Patronymic; + dbUser.BirthDate = new DateTime(request.BirthDate.Year, request.BirthDate.Month, request.BirthDate.Day, 0, 0, 0, DateTimeKind.Utc); + dbUser.Gender = request.Gender; + + await _userManager.UpdateAsync(dbUser); + + return (true, null!); + } + + public async Task<(bool isSucceed, IActionResult actionResult)> ChangeEmail(ChangeEmailRequest request) + { + var dbUser = await _userManager.FindByIdAsync(_sessionUserService.GetAuthUserId()); + + if (dbUser.Email.ToLower() == request.NewEmail.ToLower()) + { + return (false, new BadRequestObjectResult("You must specify a new email")); + } + + var changeEmailToken = await _userManager.GenerateChangeEmailTokenAsync(dbUser, request.NewEmail); + + var securityMessage = + $"Someone is trying to change email address of your account to {request.NewEmail}. " + + "If it is not you please follow account recovery procedure."; + var confirmationMessage = + "Someone changed account email to your address.\n" + + $"Here is your confirmation code: {changeEmailToken}\n\n" + + "If this was not you, please ignore this message."; + + try { await _emailSender.SendMail(dbUser.Email, "Security alert", securityMessage); } + catch (Exception) { /* ignored */ } + + try { await _emailSender.SendMail(request.NewEmail, "Change email confirmation", confirmationMessage); } + catch (Exception) { /* ignored */ } + + return (true, null!); + } + + public async Task<(bool isSucceed, IActionResult actionResult)> ConfirmChangeEmail(ConfirmChangeEmailRequest request) + { + var dbUser = await _userManager.FindByIdAsync(_sessionUserService.GetAuthUserId()); + + var result = await _userManager.ChangeEmailAsync(dbUser, request.NewEmail, request.Token); + if (!result.Succeeded) + { + return (false, new BadRequestObjectResult($"Error confirming email change {request.NewEmail}")); + } + + return (true, null!); + } + + public async Task<(bool isSucceed, IActionResult actionResult)> ChangePhoneNumber(ChangePhoneNumberRequest request) + { + var dbUser = await _userManager.FindByIdAsync(_sessionUserService.GetAuthUserId()); + + var changePhoneNumberToken = await _userManager.GenerateChangePhoneNumberTokenAsync(dbUser, request.PhoneNumber); + + var securityMessage = + $"Someone is trying to change phone number of your account to {request.PhoneNumber}. " + + "If it is not you please follow account recovery procedure."; + + try { await _emailSender.SendMail(dbUser.Email, "Security alert", securityMessage); } + catch (Exception) { /* ignored */ } + + // TODO: Send sms message to new phone number + + return (true, null!); + } + + public async Task<(bool isSucceed, IActionResult actionResult)> ConfirmPhoneNumberChange(ConfirmChangePhoneNumberRequest request) + { + var dbUser = await _userManager.FindByIdAsync(_sessionUserService.GetAuthUserId()); + + var result = await _userManager.ChangePhoneNumberAsync(dbUser, request.PhoneNumber, request.Token); + if (!result.Succeeded) + { + return (false, new BadRequestObjectResult(result.Errors)); + } + + return (true, null!); + } + + public async Task<(bool isSucceed, IActionResult actionResult)> ChangePassword(ChangePasswordRequest request) + { + var dbUser = await _userManager.FindByIdAsync(_sessionUserService.GetAuthUserId()); + + if (!await _userManager.CheckPasswordAsync(dbUser, request.CurrentPassword)) + { + return (false, new BadRequestObjectResult("Invalid current password")); + } + + await _userManager.ChangePasswordAsync(dbUser, request.CurrentPassword, request.NewPassword); + + var securityMessage = "Someone is changed your account password." + + "If this was not you please follow account recovery procedure."; + + try { await _emailSender.SendMail(dbUser.Email, "Security alert", securityMessage); } + catch (Exception) { /* ignored */ } + + return (true, null!); + } +} \ No newline at end of file diff --git a/Server/Services/AuthenticationService.cs b/Server/Services/AuthenticationService.cs index 5262bf7..499f949 100644 --- a/Server/Services/AuthenticationService.cs +++ b/Server/Services/AuthenticationService.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography; using System.Text; using Google.Apis.Auth; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; @@ -22,114 +23,104 @@ public class AuthenticationService : IAuthenticationService private readonly Jwt _jwt; private readonly UserManager _userManager; - private readonly RoleManager _roleManager; - private readonly IHttpContextAccessor _contextAccessor; - private readonly LinkGenerator _linkGenerator; private readonly IEmailSenderService _emailSender; private readonly IConfiguration _configuration; - public AuthenticationService(UserManager userManager, - RoleManager roleManager, IOptions jwt, - IHttpContextAccessor contextAccessor, LinkGenerator linkGenerator, + public AuthenticationService(UserManager userManager, IOptions jwt, IEmailSenderService emailSender, IConfiguration configuration) { _jwt = jwt.Value; _userManager = userManager; - _roleManager = roleManager; - _contextAccessor = contextAccessor; - _linkGenerator = linkGenerator; _emailSender = emailSender; _configuration = configuration; _userManager.UserValidators.Clear(); } - public async Task<(bool succeeded, string message)> RegisterAsync(RegistrationRequest regRequest) + public async Task<(bool succeeded, IActionResult actionResult)> Register(RegistrationRequest request) { - var userWithSameEmail = await _userManager.FindByEmailAsync(regRequest.Email); + var userWithSameEmail = await _userManager.FindByEmailAsync(request.Email); if (userWithSameEmail != null) { - return (false, "Email is already registered."); + return (false, new BadRequestObjectResult("Email is already registered.")); } var userWithSamePhone = await _userManager.Users - .SingleOrDefaultAsync(u => u.PhoneNumber == regRequest.PhoneNumber); + .SingleOrDefaultAsync(u => u.PhoneNumber == request.PhoneNumber); if (userWithSamePhone != null) { - return (false, "Phone is already registered."); + return (false, new BadRequestObjectResult("Phone is already registered.")); } var user = new User { UserName = "temp", - FirstName = regRequest.FirstName, - LastName = regRequest.LastName, - Patronymic = regRequest.Patronymic, - Email = regRequest.Email, - PhoneNumber = regRequest.PhoneNumber + FirstName = request.FirstName, + LastName = request.LastName, + Patronymic = request.Patronymic, + Email = request.Email, + PhoneNumber = request.PhoneNumber }; - var createUserResult = await _userManager.CreateAsync(user, regRequest.Password); + var createUserResult = await _userManager.CreateAsync(user, request.Password); if (!createUserResult.Succeeded) { - return (false, $"{createUserResult.Errors?.First().Description}"); + return (false, new BadRequestObjectResult(createUserResult.Errors)); } await _userManager.AddToRoleAsync(user, Identity.DefaultRole.ToString()); - var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); - var confirmationLink = _linkGenerator.GetUriByAction(_contextAccessor.HttpContext, - "confirmEmail", "authentication", - new { email = user.Email, token = token, redirectionUrl = regRequest.EmailConfirmationRedirectUrl }, - _contextAccessor.HttpContext.Request.Scheme); + var emailConfirmationToken = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var confirmationMessage = + "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); } + catch (Exception e) { /* ignored */ } + + // TODO: Add phone number confirmation - try - { - await _emailSender.SendMail(user.Email, "Email confirmation", confirmationLink); - } - catch (Exception e) - { - throw; - } - - return (true, $"User registered with email {user.Email}. Before signing in confirm your email" + - $"by following a link sent to registered email address."); + return (true, null!); } - public async Task<(bool succeeded, string? message)> ConfirmEmailAsync(string email, string token) + public async Task<(bool succeeded, IActionResult actionResult)> ConfirmRegistrationEmail(ConfirmRegistrationEmailRequest request) { - var user = await _userManager.FindByEmailAsync(email); - if (user == null) - { - return (false, $"Email {email} not registered"); - } + var dbUser = await _userManager.FindByEmailAsync(request.Email); - var result = await _userManager.ConfirmEmailAsync(user, token); + var result = await _userManager.ConfirmEmailAsync(dbUser, request.Token); if (!result.Succeeded) { - return (false, $"Error confirming email {email} with token {token}"); + return (false, new BadRequestObjectResult($"Error confirming email.")); } - return (true, $"Email {email} confirmed"); + return (true, null!); + } + + 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 + + return (true, null!); } public async Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateAsync(AuthenticationRequest authRequest) + AuthenticateAsync(AuthenticationRequest request) { var authResponse = new AuthenticationResponse(); - User user; - - user = await _userManager.FindByEmailAsync(authRequest.Email); + var user = await _userManager.FindByEmailAsync(request.Email); if (user == null) { - authResponse.Message = $"No accounts registered with {authRequest.Email}."; + authResponse.Message = $"No accounts registered with {request.Email}."; return (false, authResponse, null); } - if (!await _userManager.CheckPasswordAsync(user, authRequest.Password)) + if (!await _userManager.CheckPasswordAsync(user, request.Password)) { authResponse.Message = $"Incorrect email or password."; return (false, authResponse, null); @@ -160,13 +151,13 @@ public class AuthenticationService : IAuthenticationService } public async Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateWithGoogleAsync(GoogleAuthenticationRequest authRequest) + AuthenticateWithGoogleAsync(GoogleAuthenticationRequest request) { GoogleJsonWebSignature.ValidationSettings settings = new GoogleJsonWebSignature.ValidationSettings(); settings.Audience = new List { _configuration["Authentication:Google:ClientId"] }; - GoogleJsonWebSignature.Payload payload = GoogleJsonWebSignature.ValidateAsync(authRequest.IdToken, settings).Result; + GoogleJsonWebSignature.Payload payload = GoogleJsonWebSignature.ValidateAsync(request.IdToken, settings).Result; var authResponse = new AuthenticationResponse(); @@ -209,8 +200,8 @@ public class AuthenticationService : IAuthenticationService return (true, authResponse, refreshTokenString); } - public async Task<(bool succeeded, AuthenticationResponse authResponse, - string? refreshToken)> RenewRefreshTokenAsync(string? token) + public async Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> + RenewRefreshTokenAsync(string? token) { var authResponse = new AuthenticationResponse(); diff --git a/Server/Services/EmailSenderService.cs b/Server/Services/EmailSenderService.cs index 3ae2020..2db14f2 100644 --- a/Server/Services/EmailSenderService.cs +++ b/Server/Services/EmailSenderService.cs @@ -22,6 +22,11 @@ public class EmailSenderService : IEmailSenderService _smtpClient = new SmtpClient(); _smtpClient.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; } + + ~EmailSenderService() + { + _smtpClient.Dispose(); + } public async Task<(bool succeeded, string message)> SendMail(string toEmail, string subject, string message) { @@ -38,7 +43,6 @@ public class EmailSenderService : IEmailSenderService await _smtpClient.AuthenticateAsync(Encoding.ASCII, _smtpCredentials.User, _smtpCredentials.Password); await _smtpClient.SendAsync(mailMessage); await _smtpClient.DisconnectAsync(true); - _smtpClient.Dispose(); return (true, "Letter has been sent successfully"); } diff --git a/Server/Services/IAccountManagementService.cs b/Server/Services/IAccountManagementService.cs new file mode 100644 index 0000000..d6dd9c8 --- /dev/null +++ b/Server/Services/IAccountManagementService.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; +using SharedModels.Requests; + +namespace Server.Services; + +public interface IAccountManagementService +{ + Task<(bool isSucceed, IActionResult actionResult)> ChangeInformation(ChangeInformationRequest request); + + Task<(bool isSucceed, IActionResult actionResult)> ChangeEmail(ChangeEmailRequest request); + + Task<(bool isSucceed, IActionResult actionResult)> ConfirmChangeEmail(ConfirmChangeEmailRequest request); + + Task<(bool isSucceed, IActionResult actionResult)> ChangePhoneNumber(ChangePhoneNumberRequest request); + + Task<(bool isSucceed, IActionResult actionResult)> ConfirmPhoneNumberChange(ConfirmChangePhoneNumberRequest request); + + Task<(bool isSucceed, IActionResult actionResult)> ChangePassword(ChangePasswordRequest request); +} \ No newline at end of file diff --git a/Server/Services/IAuthenticationService.cs b/Server/Services/IAuthenticationService.cs index f68e1d4..fe64d35 100644 --- a/Server/Services/IAuthenticationService.cs +++ b/Server/Services/IAuthenticationService.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Mvc; using SharedModels.Requests; using SharedModels.Responses; @@ -5,15 +6,17 @@ namespace Server.Services; public interface IAuthenticationService { - Task<(bool succeeded, string message)> RegisterAsync(RegistrationRequest regRequest); + Task<(bool succeeded, IActionResult actionResult)> Register(RegistrationRequest request); + + Task<(bool succeeded, IActionResult actionResult)> ConfirmRegistrationEmail(ConfirmRegistrationEmailRequest request); + + Task<(bool succeeded, IActionResult actionResult)> ConfirmRegistrationPhoneNumber(ConfirmRegistrationPhoneNumberRequest numberRequest); - Task<(bool succeeded, string message)> ConfirmEmailAsync(string email, string token); + Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> + AuthenticateAsync(AuthenticationRequest request); Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateAsync(AuthenticationRequest authRequest); - - Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> - AuthenticateWithGoogleAsync(GoogleAuthenticationRequest authRequest); + AuthenticateWithGoogleAsync(GoogleAuthenticationRequest request); Task<(bool succeeded, AuthenticationResponse authResponse, string? refreshToken)> RenewRefreshTokenAsync(string? token); diff --git a/Server/Services/ISessionUserService.cs b/Server/Services/ISessionUserService.cs index 2738df7..55d829a 100644 --- a/Server/Services/ISessionUserService.cs +++ b/Server/Services/ISessionUserService.cs @@ -10,6 +10,5 @@ public interface ISessionUserService public Task IsAuthUserCompanyVehicleEnrollment(int enrollmentId); public Task IsAuthUserCompanyDriver(string driverId); - public Task IsAuthUserReview(int reviewId); } \ No newline at end of file diff --git a/Server/Services/IUserManagementService.cs b/Server/Services/IUserManagementService.cs index e3877b6..1b743c2 100644 --- a/Server/Services/IUserManagementService.cs +++ b/Server/Services/IUserManagementService.cs @@ -8,14 +8,15 @@ namespace Server.Services; public interface IUserManagementService { - Task<(bool isSucceeded, IActionResult? actionResult, UserDto user)> AddUser(CreateUserDto createUserDto); + Task<(bool isSucceeded, IActionResult actionResult, UserDto user)> AddUser(CreateUserDto createUserDto); - Task<(bool isSucceeded, IActionResult? actionResult, IEnumerable users, PagingMetadata pagingMetadata)> + Task<(bool isSucceeded, IActionResult actionResult, IEnumerable users, PagingMetadata pagingMetadata)> GetUsers(UserParameters parameters); + + Task<(bool isSucceeded, IActionResult actionResult, ExpandoObject user)> GetUser(string id, string? fields); + + Task<(bool isSucceeded, IActionResult actionResult, UserDto user)> + UpdateUser(string id, UpdateUserDto updateUserDto); - Task<(bool isSucceeded, IActionResult? actionResult, ExpandoObject user)> GetUser(string id, string? fields); - - Task<(bool isSucceeded, IActionResult? actionResult, UserDto user)> UpdateUser(string id, UpdateUserDto updateUserDto); - - Task<(bool isSucceed, IActionResult? actionResult)> DeleteUser(string id); + Task<(bool isSucceed, IActionResult actionResult)> DeleteUser(string id); } \ No newline at end of file diff --git a/Server/Services/UserManagementService.cs b/Server/Services/UserManagementService.cs index bb1123a..bb7b3c7 100644 --- a/Server/Services/UserManagementService.cs +++ b/Server/Services/UserManagementService.cs @@ -9,6 +9,7 @@ using Server.Models; using SharedModels.DataTransferObjects.Model; using SharedModels.QueryParameters; using SharedModels.QueryParameters.Objects; +using SharedModels.Requests; namespace Server.Services; @@ -21,9 +22,9 @@ public class UserManagementService : IUserManagementService private readonly IDataShaper _userDataShaper; private readonly IPager _pager; - public UserManagementService(IMapper mapper, UserManager userManager, RoleManager roleManager, - ISortHelper userSortHelper, IDataShaper userDataShaper, IPager pager, - ApplicationDbContext dbContext) + public UserManagementService(IMapper mapper, UserManager userManager, + RoleManager roleManager, ISortHelper userSortHelper, + IDataShaper userDataShaper, IPager pager) { _mapper = mapper; _userManager = userManager; @@ -35,7 +36,8 @@ public class UserManagementService : IUserManagementService _userManager.UserValidators.Clear(); } - public async Task<(bool isSucceeded, IActionResult? actionResult, UserDto user)> AddUser(CreateUserDto createUserDto) + public async Task<(bool isSucceeded, IActionResult actionResult, UserDto user)> + AddUser(CreateUserDto createUserDto) { var user = _mapper.Map(createUserDto); user.BirthDate = user.BirthDate == null ? null : new DateTime(user.BirthDate.Value.Ticks, DateTimeKind.Utc); @@ -45,12 +47,12 @@ public class UserManagementService : IUserManagementService if (await _userManager.FindByEmailAsync(user.Email) != null) { - return (false, new BadRequestObjectResult("Email already registered"), null); + return (false, new BadRequestObjectResult("Email already registered"), null!); } if (user.PhoneNumber != null && await _userManager.Users.FirstOrDefaultAsync(u => u.PhoneNumber == user.PhoneNumber) != null) { - return (false, new BadRequestObjectResult("Phone number already registered"), null); + return (false, new BadRequestObjectResult("Phone number already registered"), null!); } if (createUserDto.Roles != null!) @@ -59,7 +61,7 @@ public class UserManagementService : IUserManagementService { if (!await _roleManager.RoleExistsAsync(role)) { - return (false, new BadRequestObjectResult($"Roles \"{role}\" doesn't exist"), null); + return (false, new BadRequestObjectResult($"Roles \"{role}\" doesn't exist"), null!); } userDto.Roles.Add(role); @@ -71,11 +73,11 @@ public class UserManagementService : IUserManagementService userDto.Id = user.Id; - return (true, null, userDto); + return (true, null!, userDto); } - public async Task<(bool isSucceeded, IActionResult? actionResult, IEnumerable users, - PagingMetadata pagingMetadata)> GetUsers(UserParameters parameters) + public async Task<(bool isSucceeded, IActionResult actionResult, IEnumerable users, PagingMetadata pagingMetadata)> + GetUsers(UserParameters parameters) { var dbUsers = _userManager.Users.Include(u => u.Company) .Include(u => u.Reviews).Include(u => u.TicketGroups) @@ -108,7 +110,7 @@ public class UserManagementService : IUserManagementService } } - return (true, null, shapedData, pagingMetadata); + return (true, null!, shapedData, pagingMetadata); void SearchByAllUserFields(ref IQueryable users, string? search) { @@ -126,7 +128,7 @@ public class UserManagementService : IUserManagementService } } - public async Task<(bool isSucceeded, IActionResult? actionResult, ExpandoObject user)> + public async Task<(bool isSucceeded, IActionResult actionResult, ExpandoObject user)> GetUser(string id, string? fields) { var dbUser = await _userManager.Users.Include(u => u.Employer). @@ -142,24 +144,24 @@ public class UserManagementService : IUserManagementService fields = UserParameters.DefaultFields; } - var userDto = _mapper.Map(dbUser); + var userDto = _mapper.Map(dbUser); var shapedData = _userDataShaper.ShapeData(userDto, fields); - return (true, null, shapedData); + return (true, null!, shapedData); } - public async Task<(bool isSucceeded, IActionResult? actionResult, UserDto user)> + public async Task<(bool isSucceeded, IActionResult actionResult, UserDto user)> UpdateUser(string id, UpdateUserDto updateUserDto) { if (id != updateUserDto.Id) { - return (false, new BadRequestObjectResult("Object and query ids don't match"), null); + return (false, new BadRequestObjectResult("Object and query ids don't match"), null!); } if (!await _userManager.Users.AnyAsync(u => u.Id == id)) { - return (false, new NotFoundResult(), null); + return (false, new NotFoundResult(), null!); } var dbUser = await _userManager.FindByIdAsync(id); @@ -198,7 +200,7 @@ public class UserManagementService : IUserManagementService { if (!await _roleManager.RoleExistsAsync(role)) { - return (false, new BadRequestObjectResult($"Roles \"{role}\" doesn't exist"), null); + return (false, new BadRequestObjectResult($"Roles \"{role}\" doesn't exist"), null!); } } @@ -212,10 +214,10 @@ public class UserManagementService : IUserManagementService await _userManager.UpdateAsync(dbUser); - return (true, null, userDto); + return (true, null!, userDto); } - public async Task<(bool isSucceed, IActionResult? actionResult)> DeleteUser(string id) + public async Task<(bool isSucceed, IActionResult actionResult)> DeleteUser(string id) { var dbUser = await _userManager.FindByIdAsync(id); @@ -226,6 +228,6 @@ public class UserManagementService : IUserManagementService await _userManager.DeleteAsync(dbUser); - return (true, null); + return (true, null!); } } \ No newline at end of file diff --git a/Server/appsettings.Development.json b/Server/appsettings.Development.json deleted file mode 100644 index 2ce6a28..0000000 --- a/Server/appsettings.Development.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnection": "host=localhost;database=auto.bus;user id=postgres;password=postgres;Include Error Detail = true" - }, - "ApplicationName": "auto.bus", - "SmtpCredentials": { - "Host": "", - "Port": 587, - "User": "", - "Password": "" - }, - "Authentication": { - "Google": { - "ClientId": "", - "ClientSecret": "" - } - }, - "Jwt": { - "Key": "Secret which will never be exposed", - "Audience": "Application URL", - "Issuer": "Application URL", - "ValidityInMinutes": 60, - "RefreshTokenValidityInDays": 10 - } -} diff --git a/Server/appsettings.json b/Server/appsettings.json index 717c5ab..2792325 100644 --- a/Server/appsettings.json +++ b/Server/appsettings.json @@ -7,10 +7,10 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "host=localhost;database=auto.bus;user id=postgres;password=postgres;Include Error Detail = true" + "DefaultConnection": "" }, - "UseApiExplorer": true, - "ApplicationName": "auto.bus", + "UseApiExplorer": false, + "ApplicationName": "", "SmtpCredentials": { "Host": "", "Port": 587, @@ -24,10 +24,10 @@ } }, "Jwt": { - "Key": "Secret which will never be exposed", - "Audience": "Application URL", - "Issuer": "Application URL", - "ValidityInMinutes": 1, + "Key": "", + "Audience": "", + "Issuer": "", + "ValidityInMinutes": 5, "RefreshTokenValidityInDays": 10 } } diff --git a/SharedModels/Requests/ChangeEmailRequest.cs b/SharedModels/Requests/ChangeEmailRequest.cs new file mode 100644 index 0000000..ff19d54 --- /dev/null +++ b/SharedModels/Requests/ChangeEmailRequest.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests; + +public class ChangeEmailRequest +{ + [Required] + [DataType(DataType.EmailAddress)] + public string NewEmail { get; set; } = null!; +} \ No newline at end of file diff --git a/SharedModels/Requests/ChangeInformationRequest.cs b/SharedModels/Requests/ChangeInformationRequest.cs new file mode 100644 index 0000000..5560cf9 --- /dev/null +++ b/SharedModels/Requests/ChangeInformationRequest.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using Utils; + +namespace SharedModels.Requests; + +public class ChangeInformationRequest +{ + [Required] + public string FistName { get; set; } = null!; + [Required] + public string LastName { get; set; } = null!; + [Required] + public string Patronymic { get; set; } = null!; + + [Required] + [DataType(DataType.Date)] + public DateTime BirthDate { get; set; } + + [Required] + public Identity.Gender Gender { get; set; } +} \ No newline at end of file diff --git a/SharedModels/Requests/ChangePasswordRequest.cs b/SharedModels/Requests/ChangePasswordRequest.cs new file mode 100644 index 0000000..c957d4d --- /dev/null +++ b/SharedModels/Requests/ChangePasswordRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests; + +public class ChangePasswordRequest +{ + [Required] + public string CurrentPassword { get; set; } = null!; + + [Required] + public string NewPassword { get; set; } = null!; +} \ No newline at end of file diff --git a/SharedModels/Requests/ChangePhoneNumberRequest.cs b/SharedModels/Requests/ChangePhoneNumberRequest.cs new file mode 100644 index 0000000..9755c4e --- /dev/null +++ b/SharedModels/Requests/ChangePhoneNumberRequest.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests; + +public class ChangePhoneNumberRequest +{ + [Required] + [DataType(DataType.PhoneNumber)] + public string PhoneNumber { get; set; } = null!; +} \ No newline at end of file diff --git a/SharedModels/Requests/ConfirmChangeEmailRequest.cs b/SharedModels/Requests/ConfirmChangeEmailRequest.cs new file mode 100644 index 0000000..a388131 --- /dev/null +++ b/SharedModels/Requests/ConfirmChangeEmailRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests; + +public class ConfirmChangeEmailRequest : ChangeEmailRequest +{ + [Required] + public string Token { get; set; } = null!; +} \ No newline at end of file diff --git a/SharedModels/Requests/ConfirmChangePhoneNumberRequest.cs b/SharedModels/Requests/ConfirmChangePhoneNumberRequest.cs new file mode 100644 index 0000000..09c2e57 --- /dev/null +++ b/SharedModels/Requests/ConfirmChangePhoneNumberRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests; + +public class ConfirmChangePhoneNumberRequest : ChangePhoneNumberRequest +{ + [Required] + public string Token { get; set; } = null!; +} \ No newline at end of file diff --git a/SharedModels/Requests/ConfirmRegistrationEmailRequest.cs b/SharedModels/Requests/ConfirmRegistrationEmailRequest.cs new file mode 100644 index 0000000..c63f354 --- /dev/null +++ b/SharedModels/Requests/ConfirmRegistrationEmailRequest.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests; + +public class ConfirmRegistrationEmailRequest +{ + [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/ConfirmRegistrationPhoneNumberRequest.cs b/SharedModels/Requests/ConfirmRegistrationPhoneNumberRequest.cs new file mode 100644 index 0000000..dfbfc4b --- /dev/null +++ b/SharedModels/Requests/ConfirmRegistrationPhoneNumberRequest.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace SharedModels.Requests; + +public class ConfirmRegistrationPhoneNumberRequest +{ + [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/RegistrationRequest.cs b/SharedModels/Requests/RegistrationRequest.cs index 31495d1..7f330a8 100644 --- a/SharedModels/Requests/RegistrationRequest.cs +++ b/SharedModels/Requests/RegistrationRequest.cs @@ -24,7 +24,4 @@ public class RegistrationRequest [Required(ErrorMessage = "Password is required")] [DataType(DataType.Password)] public string Password { get; set; } = null!; - - [Url] - public string EmailConfirmationRedirectUrl { get; set; } = null!; }