Skip to content

Commit

Permalink
[RSN-40] Add user authentication (#43)
Browse files Browse the repository at this point in the history
* feat: add jwt and exceptions handlers

Simplify the logic on the controllers' side, custom exceptions and
a library for validation were added. The implemented handlers are
allow to catch those exceptions and return the corresponding
statuses and detailed response.

Additionally, mappers have been created to more easily convert
entities into the corresponding DTOs, fixed enum conversion
when it comes to UserRole and export postgres port on the
development environment.

* test: add unit tests for jwt, handlers and validators

* refactor: disallow registering user when phone is already used

* feat: add global route prefix middleware

* style: format code with dotnet format
  • Loading branch information
raczu authored Jun 6, 2024
1 parent e559b22 commit 642adc3
Show file tree
Hide file tree
Showing 12 changed files with 59 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public async Task HandleException_WhenVerificationException_ShouldReturnProblemD
var exception = new VerificationException("Verification error");

ProblemDetailsContext? problemDetailsContext = null;

_mockProblemDetailsService.Setup(x =>
x.TryWriteAsync(It.IsAny<ProblemDetailsContext>()))
.Callback<ProblemDetailsContext>(context => problemDetailsContext = context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ public void Setup()
_mockContext = new Mock<ReasnContext>();
_hasher = new PasswordHasher<User>();
_service = new AuthService(_mockContext.Object);

var user = new User
{
Email = "[email protected]",
Username = "jsnow",
Password = _hasher.HashPassword(null!, "password")
Password = _hasher.HashPassword(null!, "password"),
Phone = "+123 456789"
};

_mockContext.Setup(c => c.Users)
.ReturnsDbSet(new List<User> { user });
}
Expand Down Expand Up @@ -96,6 +95,19 @@ public void Register_WhenUserWithUsernameAlreadyExists_ShouldThrowBadRequestExce
Assert.ThrowsException<BadRequestException>(() => _service.Register(request));
}

[TestMethod]
public void Register_WhenUserWithPhoneAlreadyExists_ShouldThrowBadRequestException()
{
var request = new RegisterRequest
{
Email = "[email protected]",
Username = "jstark",
Phone = "+123 456789"
};

Assert.ThrowsException<BadRequestException>(() => _service.Register(request));
}

