diff --git a/API/Controllers/AuthController.cs b/API/Controllers/AuthController.cs index 3eeafaa..373c3ac 100644 --- a/API/Controllers/AuthController.cs +++ b/API/Controllers/AuthController.cs @@ -1,5 +1,7 @@ using API.DTO.Base; using API.DTO.Login; +using API.Errors; +using API.Services; using API.Services.Interfaces; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; @@ -14,17 +16,19 @@ namespace API.Controllers { private readonly ILogger _logger; private readonly IUserManager _userManager; + private readonly UserService _userService; - public AuthController(ILogger logger, IUserManager userManager) + public AuthController(ILogger logger, IUserManager userManager, UserService userService) { _logger = logger; _userManager = userManager; + _userService = userService; } [HttpPost("login")] public ActionResult login(UserLoginDTO userLogin) { - UserDTO? user = _userManager.AuthenticateUser(userLogin); + UserDTO? user = _userManager.authenticateUser(userLogin); if (user == null) return new UnauthorizedResult(); @@ -42,5 +46,18 @@ namespace API.Controllers return Ok(user); } + + [HttpPost("register")] + public ActionResult register(UserRegisterDTO registerDTO) + { + UserDTO? user = _userManager.registerUser(registerDTO); + + if (user == null) + { + return Conflict(Strings.UserExists); + } + + return Ok(user); + } } } diff --git a/API/DTO/Login/UserRegisterDTO.cs b/API/DTO/Login/UserRegisterDTO.cs new file mode 100644 index 0000000..7ab051a --- /dev/null +++ b/API/DTO/Login/UserRegisterDTO.cs @@ -0,0 +1,19 @@ +using DAL.Values; +using System.ComponentModel.DataAnnotations; + +namespace API.DTO.Login +{ + public class UserRegisterDTO + { + [MaxLength(64)] + public string firstName { get; set; } = null!; + + [MaxLength(64)] + public string lastName { get; set; } = null!; + + public PhoneNumber phoneNumber { get; set; } = null!; + + [MaxLength(1000)] + public string password { get; set; } = null!; + } +} diff --git a/API/Errors/Strings.cs b/API/Errors/Strings.cs new file mode 100644 index 0000000..521abe1 --- /dev/null +++ b/API/Errors/Strings.cs @@ -0,0 +1,7 @@ +namespace API.Errors +{ + public static class Strings + { + public const string UserExists = "User with that phone number or first and last name already exists.\nIf you would like to change your phone number please login."; + } +} diff --git a/API/Program.cs b/API/Program.cs index 05495c4..46b68d9 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -40,7 +40,17 @@ namespace API builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); - builder.Services.AddTransient(); + builder.Services.AddTransient(options => + { + ILogger logger = options.GetRequiredService>(); + SASGContext context = options.GetRequiredService(); + IYesAuthentication authentication = options.GetRequiredService(); + PermissionService permissionService = options.GetRequiredService(); + + ulong defaultUserPermission = UInt64.Parse(builder.Configuration["defaultUserPermission"] ?? throw new InvalidOperationException("defaultUserPermission is null")); + + return new UserService(logger, context, authentication, permissionService, defaultUserPermission); + }); builder.Services.AddTransient(); builder.Services.AddTransient(); diff --git a/API/Services/Interfaces/IUserManager.cs b/API/Services/Interfaces/IUserManager.cs index 29658ec..a1496e1 100644 --- a/API/Services/Interfaces/IUserManager.cs +++ b/API/Services/Interfaces/IUserManager.cs @@ -5,6 +5,8 @@ namespace API.Services.Interfaces { public interface IUserManager { - UserDTO? AuthenticateUser(UserLoginDTO loginDTO); + UserDTO? authenticateUser(UserLoginDTO loginDTO); + + UserDTO? registerUser(UserRegisterDTO registerDTO); } } diff --git a/API/Services/ServiceBase.cs b/API/Services/ServiceBase.cs index aaf01de..4cc277d 100644 --- a/API/Services/ServiceBase.cs +++ b/API/Services/ServiceBase.cs @@ -15,19 +15,19 @@ namespace API.Services where TDTO : IAdaptable { private readonly TAuthentication _auth; - private readonly SASGContext _context; private readonly ILogger _logger; + public readonly SASGContext Context; public ServiceBase(ILogger logger, SASGContext context, TAuthentication auth) { _logger = logger; - _context = context; + Context = context; _auth = auth; } public TModel? get(ulong id, User user) { - TModel? result = _context.Set().Find(id); + TModel? result = Context.Set().Find(id); if (result == null) return null; @@ -39,17 +39,17 @@ namespace API.Services if (!_auth.canGetAll(user)) return null; - return whereClause != null ? _context.Set().Where(whereClause) : _context.Set(); + return whereClause != null ? Context.Set().Where(whereClause) : Context.Set(); } public TModel? getNoAuthentication(ulong id) { - return _context.Set().Find(id); + return Context.Set().Find(id); } public IEnumerable getNoAuthentication(Expression>? whereClause = null) { - return whereClause != null ? _context.Set().Where(whereClause) : _context.Set(); + return whereClause != null ? Context.Set().Where(whereClause) : Context.Set(); } public TModel? add(TDTO item, User user) @@ -61,8 +61,8 @@ namespace API.Services model.updater = user.id; model.updated = DateTime.Now; - _context.Add(model); - _context.SaveChanges(); + Context.Add(model); + Context.SaveChanges(); return model; } @@ -72,7 +72,7 @@ namespace API.Services if (!_auth.canUpdate(model, user)) return null; - TModel? origModel = _context.Set().Find(model.id); + TModel? origModel = Context.Set().Find(model.id); if (origModel == null) return null; @@ -83,7 +83,7 @@ namespace API.Services origModel.updated = DateTime.Now; origModel.updater = user.id; - _context.SaveChanges(); + Context.SaveChanges(); return origModel; } @@ -93,7 +93,7 @@ namespace API.Services if (!_auth.canDelete(model, user)) return null; - TModel? origModel = _context.Set().Find(model.id); + TModel? origModel = Context.Set().Find(model.id); if (origModel == null) return null; @@ -104,15 +104,15 @@ namespace API.Services copyToAudit(origModel); - _context.Remove(origModel); - _context.SaveChanges(); + Context.Remove(origModel); + Context.SaveChanges(); return origModel.adaptToAudit(); } private void copyToAudit(TModel model) { - _context.Set().Add(model.adaptToAudit()); + Context.Set().Add(model.adaptToAudit()); } } } diff --git a/API/Services/UserManager.cs b/API/Services/UserManager.cs index d104d70..b79b376 100644 --- a/API/Services/UserManager.cs +++ b/API/Services/UserManager.cs @@ -21,7 +21,7 @@ namespace API.Services _preferredHashingType = preferredHashingType; } - public UserDTO? AuthenticateUser(UserLoginDTO loginDTO) + public UserDTO? authenticateUser(UserLoginDTO loginDTO) { User? user = _userService.getNoAuthentication(x => x.phoneNumber.Equals(loginDTO.phoneNumber)).FirstOrDefault(); @@ -53,5 +53,33 @@ namespace API.Services return dto; } + + public UserDTO? registerUser(UserRegisterDTO registerDTO) + { + if (_userService.getNoAuthentication(x => + x.phoneNumber.Equals(registerDTO.phoneNumber) || + x.firstName.Equals(registerDTO.firstName) && x.lastName.Equals(registerDTO.lastName)) + .Any()) + { + return null; + } + + IHashingAlgorithm? hashingAlgorithm = _hashingFactory.getAlgorithm(_preferredHashingType); + if (hashingAlgorithm == null) + { + _logger.Log(LogLevel.Error, "Preferred hashing type '{hashingType}' that isn't recognized by factory '{factory}'.", _preferredHashingType, nameof(_hashingFactory)); + return null; + } + + byte[] salt; + string hashedPassword = hashingAlgorithm.hash(registerDTO.password, out salt); + + User user = _userService.add(registerDTO, hashedPassword, salt); + + UserDTO dto = new UserDTO(); + dto.adaptFromModel(user); + + return dto; + } } } diff --git a/API/Services/UserService.cs b/API/Services/UserService.cs index 2eb03f2..88ad6f9 100644 --- a/API/Services/UserService.cs +++ b/API/Services/UserService.cs @@ -1,5 +1,6 @@ using API.Authentication.Interfaces; using API.DTO.Base; +using API.DTO.Login; using DAL.Contexts; using DAL.Models; using DAL.Models.Audits; @@ -8,9 +9,39 @@ namespace API.Services { public class UserService : ServiceBase { - - public UserService(ILogger logger, SASGContext context, IYesAuthentication auth) : base(logger, context, auth) + private readonly ulong _defaultUserPermission; + private readonly PermissionService _permissionService; + public UserService(ILogger logger, SASGContext context, IYesAuthentication auth, PermissionService permissionService, ulong defaultUserPermission) : base(logger, context, auth) { + _permissionService = permissionService; + _defaultUserPermission = defaultUserPermission; + } + + public User add(UserRegisterDTO registerDTO, string hashedPassword, byte[] salt) + { + Permission? defaultPermission = _permissionService.getNoAuthentication(_defaultUserPermission); + + if (defaultPermission == null) + throw new InvalidOperationException("defaultUserPermission doesn't exist."); + + User model = new User + { + firstName = registerDTO.firstName, + lastName = registerDTO.lastName, + phoneNumber = registerDTO.phoneNumber, + + password = hashedPassword, + salt = salt, + + permissionId = defaultPermission.id, + + updated = DateTime.Now + }; + + Context.Add(model); + Context.SaveChanges(); + + return model; } } }