using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using agologumApi.Models; using agologumApi.Services; [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { // identity things private readonly UserManager userManager_; private readonly SignInManager signInManager_; // services private readonly JwtService jwt_; private readonly UserService userService_; // class constructor (where are my initializer lists man) public AuthController(UserManager userManager, SignInManager signInManager, JwtService jwt, UserService userService) { userManager_ = userManager; signInManager_ = signInManager; jwt_ = jwt; userService_ = userService; } // register endpoint [HttpPost("register")] public async Task Register(RegisterDto dto) { // create a new user out of the dto from the request User user = new User { UserName = dto.UserName, Email = dto.Email, CreatedAt = DateTime.UtcNow // yeah why not utc }; // assigning roles to user. create a user starting with x to give it permissions to read sensitive data if(dto.UserName.StartsWith("x")) { user.Permissions = new List { Permission.SensitiveData_Read }; } // use Identity's user manager to add to db; error check if failed var result = await userManager_.CreateAsync(user, dto.Password); if(!result.Succeeded) return BadRequest(result.Errors); // respond to post as necessary return CreatedAtAction( nameof(Register), new { id = user.Id } ); } // login endpoint [HttpPost("login")] public async Task Login(LoginDto dto) { // get the user from the database given the username var user = await userManager_.FindByNameAsync(dto.UserName); // user not found with that name if (user == null) return Unauthorized(); // unauthorized instead of not found to not give away info // use identity's password validation var result = await signInManager_.CheckPasswordSignInAsync(user, dto.Password, false); // if failed then youre not real ! if(!result.Succeeded) return Unauthorized(); // login sucess, give you an authentication token var accessToken = await jwt_.GenerateJwt(user); var refreshToken = jwt_.GenerateRefreshToken(); // the refresh token is good enough to refresh your access token RefreshToken newTokenObject = new RefreshToken { Token = refreshToken, UserId = user.Id, CreatedAt = DateTime.UtcNow, ExpiresAt = DateTime.UtcNow.AddDays(30), IsRevoked = false }; await jwt_.AddRefreshToken(newTokenObject); // the jwt says we trust who you are and can substitute it for login // contains permissions claims too // return both access and refresh token return Ok(new { accessToken, refreshToken }); } // logout endpoint [Authorize] // authorize is handled by middleware [HttpPost("logout")] public async Task Logout(string refreshTokenString) { // revoke refresh token bool success = await jwt_.RevokeRefreshToken(refreshTokenString); if(!success) return NotFound(); // frontend refreshes page and detects logout return Ok(); } // refresh token endpoint [HttpPost("refresh")] // allow-anonymous by default public async Task Refresh(TokenDto request) { // reached when the frontend gets an unauthorized response and autoattempts to refresh if available // get token from request and check if its valid RefreshToken? storedToken = await jwt_.GetRefreshToken(request.RefreshToken); if (storedToken == null) return Unauthorized(); bool valid = (storedToken.IsRevoked) || (storedToken.ExpiresAt < DateTime.UtcNow); if(!valid) return Unauthorized(); // TODO: delete the invalid token // get user from the token and give them new tokens User? user = await jwt_.GetUser(storedToken.UserId); if(user == null) return NotFound(); string? newAccessToken = await jwt_.GenerateJwt(user); if(newAccessToken == null) return NotFound(); string newRefreshToken = jwt_.GenerateRefreshToken(); // construct new token storedToken.IsRevoked = true; RefreshToken newTokenObject = new RefreshToken { Token = newRefreshToken, UserId = storedToken.UserId, CreatedAt = DateTime.UtcNow, ExpiresAt = DateTime.UtcNow.AddDays(30), IsRevoked = false }; await jwt_.AddRefreshToken(newTokenObject); // return new tokens return Ok(new { accessToken = newAccessToken, refreshToken = newRefreshToken }); } // TODO // email verification // password reset // oh hell naw 2FA I do not care enough }