[TestMethod]
public void Register_WhenUserDoesNotExist_ShouldReturnRegisteredUser()
{
Expand All @@ -106,7 +118,7 @@ public void Register_WhenUserDoesNotExist_ShouldReturnRegisteredUser()
Email = "[email protected]",
Username = "jstark",
Password = "S3cureP@ssword!",
Phone = "+123 456789",
Phone = "+123 456781",
Address = new AddressDto
{
Street = "The Wall",
Expand Down
5 changes: 4 additions & 1 deletion Server/ReasnAPI/ReasnAPI/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
using Microsoft.AspNetCore.Mvc;
using ReasnAPI.Mappers;
using ReasnAPI.Models.Authentication;
using ReasnAPI.Models.DTOs;
using ReasnAPI.Services.Authentication;

namespace ReasnAPI.Controllers;

[ApiController]
[Route("api/[controller]")]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly AuthService _authService;
Expand All @@ -20,6 +21,7 @@ public AuthController(AuthService authService, TokenService tokenService)
}

[HttpPost("login")]
[ProducesResponseType<TokenPayload>(StatusCodes.Status200OK)]
public IActionResult Login(
[FromBody] LoginRequest request,
[FromServices] IValidator<LoginRequest> validator)
Expand All @@ -32,6 +34,7 @@ public IActionResult Login(
}

[HttpPost("register")]
[ProducesResponseType<UserDto>(StatusCodes.Status201Created)]
public IActionResult Register(
[FromBody] RegisterRequest request,
[FromServices] IValidator<RegisterRequest> validator)
Expand Down
2 changes: 1 addition & 1 deletion Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace ReasnAPI.Controllers;

[ApiController]
[Route("api/[controller]")]
[Route("[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace ReasnAPI.Middlewares;

public class GlobalRoutePrefixMiddleware
{
private readonly RequestDelegate _next;
private readonly string _routePrefix;

public GlobalRoutePrefixMiddleware(RequestDelegate next, string routePrefix)
{
_next = next;
_routePrefix = routePrefix;
}

public async Task InvokeAsync(HttpContext context)
{
context.Request.PathBase = new PathString(_routePrefix);
await _next(context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace ReasnAPI.Models.Authentication;

public class TokenPayload
{
public string TokenType { get; set; }
public string AccessToken { get; set; }
public string TokenType { get; set; } = null!;
public string AccessToken { get; set; } = null!;
public int ExpiresIn { get; set; }
}
3 changes: 1 addition & 2 deletions Server/ReasnAPI/ReasnAPI/Models/Database/ReasnContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasPostgresEnum("common", "event_status", new[] { "Completed", "In progress", "Approved", "Waiting for approval" })
.HasPostgresEnum("common", "object_type", new[] { "Event", "User" })
.HasPostgresEnum("common", "participant_status", new[] { "Interested", "Participating" })
.HasPostgresEnum("users", "role", new[] { "User", "Organizer", "Admin" });

.HasPostgresEnum("users", "role", new[] { "User", "Organizer", "Admin" });
modelBuilder.Entity<Address>(entity =>
{
entity.HasKey(e => e.Id).HasName("address_pkey");
Expand Down
8 changes: 4 additions & 4 deletions Server/ReasnAPI/ReasnAPI/Models/Enums/UserRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ namespace ReasnAPI.Models.Enums;
public enum UserRole
{
[PgName("User")]
User,

User,

[PgName("Organizer")]
Organizer,

Organizer,
[PgName("Admin")]
Admin
}
4 changes: 4 additions & 0 deletions Server/ReasnAPI/ReasnAPI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using ReasnAPI.Exceptions;
using ReasnAPI.Middlewares;
using ReasnAPI.Models.Database;
using ReasnAPI.Services.Authentication;
using ReasnAPI.Validators;
Expand Down Expand Up @@ -103,6 +104,9 @@

var app = builder.Build();

app.UseMiddleware<GlobalRoutePrefixMiddleware>("/api/v1");
app.UsePathBase(new PathString("/api/v1"));

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ public User Register(RegisterRequest request)
{
var userAlreadyExists = _context.Users.Any(u =>
u.Email.ToUpper() == request.Email.ToUpper() ||
u.Username.ToUpper() == request.Username.ToUpper());
u.Username.ToUpper() == request.Username.ToUpper() ||
u.Phone == request.Phone);

if (userAlreadyExists)
{
throw new BadRequestException(
"User with provided email or username already exists");
"User with provided email, username or phone already exists");
}

var user = new User
Expand Down
4 changes: 2 additions & 2 deletions Server/ReasnAPI/ReasnAPI/Validators/AddressValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class AddressValidator : AbstractValidator<AddressDto>
private const string CityRegex = @"^\p{Lu}[\p{Ll}'.]+(?:[\s-][\p{L}'.]+)*$";
private const string StreetRegex = @"^[\p{L}\d\s\-/.,#']+(?<![-\s#,])$";
private const string StateRegex = @"^\p{Lu}\p{Ll}+(?:(\s|-)\p{L}+)*$";
private const string ZipCodeRegex = @"^[\p{L}\d\s-]{3,}$";

private const string ZipCodeRegex = @"^[\p{L}\d\s-]{3,}$";

public AddressValidator()
{
RuleFor(a => a.Country)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public RegisterRequestValidator()

RuleFor(r => r.Email)
.NotEmpty()
.MaximumLength(MaxEmailLength)
.EmailAddress();
.EmailAddress()
.MaximumLength(MaxEmailLength);

RuleFor(r => r.Password)
.NotEmpty()
Expand Down

0 comments on commit 642adc3

Please sign in to comment.