diff --git a/Controllers/AuthController.cs b/Controllers/AuthController.cs index 9e31013..967c5de 100644 --- a/Controllers/AuthController.cs +++ b/Controllers/AuthController.cs @@ -4,6 +4,7 @@ using QuotifyBE.Data; using QuotifyBE.Entities; using QuotifyBE.DTOs; using System.Threading.Tasks; +using QuotifyBE.Mapping; namespace QuotifyBE.Controllers; @@ -35,13 +36,13 @@ public class AuthController : ControllerBase /// in the Authorization header, e.g.: Authorization: bearer {jwt} /// /// User's credentials (email and password) - /// JWT valid for 5 minutes. - /// Returned on request with valid credentials + /// JWT valid for 5 minutes and basic user data. + /// Returned on request with valid credentials. Contains the token, but also user data. /// Returned on request with missing form data (email, password or both) /// Returned on request with unknown pair of email and password (wrong password) /// Returned on request with unknwon email [HttpPost("login")] - [ProducesResponseType(200)] + [ProducesResponseType(typeof(SuccessfulLoginDTO), 200)] [ProducesResponseType(typeof(ErrorDTO), 400)] [ProducesResponseType(typeof(ErrorDTO), 401)] [ProducesResponseType(typeof(ErrorDTO), 404)] @@ -60,19 +61,22 @@ public class AuthController : ControllerBase return NotFound(new {status = "error", error_msg = "User not found"}); } + // Hash the password and compare with the user-provided one string hashedFormPassword = guhf.HashWithSHA512(formUser.Password); if (hashedFormPassword == user.PasswordHash) { // All set - generate the token and return it var token = guhf.GenerateJwtToken(formUser.Email); - return Ok(new { status = "ok", token }); + SuccessfulLoginDTO response = user.ToSuccessfulLoginDTO(token); + + return Ok(response); } else return Unauthorized(new {status = "error", error_msg = "Unknown pair of email and password"}); } // GET /api/v1/auth/some_values /// - /// Dummy, authed endpoint + /// [AUTHED] Dummy, authed endpoint /// /// /// Dummy, authed endpoint used to test JWTs. diff --git a/Controllers/QuoteController.cs b/Controllers/QuoteController.cs index 0665bf9..14d38e5 100644 --- a/Controllers/QuoteController.cs +++ b/Controllers/QuoteController.cs @@ -33,7 +33,7 @@ public class QuotesController : ControllerBase /// The page number /// A page (10 quotes) /// Returned on valid request - /// Returned when requested page is invalid or does not exist + /// Returned when requested page is invalid [HttpGet("page/{page_no}")] [ProducesResponseType(typeof(List), 200)] [ProducesResponseType(typeof(ErrorDTO), 404)] @@ -44,21 +44,23 @@ public class QuotesController : ControllerBase if (page_no <= 0) { - return NotFound(new ErrorDTO { Status = "error", error_msg = "Numer strony musi być większy niż 0." }); + return NotFound(new ErrorDTO { Status = "error", Error_msg = "Numer strony musi być większy niż 0" }); } - var quotes = await _db.Quotes.Include(q => q.QuoteCategories).ThenInclude(qc => qc.Category).Include(q => q.User).Include(q => q.Image).OrderBy(q => q.Id).Skip((page_no-1)*PageSize).Take(PageSize).ToListAsync(); + var quotes = await _db.Quotes + .Include(q => q.QuoteCategories) + .ThenInclude(qc => qc.Category) + .Include(q => q.User) + .Include(q => q.Image) + .OrderBy(q => q.Id) + .Skip((page_no - 1) * PageSize) + .Take(PageSize) + .ToListAsync(); + var result = quotes + .Select(q => q.ToQuoteShortDTO()) + .ToList(); - - if (quotes == null || totalQuotes == 0) - { - return NotFound(new ErrorDTO { Status = "error", error_msg = "Brak cytatów na tej stronie." }); - } - var result = quotes.Select(q => q.ToQuoteShortDTO(_db)).ToList(); - - //return NotFound(new { status = "error", error_msg = "Not implemented" }); return Ok(result); - } @@ -86,29 +88,31 @@ public class QuotesController : ControllerBase if (quote == null) return NotFound(new { status = "error", error_msg = "Quote not found" }); - return Ok(quote.ToQuoteShortDTO(_db)); + return Ok(quote.ToQuoteShortDTO()); } // POST /api/v1/quotes/new /// - /// Add a new quote + /// [AUTHED] Add a new quote /// /// Newly created quote's id /// Form data containing required quote information /// Returned on valid request /// Returned when any of the categories does not exist - /// Returned when user's id does not match the creator's id + /// Returned when user's id does not match the creator's id [HttpPost("new")] [Authorize] - [ProducesResponseType(201)] // ? + [ProducesResponseType(201)] // ? FIXME [ProducesResponseType(typeof(ErrorDTO), 400)] - [ProducesResponseType(typeof(ErrorDTO), 401)] + [ProducesResponseType(typeof(ErrorDTO), 403)] public async Task CreateQuote([FromBody] CreateQuoteDTO request) { // Get user ID from claims + // FIXME var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (userIdClaim == null || !int.TryParse(userIdClaim, out int userId)) - return Unauthorized(new {status = "error", error_msg = "Invalid user ID"}); + // https://stackoverflow.com/a/47708867 + return StatusCode(403, new ErrorDTO { Status = "error", Error_msg = "Invalid user ID" }); // Find or create image Image? image = null; @@ -140,7 +144,7 @@ public class QuotesController : ControllerBase { var categoryExists = await _db.Categories.AnyAsync(c => c.Id == categoryId); if (!categoryExists) - return BadRequest(new {status = "error", error_msg = $"Category ID {categoryId} not found"}); + return BadRequest(new ErrorDTO { Status = "error", Error_msg = $"Category ID {categoryId} not found"}); quote.QuoteCategories.Add(new QuoteCategory { @@ -170,12 +174,11 @@ public class QuotesController : ControllerBase { var totalQuotes = await _db.Quotes.CountAsync(); if (totalQuotes == 0) - return NotFound(new { status = "error", error_msg = "No quotes to choose from" }); + return NotFound(new ErrorDTO { Status = "error", Error_msg = "No quotes to choose from" }); var random = new Random(); var skip = random.Next(0, totalQuotes); - // FIXME var quote = await _db.Quotes .Include(q => q.QuoteCategories!) .ThenInclude(qc => qc.Category) @@ -184,7 +187,7 @@ public class QuotesController : ControllerBase .FirstOrDefaultAsync(); if (quote == null) - return NotFound(); + return NotFound(new ErrorDTO { Status = "error", Error_msg = "Unknown error - couldn't get quote"}); Image? image = null; if (quote.ImageId != 0) diff --git a/Controllers/Seed.cs b/Controllers/Seed.cs index 685e66f..d230c29 100644 --- a/Controllers/Seed.cs +++ b/Controllers/Seed.cs @@ -28,8 +28,8 @@ namespace QuotifyBE.Controllers { Name="admin", Email = "admin@mail.com", - PasswordHash = guhf.HashWithSHA512("admin") - + // hashed twice, once by frontend, and second time by backend + PasswordHash = guhf.HashWithSHA512(guhf.HashWithSHA512("admin")) }; _db.Users.Add(Admin); await _db.SaveChangesAsync(); diff --git a/DTOs/ErrorDTO.cs b/DTOs/ErrorDTO.cs index a7dda34..7e46195 100644 --- a/DTOs/ErrorDTO.cs +++ b/DTOs/ErrorDTO.cs @@ -2,6 +2,7 @@ namespace QuotifyBE.DTOs; public record class ErrorDTO { - public string Status { get; set; } - public string error_msg { get; set; } + required public string Status { get; set; } + required public string Error_msg { get; set; } + } diff --git a/DTOs/SuccessfulLoginDTO.cs b/DTOs/SuccessfulLoginDTO.cs new file mode 100644 index 0000000..10ea3bf --- /dev/null +++ b/DTOs/SuccessfulLoginDTO.cs @@ -0,0 +1,9 @@ +namespace QuotifyBE.DTOs; + +public record class SuccessfulLoginDTO +{ + required public string Status { get; set; } + required public string Token { get; set; } + required public UserInfoDTO User { get; set; } + +}; diff --git a/DTOs/UserInfoDTO.cs b/DTOs/UserInfoDTO.cs new file mode 100644 index 0000000..8c1c416 --- /dev/null +++ b/DTOs/UserInfoDTO.cs @@ -0,0 +1,9 @@ +namespace QuotifyBE.DTOs; + +public record class UserInfoDTO +{ + public int Id { get; set; } + required public string Name { get; set; } + required public string Email { get; set; } + +}; diff --git a/Entities/User.cs b/Entities/User.cs index 5ddf67f..d35a09a 100644 --- a/Entities/User.cs +++ b/Entities/User.cs @@ -1,4 +1,4 @@ -namespace QuotifyBE.Entities +namespace QuotifyBE.Entities { public class User { diff --git a/Mapping/QuoteMapping.cs b/Mapping/QuoteMapping.cs index 0887ef1..278ed9d 100644 --- a/Mapping/QuoteMapping.cs +++ b/Mapping/QuoteMapping.cs @@ -8,7 +8,7 @@ namespace QuotifyBE.Mapping; public static class QuoteMapping { - public static QuoteShortDTO ToQuoteShortDTO(this Quote quote, ApplicationDbContext db) + public static QuoteShortDTO ToQuoteShortDTO(this Quote quote) { List categoryNames = []; diff --git a/Mapping/UserMapping.cs b/Mapping/UserMapping.cs new file mode 100644 index 0000000..362311b --- /dev/null +++ b/Mapping/UserMapping.cs @@ -0,0 +1,29 @@ +using QuotifyBE.DTOs; +using QuotifyBE.Entities; + +namespace QuotifyBE.Mapping; + +public static class UserMapping +{ + public static SuccessfulLoginDTO ToSuccessfulLoginDTO(this User user, string token) + { + + return new SuccessfulLoginDTO + { + Status = "ok", + Token = token, + User = user.ToUserInfoDTO() + }; + } + + public static UserInfoDTO ToUserInfoDTO(this User user) + { + + return new UserInfoDTO + { + Id = user.Id, + Name = user.Name, + Email = user.Email + }; + } +}