diff --git a/api/Migrations/AppDbContextModelSnapshot.cs b/api/Migrations/AppDbContextModelSnapshot.cs index 0d019e7..26b91d3 100644 --- a/api/Migrations/AppDbContextModelSnapshot.cs +++ b/api/Migrations/AppDbContextModelSnapshot.cs @@ -1,5 +1,6 @@ // using System; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -249,6 +250,10 @@ namespace agologum_api.Migrations b.Property("PasswordHash") .HasColumnType("text"); + b.PrimitiveCollection>("Permissions") + .IsRequired() + .HasColumnType("text[]"); + b.Property("PhoneNumber") .HasColumnType("text"); diff --git a/api/Program.cs b/api/Program.cs index 897d159..ea1bbb3 100644 --- a/api/Program.cs +++ b/api/Program.cs @@ -49,11 +49,9 @@ builder.Services.AddAuthentication(options => { builder.Services.AddAuthorization(options => { options.AddPolicy("SensitiveDataRead", policy => - policy.RequireRole("admin", "superuser")); + policy.RequireClaim("permission", Permission.SensitiveData_Read)); options.AddPolicy("SensitiveDataModify", policy => - policy.RequireRole("superuser")); - - // TODO: policies are read at runtime. define policy names in a central place and distribute the symbol + policy.RequireClaim("permission", Permission.SensitiveData_Modify)); }); diff --git a/api/src/Controllers/UsersController.cs b/api/src/Controllers/UsersController.cs index 38f8d7f..4491636 100644 --- a/api/src/Controllers/UsersController.cs +++ b/api/src/Controllers/UsersController.cs @@ -20,7 +20,7 @@ public class UsersController : ControllerBase { service_ = service; } - [Authorize(Policy = "SensitiveDataRead")] + [Authorize(Policy = Permission.SensitiveData_Read)] [HttpGet] public async Task>> getUsers() { List rawArray = await service_.GetAll(); @@ -42,7 +42,7 @@ public class UsersController : ControllerBase { return Ok(dtoArray); } - [Authorize(Policy = "SensitiveDataRead")] + [Authorize(Policy = Permission.SensitiveData_Read)] [HttpGet("{id:int}")] public async Task> getUser(string id) { @@ -60,7 +60,7 @@ public class UsersController : ControllerBase { return Ok(newDto); } - [Authorize(Policy = "SensitiveDataModify")] + [Authorize(Policy = Permission.SensitiveData_Modify)] [HttpDelete("{id}")] public async Task deleteUser(string id) { @@ -68,6 +68,11 @@ public class UsersController : ControllerBase { if (!success) return NotFound(); + // TODO: set safeguard to no delete the current user + return NoContent(); } + + // TODO: add controls on editing roles + } \ No newline at end of file diff --git a/api/src/Models/User.cs b/api/src/Models/User.cs index a2bc758..a173bfb 100644 --- a/api/src/Models/User.cs +++ b/api/src/Models/User.cs @@ -7,6 +7,8 @@ public class User : IdentityUser { public DateTime CreatedAt { get; set; } + public List Permissions { get; set; } = [ Permission.SensitiveData_Read, Permission.SensitiveData_Modify ]; // just seeding these here initially + // properties inherited from IdentityUser: /* AccessFailedCount: Gets or sets the number of failed login attempts for the current user. @@ -49,7 +51,8 @@ public class LoginDto { public class UserDto { - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; // gets compressed to a string + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; // gets compressed to a string' + public List permissions { get; set; } = []; public string? Email { get; set; } = ""; public string Id { get; set; } = ""; public string? UserName { get; set; } = ""; diff --git a/api/src/Services/JwtService.cs b/api/src/Services/JwtService.cs index 093c792..24fa21a 100644 --- a/api/src/Services/JwtService.cs +++ b/api/src/Services/JwtService.cs @@ -31,21 +31,22 @@ public class JwtService { if(user.UserName == null) return null; - var roles = await userManager_.GetRolesAsync(user); - // not too sure var claims = new List { new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) }; - claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); + List permissions = user.Permissions; + foreach(var perm in permissions) { + claims.Add(new Claim("permission", perm)); + } var token = new JwtSecurityToken( issuer: "agologum", audience: "agologum", claims: claims, - expires: DateTime.UtcNow.AddHours(2), // will add a refresher later + expires: DateTime.UtcNow.AddHours(2), signingCredentials: creds ); diff --git a/client/src/api/axios.ts b/client/src/api/axios.ts index 8d05317..601abe3 100644 --- a/client/src/api/axios.ts +++ b/client/src/api/axios.ts @@ -87,4 +87,7 @@ api.interceptors.response.use(response => response, async error => { // mainly f return Promise.reject(error); }) +// TODO: if you get a 403 while navigating then redirect to the last authenticated page +// if you gert a 403 on a form submissio nthen do like an unauthorized popup + export default api;