diff --git a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs index ed38d6b..504f28b 100644 --- a/exercise.tests/IntegrationTests/BaseIntegrationTest.cs +++ b/exercise.tests/IntegrationTests/BaseIntegrationTest.cs @@ -72,9 +72,9 @@ FROM users u protected const int StudentCommentID2 = 3; - protected async Task LoginAndGetToken(string email, string password, bool success = true) + protected async Task LoginAndGetToken(string email, string password, bool success = true, bool longlife = false) { - var loginBody = new LoginRequestDTO { email = email, password = password }; + var loginBody = new LoginRequestDTO { email = email, password = password, longlifetoken = longlife }; var loginRequestBody = new StringContent( JsonSerializer.Serialize(loginBody), Encoding.UTF8, diff --git a/exercise.tests/IntegrationTests/TokenTests.cs b/exercise.tests/IntegrationTests/TokenTests.cs new file mode 100644 index 0000000..1d2b0d8 --- /dev/null +++ b/exercise.tests/IntegrationTests/TokenTests.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace exercise.tests.IntegrationTests +{ + public class TokenTests : BaseIntegrationTest + { + [Test] + public async Task CreateToken_ShouldGenerateValidJwt() + { + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(token); + + + //var realid = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value; + string? email = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + string? role = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value; + var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp")?.Value; + + + using (Assert.EnterMultipleScope()) + { + Assert.That(email, Is.EqualTo(TeacherEmail)); + Assert.That(role, Is.EqualTo("1")); + Assert.That(jwt.ValidTo, Is.GreaterThan(DateTime.UtcNow)); + } + } + + [Test] + public async Task CreateToken_LongLife_ShouldExpireLater() { + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword, true, true); + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(token); + + //var realid = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value; + string? email = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + string? role = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value; + var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp")?.Value; + + //Console.WriteLine(expClaim); + Assert.Multiple(() => + { + Assert.That(email, Is.EqualTo(TeacherEmail)); + Assert.That(role, Is.EqualTo("1")); + Assert.That(jwt.ValidTo, Is.GreaterThan(DateTime.UtcNow.AddDays(6.5))); + Assert.That(jwt.ValidTo, Is.LessThan(DateTime.UtcNow.AddDays(7.5))); + }); + } + + [Test] + public async Task CreateToken_NormalLife_ShouldExpireLater() + { + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + var handler = new JwtSecurityTokenHandler(); + var jwt = handler.ReadJwtToken(token); + + //var realid = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value; + string? email = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + string? role = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value; + var expClaim = jwt.Claims.FirstOrDefault(c => c.Type == "exp")?.Value; + + //Console.WriteLine(expClaim); + + using (Assert.EnterMultipleScope()) + { + Assert.That(email, Is.EqualTo(TeacherEmail)); + Assert.That(role, Is.EqualTo("1")); + Assert.That(jwt.ValidTo, Is.GreaterThan(DateTime.UtcNow.AddMinutes(50))); + Assert.That(jwt.ValidTo, Is.LessThan(DateTime.UtcNow.AddHours(2))); + } + } + + } +} diff --git a/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs b/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs index 5610e1d..81140f1 100644 --- a/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs +++ b/exercise.wwwapi/Authorization/Handlers/UserExistsHandler.cs @@ -27,8 +27,8 @@ protected override async Task HandleRequirementAsync( //_logger.LogWarning("Available claims in token: {Claims}", string.Join(", ", claims)); // Get user ID from claims - var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier) - ?? context.User.FindFirst(ClaimTypes.Sid); + var userIdClaim = context.User.FindFirst(ClaimTypes.Sid) + ?? context.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out int userId)) diff --git a/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs b/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs index 3c59203..201eae1 100644 --- a/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs +++ b/exercise.wwwapi/DTOs/Login/LoginRequestDTO.cs @@ -7,5 +7,6 @@ public class LoginRequestDTO { public string? email { get; set; } public string? password { get; set; } + public bool? longlifetoken { get; set; } } } diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 01b5a71..3341469 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -13,6 +13,7 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; +using System.Threading.Tasks; namespace exercise.wwwapi.EndPoints { @@ -27,8 +28,9 @@ public static void ConfigureAuthApi(this WebApplication app) users.MapGet("/", GetUsers).WithSummary("Get all users by first name if provided"); users.MapGet("/{id:int}", GetUserById).WithSummary("Get user by user id"); users.MapPatch("/{id:int}", UpdateUser).WithSummary("Update a user"); + } - + /// /// Retrieves users, optionally filtered by a case-insensitive search on first name, last name, or full name. /// @@ -51,11 +53,6 @@ public static void ConfigureAuthApi(this WebApplication app) private static async Task GetUsers(IRepository repository, ClaimsPrincipal claims, string? name) { int? id = claims.UserRealId(); - if (id == null) - { - return TypedResults.Ok(new ResponseDTO() - { Message = "Invalid token" }); - } IEnumerable results = await repository.Get(); string? search = name?.Trim().ToLower(); @@ -145,9 +142,12 @@ private static IResult Login(IRepository repository, IMapper mapper, Login return Results.BadRequest(new ResponseDTO() { Message = "Invalid email and/or password provided" }); } - string token = CreateToken(user, config); + string token; + if (request.longlifetoken.GetValueOrDefault()) token = CreateToken(user, config, 7); + else token = CreateToken(user, config, 1.0 / 24); + - ResponseDTO response = new ResponseDTO + ResponseDTO response = new ResponseDTO { Message = "success", Data = new LoginSuccessDTO() @@ -161,7 +161,7 @@ private static IResult Login(IRepository repository, IMapper mapper, Login return Results.Ok(response); } - + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] @@ -269,7 +269,7 @@ private static async Task UpdateUser(IRepository repository, Clai } // Helper, creates jwt tokens - private static string CreateToken(User user, IConfigurationSettings config) + private static string CreateToken(User user, IConfigurationSettings config, double days) { List claims = [ @@ -284,7 +284,7 @@ private static string CreateToken(User user, IConfigurationSettings config) var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature); var token = new JwtSecurityToken( claims: claims, - expires: DateTime.Now.AddDays(1), + expires: DateTime.UtcNow.AddDays(days), signingCredentials: credentials ); var jwt = new JwtSecurityTokenHandler().WriteToken(token);