rework policies to use permissions over roles
Some checks failed
Build and Deploy API / build-and-deploy (push) Failing after 7s
Build and Deploy Frontend / build-and-deploy (push) Successful in 6s

This commit is contained in:
2026-04-22 19:34:55 -05:00
parent 1a0bf385b6
commit 152db3d99f
6 changed files with 27 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
// <auto-generated />
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<string>("PasswordHash")
.HasColumnType("text");
b.PrimitiveCollection<List<string>>("Permissions")
.IsRequired()
.HasColumnType("text[]");
b.Property<string>("PhoneNumber")
.HasColumnType("text");

View File

@@ -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));
});

View File

@@ -20,7 +20,7 @@ public class UsersController : ControllerBase {
service_ = service;
}
[Authorize(Policy = "SensitiveDataRead")]
[Authorize(Policy = Permission.SensitiveData_Read)]
[HttpGet]
public async Task<ActionResult<List<User>>> getUsers() {
List<User> 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<ActionResult<User>> 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<ActionResult> 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
}

View File

@@ -7,6 +7,8 @@ public class User : IdentityUser {
public DateTime CreatedAt { get; set; }
public List<string> 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<string> permissions { get; set; } = [];
public string? Email { get; set; } = "";
public string Id { get; set; } = "";
public string? UserName { get; set; } = "";

View File

@@ -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<Claim> {
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
};
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
List<string> 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
);

View File

@@ -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;