Feature/Auth: last one was authentication, this one is authorization #4
@@ -1,5 +1,6 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
@@ -249,6 +250,10 @@ namespace agologum_api.Migrations
|
|||||||
b.Property<string>("PasswordHash")
|
b.Property<string>("PasswordHash")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<List<string>>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
b.Property<string>("PhoneNumber")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
|||||||
@@ -49,11 +49,9 @@ builder.Services.AddAuthentication(options => {
|
|||||||
builder.Services.AddAuthorization(options => {
|
builder.Services.AddAuthorization(options => {
|
||||||
|
|
||||||
options.AddPolicy("SensitiveDataRead", policy =>
|
options.AddPolicy("SensitiveDataRead", policy =>
|
||||||
policy.RequireRole("admin", "superuser"));
|
policy.RequireClaim("permission", Permission.SensitiveData_Read));
|
||||||
options.AddPolicy("SensitiveDataModify", policy =>
|
options.AddPolicy("SensitiveDataModify", policy =>
|
||||||
policy.RequireRole("superuser"));
|
policy.RequireClaim("permission", Permission.SensitiveData_Modify));
|
||||||
|
|
||||||
// TODO: policies are read at runtime. define policy names in a central place and distribute the symbol
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class UsersController : ControllerBase {
|
|||||||
service_ = service;
|
service_ = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "SensitiveDataRead")]
|
[Authorize(Policy = Permission.SensitiveData_Read)]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<List<User>>> getUsers() {
|
public async Task<ActionResult<List<User>>> getUsers() {
|
||||||
List<User> rawArray = await service_.GetAll();
|
List<User> rawArray = await service_.GetAll();
|
||||||
@@ -42,7 +42,7 @@ public class UsersController : ControllerBase {
|
|||||||
return Ok(dtoArray);
|
return Ok(dtoArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "SensitiveDataRead")]
|
[Authorize(Policy = Permission.SensitiveData_Read)]
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
public async Task<ActionResult<User>> getUser(string id) {
|
public async Task<ActionResult<User>> getUser(string id) {
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ public class UsersController : ControllerBase {
|
|||||||
return Ok(newDto);
|
return Ok(newDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "SensitiveDataModify")]
|
[Authorize(Policy = Permission.SensitiveData_Modify)]
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public async Task<ActionResult> deleteUser(string id) {
|
public async Task<ActionResult> deleteUser(string id) {
|
||||||
|
|
||||||
@@ -68,6 +68,11 @@ public class UsersController : ControllerBase {
|
|||||||
|
|
||||||
if (!success) return NotFound();
|
if (!success) return NotFound();
|
||||||
|
|
||||||
|
// TODO: set safeguard to no delete the current user
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add controls on editing roles
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ public class User : IdentityUser {
|
|||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
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:
|
// properties inherited from IdentityUser:
|
||||||
/*
|
/*
|
||||||
AccessFailedCount: Gets or sets the number of failed login attempts for the current user.
|
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 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? Email { get; set; } = "";
|
||||||
public string Id { get; set; } = "";
|
public string Id { get; set; } = "";
|
||||||
public string? UserName { get; set; } = "";
|
public string? UserName { get; set; } = "";
|
||||||
|
|||||||
@@ -31,21 +31,22 @@ public class JwtService {
|
|||||||
|
|
||||||
if(user.UserName == null) return null;
|
if(user.UserName == null) return null;
|
||||||
|
|
||||||
var roles = await userManager_.GetRolesAsync(user);
|
|
||||||
|
|
||||||
// not too sure
|
// not too sure
|
||||||
var claims = new List<Claim> {
|
var claims = new List<Claim> {
|
||||||
new Claim(ClaimTypes.Name, user.UserName),
|
new Claim(ClaimTypes.Name, user.UserName),
|
||||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
|
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(
|
var token = new JwtSecurityToken(
|
||||||
issuer: "agologum",
|
issuer: "agologum",
|
||||||
audience: "agologum",
|
audience: "agologum",
|
||||||
claims: claims,
|
claims: claims,
|
||||||
expires: DateTime.UtcNow.AddHours(2), // will add a refresher later
|
expires: DateTime.UtcNow.AddHours(2),
|
||||||
signingCredentials: creds
|
signingCredentials: creds
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -87,4 +87,7 @@ api.interceptors.response.use(response => response, async error => { // mainly f
|
|||||||
return Promise.reject(error);
|
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;
|
export default api;
|
||||||
|
|||||||
Reference in New Issue
Block a user