diff --git a/exercise.tests/IntegrationTests/PostTests.cs b/exercise.tests/IntegrationTests/PostTests.cs index 48e7e8f..b7c5f8c 100644 --- a/exercise.tests/IntegrationTests/PostTests.cs +++ b/exercise.tests/IntegrationTests/PostTests.cs @@ -44,7 +44,7 @@ public async Task GetAllPosts() using (Assert.EnterMultipleScope()) { Assert.That(post?["id"]?.GetValue(), Is.GreaterThan(0)); - Assert.That(post["content"]?.GetValue(), Is.Not.Null); + Assert.That(post?["content"]?.GetValue(), Is.Not.Null); Assert.That(post["numLikes"]?.GetValue(), Is.Not.Null); Assert.That(post["createdAt"], Is.Not.Null); } @@ -569,7 +569,6 @@ public async Task GetPostsByUser_NotFound() [Test] public async Task GetCommentsByUser_Success() { - //Assert get token var token = await LoginAndGetToken("oyvind.perez1@example.com", "SuperHash!4"); diff --git a/exercise.tests/IntegrationTests/UserTests.cs b/exercise.tests/IntegrationTests/UserTests.cs index 4837cc6..50f92d5 100644 --- a/exercise.tests/IntegrationTests/UserTests.cs +++ b/exercise.tests/IntegrationTests/UserTests.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using exercise.tests.Helpers; +using exercise.wwwapi.DTOs; namespace exercise.tests.IntegrationTests { @@ -13,26 +14,8 @@ namespace exercise.tests.IntegrationTests /// Integration tests exercising the user management endpoints end-to-end via the API surface. /// [TestFixture] - public class UserTests + public class UserTests : BaseIntegrationTest { - private WebApplicationFactory _factory; - private HttpClient _client; - - [SetUp] - public void SetUp() - { - // Arrange - _factory = new WebApplicationFactory(); - _client = _factory.CreateClient(); - } - - [TearDown] - public void TearDown() - { - _client.Dispose(); - _factory.Dispose(); - } - /// /// Confirms that valid registration payloads yield an HTTP 201 Created response. /// @@ -193,19 +176,18 @@ public async Task Login_failure(string email, string password) [Test] public async Task UpdateUserSuccess() { - var fieldsToUpdate = new Dictionary + var fieldsToUpdate = new UserPatchDTO() { - { "username", "roman-olsen13"}, - { "email", "roman.olsen13@example.com"}, - { "password", "aGoodPassword!200" }, - { "role", 0} + Username = "roman-olsen13", + Email = "roman.olsen13@example.com", + Password = "aGoodPassword!200" , + Role = 0 }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); } @@ -216,14 +198,12 @@ public async Task UpdateUserSuccess() [Test] public async Task UpdateUserNullFieldsOnly() { - var fieldsToUpdate = new Dictionary { }; - - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - + var fieldsToUpdate = new UserPatchDTO(); + + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); int userId = 1; - var response = await _client.PatchAsync($"/users/{userId}", content); - + + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -233,17 +213,15 @@ public async Task UpdateUserNullFieldsOnly() [Test] public async Task UpdateUserInvalidUsername() { - var fieldsToUpdate = new Dictionary + var fieldsToUpdate = new UserPatchDTO() { - { "username", "roman--olsen13"} + Username = "roman--olsen13" }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); - + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -253,17 +231,15 @@ public async Task UpdateUserInvalidUsername() [Test] public async Task UpdateUserInvalidGitHubUsername() { - var fieldsToUpdate = new Dictionary + var fieldsToUpdate = new UserPatchDTO() { - { "gitHubUsername", "roman--olsen13"} + GithubUsername = "roman--olsen13" }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); - + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -273,16 +249,15 @@ public async Task UpdateUserInvalidGitHubUsername() [Test] public async Task UpdateUserInvalidEmail() { - var fieldsToUpdate = new Dictionary + var fieldsToUpdate = new UserPatchDTO() { - { "email", "roman.olsen13@.e.com"} + Email = "roman.olsen13@.e.com" }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); + var token = await LoginAndGetToken(TeacherEmail, TeacherPassword); int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -298,12 +273,11 @@ public async Task UpdateUserInvalidPassword() { "password", "nope!"} }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); + var token = await LoginAndGetToken(TeacherEmail, TeacherPassword); int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); - + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -318,11 +292,10 @@ public async Task UpdateUserInvalidRole() { "role", 10} }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); + var token = await LoginAndGetToken(TeacherEmail, TeacherPassword); int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -338,11 +311,10 @@ public async Task UpdateUserUsernameExists() { "username", "nigel-nowak2"} }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); + var token = await LoginAndGetToken(TeacherEmail, TeacherPassword); int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -353,16 +325,15 @@ public async Task UpdateUserUsernameExists() [Test] public async Task UpdateUserGitHubUsernameExists() { - var fieldsToUpdate = new Dictionary + var fieldsToUpdate = new UserPatchDTO() { - { "gitHubUsername", "nigel-nowak2"} + GithubUsername = "nigel-nowak2" }; - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -373,16 +344,15 @@ public async Task UpdateUserGitHubUsernameExists() [Test] public async Task UpdateUserEmailExists() { - var fieldsToUpdate = new Dictionary + var fieldsToUpdate = new UserPatchDTO() { - { "email", "nigel.nowak2@example.com"} + Email="nigel.nowak2@example.com" }; - - var json = JsonSerializer.Serialize(fieldsToUpdate); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - + int userId = 13; - var response = await _client.PatchAsync($"/users/{userId}", content); + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + + var response = await SendAuthenticatedPatchAsync($"/users/{userId}", token, fieldsToUpdate); Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); } @@ -396,7 +366,9 @@ public async Task UpdateUserEmailExists() [TestCase("10000000", HttpStatusCode.NotFound)] public async Task GetUserByIdTest(string id, HttpStatusCode responseStatus) { - var response = await _client.GetAsync($"/users/{id}"); + string token = await LoginAndGetToken(TeacherEmail, TeacherPassword); + + var response = await SendAuthenticatedGetAsync($"/users/{id}", token); Assert.That(response.StatusCode, Is.EqualTo(responseStatus)); } diff --git a/exercise.wwwapi/Endpoints/LogEndpoints.cs b/exercise.wwwapi/Endpoints/LogEndpoints.cs index 9fce6c0..828e681 100644 --- a/exercise.wwwapi/Endpoints/LogEndpoints.cs +++ b/exercise.wwwapi/Endpoints/LogEndpoints.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; namespace exercise.wwwapi.Endpoints { @@ -10,10 +11,12 @@ public static void ConfigureLogEndpoints(this WebApplication app) logs.MapPost("/", CreateDeliveryLog).WithSummary("Create a delivery log"); } + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - public static async Task CreateDeliveryLog() + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + private static Task CreateDeliveryLog() { - return TypedResults.Ok(); + return Task.FromResult(TypedResults.Ok()); } } } diff --git a/exercise.wwwapi/Endpoints/PostEndpoints.cs b/exercise.wwwapi/Endpoints/PostEndpoints.cs index 63401ae..6371d88 100644 --- a/exercise.wwwapi/Endpoints/PostEndpoints.cs +++ b/exercise.wwwapi/Endpoints/PostEndpoints.cs @@ -37,9 +37,10 @@ public static void ConfigurePostEndpoints(this WebApplication app) [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public static IResult CreatePost( + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + private static IResult CreatePost( IRepository userservice, IRepository postservice, IMapper mapper, @@ -74,9 +75,11 @@ CreatePostDTO request return Results.Created($"/posts/{post.Id}", response); } + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - public static IResult GetAllPosts(IRepository service, IMapper mapper) + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + private static IResult GetAllPosts(IRepository service, IMapper mapper) { IEnumerable results = service.GetWithIncludes(q => q.Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User)); IEnumerable postDTOs = mapper.Map>(results); @@ -91,9 +94,10 @@ public static IResult GetAllPosts(IRepository service, IMapper mapper) [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public static IResult UpdatePost(IRepository service, IMapper mapper, ClaimsPrincipal user, int postid, UpdatePostDTO request) + private static IResult UpdatePost(IRepository service, IMapper mapper, ClaimsPrincipal user, int postid, UpdatePostDTO request) { if (string.IsNullOrWhiteSpace(request.Content)) return TypedResults.BadRequest(new ResponseDTO{ Message = "Content cannot be empty" @@ -128,8 +132,9 @@ public static IResult UpdatePost(IRepository service, IMapper mapper, Clai [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] private static IResult DeletePost(IRepository service, ClaimsPrincipal user, int postid) { Post? post = service.GetById(postid, q => q.Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User)); @@ -151,8 +156,9 @@ private static IResult DeletePost(IRepository service, ClaimsPrincipal use [Authorize] [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] private static IResult AddCommentToPost( IRepository commentService, IRepository postService, @@ -203,6 +209,7 @@ private static IResult AddCommentToPost( [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] private static IResult GetCommentsForPost(IRepository postservice, IMapper mapper, int postId) { Post? post = postservice.GetById(postId, q => q.Include(p => p.User).Include(p => p.Comments).ThenInclude(c => c.User)); @@ -211,10 +218,12 @@ private static IResult GetCommentsForPost(IRepository postservice, IMapper List commentsDTO = mapper.Map>(comments); return TypedResults.Ok(new ResponseDTO> { Message = "Success", Data = commentsDTO }); } - + + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] private static IResult UpdateComment( IRepository service, IMapper mapper, @@ -252,6 +261,7 @@ private static IResult UpdateComment( [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] private static IResult DeleteCommentById(IRepository service, ClaimsPrincipal user, int commentId) { @@ -278,6 +288,7 @@ private static IResult DeleteCommentById(IRepository service, Claim [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] private static IResult GetPostsByUser(IRepository service, IMapper mapper, int userid) { @@ -295,6 +306,7 @@ private static IResult GetPostsByUser(IRepository service, IMapper mapper, [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] private static IResult GetCommentsByUser(IRepository service, IMapper mapper, int userid) { diff --git a/exercise.wwwapi/Endpoints/SecureApi.cs b/exercise.wwwapi/Endpoints/SecureApi.cs index 4bf7a74..4d63b1b 100644 --- a/exercise.wwwapi/Endpoints/SecureApi.cs +++ b/exercise.wwwapi/Endpoints/SecureApi.cs @@ -12,16 +12,14 @@ public static class SecureApi public static void ConfigureSecureApi(this WebApplication app) { app.MapGet("message", GetMessage); - - } + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - private static async Task GetMessage(IRepository service, ClaimsPrincipal user) + private static Task GetMessage(IRepository service, ClaimsPrincipal user) { - - return TypedResults.Ok(new { LoggedIn = true, UserId = user.UserRealId().ToString(), Email = $"{user.Email()}", Message = "Pulled the userid and email out of the claims" }); + return Task.FromResult(TypedResults.Ok(new { LoggedIn = true, UserId = user.UserRealId().ToString(), Email = $"{user.Email()}", Message = "Pulled the userid and email out of the claims" })); } } } diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index dd1ee34..01b5a71 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -20,23 +20,49 @@ public static class UserEndpoints { public static void ConfigureAuthApi(this WebApplication app) { + app.MapPost("/login", Login).WithSummary("Localhost Login"); + var users = app.MapGroup("users"); users.MapPost("/", Register).WithSummary("Create user"); users.MapGet("/", GetUsers).WithSummary("Get all users by first name if provided"); - users.MapGet("/{id}", GetUserById).WithSummary("Get user by user id"); - users.MapPatch("/{id}", UpdateUser).WithSummary("Update a user"); - app.MapPost("/login", Login).WithSummary("Localhost Login"); + 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. + /// + /// + /// The user repository used to fetch users. + /// + /// + /// -user that authorizes the user to use this endpoint. + /// + /// + /// Optional search term to filter users by first name, last name, or "FirstName LastName". + /// + /// + /// with a 200 OK containing a of on success, + /// or a 401 Unauthorized if the caller is not authorized. + /// [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - private static async Task GetUsers(IRepository service, string? name, ClaimsPrincipal user) + private static async Task GetUsers(IRepository repository, ClaimsPrincipal claims, string? name) { - IEnumerable results = await service.Get(); + 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(); + UsersSuccessDTO userData = new UsersSuccessDTO() { - Users = !string.IsNullOrWhiteSpace(name) + Users = !string.IsNullOrWhiteSpace(search) ? results.Where(i => (i.FirstName.ToLower().Contains(search)) || (i.LastName.ToLower().Contains(search)) || @@ -44,42 +70,41 @@ private static async Task GetUsers(IRepository service, string? n ).ToList() : results.ToList() }; + ResponseDTO response = new ResponseDTO() { Message = "success", Data = userData }; + return TypedResults.Ok(response); } - + + // TODO: Docstring + // Public endpoint, not authorized [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status409Conflict)] - private static IResult Register(RegisterRequestDTO request, IRepository service, IMapper mapper) + private static IResult Register(IRepository repository, IMapper mapper, RegisterRequestDTO request) { - - // syntax checks - // check valid password + // Validate password string validationResult = Validator.Password(request.password); if (validationResult != "Accepted") return TypedResults.BadRequest(new ResponseDTO() { Message = validationResult }); - // check valid email + + // Validate email string emailValidation = Validator.Email(request.email); if (emailValidation != "Accepted") return TypedResults.BadRequest(new ResponseDTO() { Message = emailValidation }); - - // check if email is in database - var emailExists = service.GetAllFiltered(q => q.Email == request.email); + var emailExists = repository.GetAllFiltered(q => q.Email == request.email); if (emailExists.Count() != 0) return Results.Conflict(new ResponseDTO() { Message = "Fail" }); - - - string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.password); - - var user = new User(); - - user.PasswordHash = passwordHash; - user.Email = request.email; - - service.Insert(user); - service.Save(); + + // Put user + var user = new User + { + PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.password), + Email = request.email + }; + repository.Insert(user); + repository.Save(); ResponseDTO response = new ResponseDTO { @@ -89,11 +114,12 @@ private static IResult Register(RegisterRequestDTO request, IRepository se return Results.Created($"/users/{user.Id}", response); } - + + // TODO: Docstring + // Public endpoint (not authorized) [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - private static IResult Login(LoginRequestDTO request, IRepository service, IConfigurationSettings config, IMapper mapper) + private static IResult Login(IRepository repository, IMapper mapper, LoginRequestDTO request, IConfigurationSettings config) { //if (string.IsNullOrEmpty(request.username)) request.username = request.email; @@ -107,12 +133,10 @@ private static IResult Login(LoginRequestDTO request, IRepository service, //email doesn't exist, should probably be 404 user not found, but should maybe just say invalid email or password //check if email is in database - var emailExists = service.GetAllFiltered(q => q.Email == request.email); + var emailExists = repository.GetAllFiltered(q => q.Email == request.email); if (emailExists.Count() == 0) return TypedResults.BadRequest(new ResponseDTO() { Message = "Invalid email and/or password provided" }); - - - - User user = service.GetAll().FirstOrDefault(u => u.Email == request.email)!; + + User user = repository.GetAll().FirstOrDefault(u => u.Email == request.email)!; if (!BCrypt.Net.BCrypt.Verify(request.password, user.PasswordHash)) @@ -137,11 +161,14 @@ private static IResult Login(LoginRequestDTO request, IRepository service, return Results.Ok(response); } + + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public static async Task GetUserById(IRepository service, int id, IMapper mapper) + private static async Task GetUserById(IRepository repository, ClaimsPrincipal claims, IMapper mapper, int id) { - var user = service.GetById(id); + var user = repository.GetById(id); if (user == null) return TypedResults.NotFound(new ResponseDTO { Message = "User not found" }); ResponseDTO response = new ResponseDTO @@ -152,11 +179,13 @@ public static async Task GetUserById(IRepository service, int id, return TypedResults.Ok(response); } - + + [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public static async Task UpdateUser(IRepository repository, int id, UserPatchDTO userPatch, IMapper mapper) + private static async Task UpdateUser(IRepository repository, ClaimsPrincipal claims, IMapper mapper, int id, UserPatchDTO userPatch) { if (userPatch.GetType().GetProperties().Length > 0 && userPatch.GetType().GetProperties().All((p) => p.GetValue(userPatch) == null)) return TypedResults.BadRequest(new ResponseDTO() { Message = "Provide at least one field for update" }); @@ -164,6 +193,15 @@ public static async Task UpdateUser(IRepository repository, int i var user = repository.GetById(id); if (user == null) return TypedResults.NotFound(new ResponseDTO { Message = "User not found" }); + + if (user.Id != claims.UserRealId() && claims.Role() == (int)Roles.student) + { + var forbiddenResponse = new ResponseDTO + { + Message = "You are not authorized to edit this post." + }; + return TypedResults.Json(forbiddenResponse, statusCode: StatusCodes.Status403Forbidden); + } if (userPatch.Username != null && userPatch.Username != user.Username) { @@ -229,16 +267,18 @@ public static async Task UpdateUser(IRepository repository, int i return TypedResults.Ok(response); } + + // Helper, creates jwt tokens private static string CreateToken(User user, IConfigurationSettings config) { - List claims = new List - { + List claims = + [ new Claim(ClaimTypes.Sid, user.Id.ToString()), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Email, user.Email), new Claim(ClaimTypes.Role, ((int)user.Role).ToString()) - }; + ]; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetValue("AppSettings:Token"))); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature); diff --git a/exercise.wwwapi/Endpoints/ValidationEndpoint.cs b/exercise.wwwapi/Endpoints/ValidationEndpoint.cs index 4902855..32861c2 100644 --- a/exercise.wwwapi/Endpoints/ValidationEndpoint.cs +++ b/exercise.wwwapi/Endpoints/ValidationEndpoint.cs @@ -23,7 +23,7 @@ public static void ConfigureValidationEndpoint(this WebApplication app) /// Source: https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/iam-configuration-reference/username-considerations-for-external-authentication /// /// A object used to query the user data source for existing GitHub usernames. - /// The username string to validate. + /// The username string to validate. /// /// 200 OK response with a message if the username is accepted.
/// 400 Bad Request with a message if the username is invalid or if username already exists in database. @@ -34,11 +34,12 @@ public static void ConfigureValidationEndpoint(this WebApplication app) [ProducesResponseType(StatusCodes.Status400BadRequest)] private static IResult ValidateGitUsername(IRepository repository, [FromQuery] string username) { - if (username == null || string.IsNullOrEmpty(username)) return TypedResults.BadRequest(new ResponseDTO() { Message = "Something went wrong!" }); string result = Helpers.Validator.Username(username); if (result != "Accepted") return TypedResults.BadRequest(new ResponseDTO() { Message = result }); + var gitUsernameExists = repository.GetAllFiltered(q => q.GithubUsername == username); if (gitUsernameExists.Count() != 0) return TypedResults.BadRequest(new ResponseDTO() { Message = "GitHub username is already in use" }); + return TypedResults.Ok(new ResponseDTO() { Message = result }); } @@ -57,18 +58,19 @@ private static IResult ValidateGitUsername(IRepository repository, [FromQu [ProducesResponseType(StatusCodes.Status400BadRequest)] private static IResult ValidateEmail(IRepository repository, string email) { - if (email == null || string.IsNullOrEmpty(email)) return TypedResults.BadRequest(new ResponseDTO() { Message = "Something went wrong!" }); string result = Helpers.Validator.Email(email); if (result != "Accepted") return TypedResults.BadRequest(new ResponseDTO() { Message = result }); + var emailExists = repository.GetAllFiltered(q => q.Email == email); if (emailExists.Count() != 0) return TypedResults.BadRequest(new ResponseDTO() { Message = "Email already exists" }); + return TypedResults.Ok(new ResponseDTO() { Message = result }); } /// /// Validates a password using custom password rules. /// - /// A object containing the password to validate. + /// A object containing the password to validate. /// /// 200 OK response if the password is accepted.
/// 400 Bad Request with a message if the password is invalid or if validation fails. @@ -77,14 +79,14 @@ private static IResult ValidateEmail(IRepository repository, string email) /// Password is invalid or validation failed. [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - private static IResult ValidatePassword(PasswordDTO passwordDTO) + private static IResult ValidatePassword(PasswordDTO? passwordDto) { - if (passwordDTO == null || string.IsNullOrEmpty(passwordDTO.password)) - return TypedResults.BadRequest(new ResponseDTO() { Message = "Something went wrong!" }); - string result = Helpers.Validator.Password(passwordDTO.password); - if (result == null) return TypedResults.BadRequest(new ResponseDTO() { Message = "Something went wrong!" }); - else if (result == "Accepted") return TypedResults.Ok(new ResponseDTO() { Message = result }); - else return TypedResults.BadRequest(new ResponseDTO() { Message = result }); + if (passwordDto == null || string.IsNullOrEmpty(passwordDto.password)) return TypedResults.BadRequest(new ResponseDTO() { Message = "Something went wrong!" }); + + string result = Helpers.Validator.Password(passwordDto.password); + if (result == "Accepted") return TypedResults.Ok(new ResponseDTO() { Message = result }); + + return TypedResults.BadRequest(new ResponseDTO() { Message = result }); } /// @@ -104,11 +106,12 @@ private static IResult ValidatePassword(PasswordDTO passwordDTO) [ProducesResponseType(StatusCodes.Status400BadRequest)] private static IResult ValidateUsername(IRepository repository, [FromQuery] string username) { - if (username == null || string.IsNullOrEmpty(username)) return TypedResults.BadRequest(new ResponseDTO() { Message = "Empty input" }); string result = Helpers.Validator.Username(username); if (result != "Accepted") return TypedResults.BadRequest(new ResponseDTO() { Message = result }); + var usernameExists = repository.GetAllFiltered(q => q.Username == username); if (usernameExists.Count() != 0) return TypedResults.BadRequest(new ResponseDTO() { Message = "Username is already in use" }); + return TypedResults.Ok(new ResponseDTO() { Message = result }); } }