From fbd079d337cf68a47516ea04d8e15da77486c2e8 Mon Sep 17 00:00:00 2001 From: Dhananjaya99 <152056742+ashanruu@users.noreply.github.com> Date: Sun, 18 Oct 2026 12:45:58 +0530 Subject: [PATCH] Remove role management from AuthHex; update documentation and user handling to reflect ERP ownership of roles. --- API_DOCUMENTATION.md | 5 + AppDBContext.cs | 11 -- Controllers/APIController.cs | 11 +- Interfaces/IRoleManageRepository.cs | 13 -- Models/Role.cs | 15 -- Models/User.cs | 7 +- Program.cs | 3 - Repos/RoleManageRepository.cs | 43 ----- Repos/UserManageRepository.cs | 1 - .../AltOptionManagerService.cs | 1 - Services/FunctionHandler/RoleManager.cs | 72 --------- Services/RoleManager/RoleManagerService.cs | 151 ------------------ Services/UserManager/UserManagerService.cs | 18 +-- Utility/JwtTokenHelper.cs | 11 +- 14 files changed, 12 insertions(+), 350 deletions(-) delete mode 100644 Interfaces/IRoleManageRepository.cs delete mode 100644 Models/Role.cs delete mode 100644 Repos/RoleManageRepository.cs delete mode 100644 Services/FunctionHandler/RoleManager.cs delete mode 100644 Services/RoleManager/RoleManagerService.cs diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md index ac9c594..6eb60d6 100644 --- a/API_DOCUMENTATION.md +++ b/API_DOCUMENTATION.md @@ -3,6 +3,11 @@ Base route: `api` All endpoints are `[AllowAnonymous]` at the HTTP layer, but individual functions may require a Bearer JWT (read via `HttpContext` claims) — noted per function below. +> **Roles were removed (2026-10).** AuthHex is authentication only: it has no roles, `/api/role` no longer exists, `registerUser` +> no longer needs (and ignores) `roleId`, user payloads carry no `roleId`/`role`, and the JWT has no `RoleId`/`RoleCode` +> claims. The ERP owns roles and permissions (users.RoleId in ERPCore). The role fields and the **/api/role** section +> below describe the old behaviour and are kept only for reference. Migration steps: `Docs/migrations/2026-10-remove-roles.sql`. + ## Common envelope **Request** (`ApiRequest`) diff --git a/AppDBContext.cs b/AppDBContext.cs index 4ce484b..3ff4cf6 100644 --- a/AppDBContext.cs +++ b/AppDBContext.cs @@ -8,7 +8,6 @@ namespace AuthHex { public AppDBContext(DbContextOptions options) : base(options) { } public DbSet Users => Set(); - public DbSet Roles => Set(); public DbSet UserType => Set(); public DbSet UserSessions => Set(); @@ -69,10 +68,6 @@ namespace AuthHex .HasForeignKey(up => up.UserId) .OnDelete(DeleteBehavior.Cascade); - // Configure Role primary key - modelBuilder.Entity() - .HasKey(r => r.RoleId); - // Configure UserType primary key modelBuilder.Entity() .HasKey(ut => ut.UserTypeId); @@ -81,12 +76,6 @@ namespace AuthHex modelBuilder.Entity() .HasKey(u => u.Id); - modelBuilder.Entity() - .HasOne(u => u.Role) - .WithMany() - .HasForeignKey(u => u.RoleId) - .OnDelete(DeleteBehavior.Restrict); - modelBuilder.Entity() .HasOne(u => u.UserType) .WithMany() diff --git a/Controllers/APIController.cs b/Controllers/APIController.cs index 5567d97..cbe8508 100644 --- a/Controllers/APIController.cs +++ b/Controllers/APIController.cs @@ -10,12 +10,11 @@ namespace AuthHex.Controllers [ApiController] [Route("api")] [EnableCors("AllowAll")] - public class APIController(UserManager userManager, RecoveryManager recoveryManager, AltOptionManager altOptionManager, RoleManager roleManager) : ControllerBase + public class APIController(UserManager userManager, RecoveryManager recoveryManager, AltOptionManager altOptionManager) : ControllerBase { private readonly UserManager _userManager = userManager; private readonly RecoveryManager _recoveryManager = recoveryManager; private readonly AltOptionManager _altOptionManager = altOptionManager; - private readonly RoleManager _roleManager = roleManager; [HttpGet("status")] [AllowAnonymous] @@ -76,13 +75,5 @@ namespace AuthHex.Controllers return StatusCode(response.StatusCode, response); } - [HttpPost("role")] - [AllowAnonymous] - public async Task RoleExecute([FromBody] ApiRequest request) - { - var response = await _roleManager.Execute(request, HttpContext); - return StatusCode(response.StatusCode, response); - } - } } diff --git a/Interfaces/IRoleManageRepository.cs b/Interfaces/IRoleManageRepository.cs deleted file mode 100644 index d7de6d4..0000000 --- a/Interfaces/IRoleManageRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AuthHex.Models; - -namespace AuthHex.Interfaces -{ - public interface IRoleManageRepository - { - Task AddRoleAsync(Role role, CancellationToken ct = default); - Task GetRoleByIdAsync(Guid roleId, CancellationToken ct = default); - Task> ListRolesAsync(CancellationToken ct = default); - Task RoleInUseAsync(Guid roleId, CancellationToken ct = default); - Task DeleteRoleAsync(Role role, CancellationToken ct = default); - } -} diff --git a/Models/Role.cs b/Models/Role.cs deleted file mode 100644 index e834360..0000000 --- a/Models/Role.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace AuthHex.Models -{ - public class Role - { - public Guid RoleId { get; set; } - - public string Code { get; set; } = string.Empty; // ADMIN, USER, STAFF - public string?Name { get; set; } = string.Empty; - - public bool? IsSystemRole { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - - } -} diff --git a/Models/User.cs b/Models/User.cs index e3e868f..bbbe765 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -30,10 +30,9 @@ namespace AuthHex.Models public bool IsMfaEnabled { get; set; } public UserMfa? UserMfa { get; set; } - //User Role and Type - public Guid RoleId { get; set; } - public Role? Role { get; set; } - + // Roles are not an authentication concern: the ERP owns a person's role and permissions (users.RoleId in ERPCore). + // AuthHex only says who the person is. (The old Users.RoleId column is left in the table, nullable and unmapped, + // until it is dropped - see Docs/migrations/2026-10-remove-roles.sql.) public Guid UserTypeId { get; set; } public UserType? UserType { get; set; } diff --git a/Program.cs b/Program.cs index 174b969..73c076a 100644 --- a/Program.cs +++ b/Program.cs @@ -57,18 +57,15 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); var configuredOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? Array.Empty(); var allowedOrigins = configuredOrigins diff --git a/Repos/RoleManageRepository.cs b/Repos/RoleManageRepository.cs deleted file mode 100644 index 0bc9a00..0000000 --- a/Repos/RoleManageRepository.cs +++ /dev/null @@ -1,43 +0,0 @@ -using AuthHex.Interfaces; -using AuthHex.Models; -using Microsoft.EntityFrameworkCore; - -namespace AuthHex.Repos -{ - public class RoleManageRepository : IRoleManageRepository - { - private readonly AppDBContext _dbContext; - - public RoleManageRepository(AppDBContext dbContext) - { - _dbContext = dbContext; - } - - public Task AddRoleAsync(Role role, CancellationToken ct = default) - { - _dbContext.Roles.Add(role); - return Task.FromResult(role); - } - - public async Task GetRoleByIdAsync(Guid roleId, CancellationToken ct = default) - { - return await _dbContext.Roles.FirstOrDefaultAsync(r => r.RoleId == roleId, ct); - } - - public async Task> ListRolesAsync(CancellationToken ct = default) - { - return await _dbContext.Roles.OrderBy(r => r.Code).ToListAsync(ct); - } - - public async Task RoleInUseAsync(Guid roleId, CancellationToken ct = default) - { - return await _dbContext.Users.AnyAsync(u => u.RoleId == roleId, ct); - } - - public Task DeleteRoleAsync(Role role, CancellationToken ct = default) - { - _dbContext.Roles.Remove(role); - return Task.CompletedTask; - } - } -} diff --git a/Repos/UserManageRepository.cs b/Repos/UserManageRepository.cs index b30b908..1d1db84 100644 --- a/Repos/UserManageRepository.cs +++ b/Repos/UserManageRepository.cs @@ -70,7 +70,6 @@ namespace AuthHex.Repos public async Task GetUserByIdAsync(Guid userId, CancellationToken ct = default) { return await _dbContext.Users - .Include(u => u.Role) .Include(u => u.UserType) .FirstOrDefaultAsync(u => u.Id == userId, ct); } diff --git a/Services/AltOptionManager/AltOptionManagerService.cs b/Services/AltOptionManager/AltOptionManagerService.cs index 95dc478..d87e37c 100644 --- a/Services/AltOptionManager/AltOptionManagerService.cs +++ b/Services/AltOptionManager/AltOptionManagerService.cs @@ -377,7 +377,6 @@ public class AltOptionManagerService user.EmailVerified, user.MobileNumberVerified, user.IsMfaEnabled, - RoleId = user.RoleId, UserTypeId = user.UserTypeId } } diff --git a/Services/FunctionHandler/RoleManager.cs b/Services/FunctionHandler/RoleManager.cs deleted file mode 100644 index 3d6e071..0000000 --- a/Services/FunctionHandler/RoleManager.cs +++ /dev/null @@ -1,72 +0,0 @@ -using AuthHex.Models.IOs; -using AuthHex.Services.RoleManager; - -namespace AuthHex.Services.FunctionHandler -{ - public class RoleManager - { - public readonly Dictionary>> _functionHandler; - - public RoleManager(RoleManagerService roleManagerService) - { - _functionHandler = new() - { - { "createRole", roleManagerService.CreateRole }, - { "getRole", roleManagerService.GetRole }, - { "listRoles", async (payload, context) => await roleManagerService.ListRoles(context) }, - { "updateRole", roleManagerService.UpdateRole }, - { "deleteRole", roleManagerService.DeleteRole } - }; - } - - public async Task Execute(ApiRequest request, HttpContext httpContext) - { - if (!_functionHandler.TryGetValue(request.FunctionName, out var handler)) - { - return new ApiResponse - { - StatusCode = 404, - Message = $"Function '{request.FunctionName}' not found.", - Data = null - }; - } - try - { - var results = await handler(request.Payload, httpContext); - return new ApiResponse - { - StatusCode = 200, - Message = "success", - Data = results - }; - } - catch (KeyNotFoundException ex) - { - return new ApiResponse - { - StatusCode = 400, - Message = ex.Message, - Data = null - }; - } - catch (InvalidOperationException ex) - { - return new ApiResponse - { - StatusCode = 400, - Message = ex.Message, - Data = null - }; - } - catch (ArgumentException ex) - { - return new ApiResponse - { - StatusCode = 500, - Message = ex.Message, - Data = null - }; - } - } - } -} diff --git a/Services/RoleManager/RoleManagerService.cs b/Services/RoleManager/RoleManagerService.cs deleted file mode 100644 index 6a00889..0000000 --- a/Services/RoleManager/RoleManagerService.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System.Text.Json; -using AuthHex.Infra.UoW; -using AuthHex.Interfaces; -using AuthHex.Models; -using Core_Archi.Helpers; -using Microsoft.AspNetCore.Http; - -namespace AuthHex.Services.RoleManager; - -public class RoleManagerService -{ - private readonly IRoleManageRepository _repository; - private readonly IUnitOfWork _uow; - - public RoleManagerService(IRoleManageRepository repository, IUnitOfWork uow) - { - _repository = repository; - _uow = uow; - } - - public async Task CreateRole(object payload, HttpContext httpContext) - { - await _uow.BeginAsync(); - try - { - var data = DeserializePayload.Deserialize(payload); - - var code = data.ContainsKey("code") ? data["code"].GetString() : null; - var name = data.ContainsKey("name") ? data["name"].GetString() : null; - var isSystemRole = data.ContainsKey("isSystemRole") - && data["isSystemRole"].ValueKind != JsonValueKind.Null - && data["isSystemRole"].GetBoolean(); - - if (string.IsNullOrWhiteSpace(code)) - throw new ArgumentException("code is required"); - - var role = new Role - { - RoleId = Guid.NewGuid(), - Code = code, - Name = string.IsNullOrWhiteSpace(name) ? code : name, - IsSystemRole = isSystemRole, - CreatedAt = DateTime.UtcNow - }; - - await _repository.AddRoleAsync(role); - await _uow.CommitAsync(); - - return ToDto(role); - } - catch - { - await _uow.RollbackAsync(); - throw; - } - } - - public async Task GetRole(object payload, HttpContext httpContext) - { - var data = DeserializePayload.Deserialize(payload); - - if (!data.ContainsKey("roleId") || !Guid.TryParse(data["roleId"].GetString(), out var roleId)) - throw new ArgumentException("Invalid roleId"); - - var role = await _repository.GetRoleByIdAsync(roleId) - ?? throw new KeyNotFoundException("Role not found"); - - return ToDto(role); - } - - public async Task ListRoles(HttpContext httpContext) - { - var roles = await _repository.ListRolesAsync(); - return roles.Select(ToDto).ToList(); - } - - public async Task UpdateRole(object payload, HttpContext httpContext) - { - await _uow.BeginAsync(); - try - { - var data = DeserializePayload.Deserialize(payload); - - if (!data.ContainsKey("roleId") || !Guid.TryParse(data["roleId"].GetString(), out var roleId)) - throw new ArgumentException("Invalid roleId"); - - var role = await _repository.GetRoleByIdAsync(roleId) - ?? throw new KeyNotFoundException("Role not found"); - - if (data.ContainsKey("code")) - { - var code = data["code"].GetString(); - if (string.IsNullOrWhiteSpace(code)) - throw new ArgumentException("code cannot be empty"); - role.Code = code; - } - - if (data.ContainsKey("name")) - role.Name = data["name"].GetString(); - - if (data.ContainsKey("isSystemRole") && data["isSystemRole"].ValueKind != JsonValueKind.Null) - role.IsSystemRole = data["isSystemRole"].GetBoolean(); - - await _uow.CommitAsync(); - - return ToDto(role); - } - catch - { - await _uow.RollbackAsync(); - throw; - } - } - - public async Task DeleteRole(object payload, HttpContext httpContext) - { - await _uow.BeginAsync(); - try - { - var data = DeserializePayload.Deserialize(payload); - - if (!data.ContainsKey("roleId") || !Guid.TryParse(data["roleId"].GetString(), out var roleId)) - throw new ArgumentException("Invalid roleId"); - - var role = await _repository.GetRoleByIdAsync(roleId) - ?? throw new KeyNotFoundException("Role not found"); - - if (await _repository.RoleInUseAsync(roleId)) - throw new InvalidOperationException("ROLE_IN_USE: role is assigned to one or more users"); - - await _repository.DeleteRoleAsync(role); - await _uow.CommitAsync(); - - return new { message = "Role deleted successfully" }; - } - catch - { - await _uow.RollbackAsync(); - throw; - } - } - - private static object ToDto(Role role) => new - { - role.RoleId, - role.Code, - role.Name, - role.IsSystemRole, - role.CreatedAt - }; -} diff --git a/Services/UserManager/UserManagerService.cs b/Services/UserManager/UserManagerService.cs index 59e7293..f6f66cf 100644 --- a/Services/UserManager/UserManagerService.cs +++ b/Services/UserManager/UserManagerService.cs @@ -65,8 +65,7 @@ public class UserManagerService var deviceName = data.ContainsKey("deviceName") ? data["deviceName"].GetString() : "Unknown Device"; - if (!data.ContainsKey("roleId") || !Guid.TryParse(data["roleId"].GetString(), out var RoleId)) - throw new ArgumentException("Invalid roleId"); + // A "roleId" in the payload (old ERP builds still send one) is accepted and ignored: roles are the ERP's business. if (!data.ContainsKey("userTypeId") || !Guid.TryParse(data["userTypeId"].GetString(), out var UserTypeId)) throw new ArgumentException("Invalid userTypeId"); @@ -80,7 +79,6 @@ public class UserManagerService Email = Email, MobileNumber = MobileNumber, Nic = Nic, - RoleId = RoleId, UserTypeId = UserTypeId }; bool canSave = await CanRegisterUser(userToCheck); @@ -110,7 +108,6 @@ public class UserManagerService Nic = Nic, Email = Email, MobileNumber = MobileNumber, - RoleId = RoleId, UserTypeId = UserTypeId, CreatedAt = DateTime.UtcNow, IsActive = true, @@ -194,7 +191,6 @@ public class UserManagerService user.EmailVerified, user.MobileNumberVerified, user.IsMfaEnabled, - RoleId = user.RoleId, UserTypeId = user.UserTypeId } }; @@ -303,7 +299,6 @@ public class UserManagerService user.EmailVerified, user.MobileNumberVerified, user.IsMfaEnabled, - RoleId = user.RoleId, UserTypeId = user.UserTypeId } }; @@ -412,7 +407,6 @@ public class UserManagerService user.EmailVerified, user.MobileNumberVerified, user.IsMfaEnabled, - RoleId = user.RoleId, UserTypeId = user.UserTypeId } } @@ -505,7 +499,6 @@ public class UserManagerService user.EmailVerified, user.MobileNumberVerified, user.IsMfaEnabled, - RoleId = user.RoleId, UserTypeId = user.UserTypeId } }; @@ -552,12 +545,6 @@ public class UserManagerService user.IsActive, user.IsLocked, user.CreatedAt, - Role = new - { - user.Role?.RoleId, - user.Role?.Code, - user.Role?.Name - }, UserType = new { user.UserType?.UserTypeId, @@ -1215,9 +1202,6 @@ public class UserManagerService if (user.UserTypeId == userToCheck.UserTypeId) return false; - if (user.RoleId == userToCheck.RoleId) - return false; - if (user.IsLocked == false) return false; diff --git a/Utility/JwtTokenHelper.cs b/Utility/JwtTokenHelper.cs index 9e04732..87b2660 100644 --- a/Utility/JwtTokenHelper.cs +++ b/Utility/JwtTokenHelper.cs @@ -22,13 +22,8 @@ public class JwtTokenHelper public async Task GenerateToken(User user) { - // Load user's role and usertype if not already loaded - if (user.Role == null) - { - await _dbContext.Entry(user) - .Reference(u => u.Role) - .LoadAsync(); - } + // Load the user's usertype if not already loaded. (No role: authorization belongs to the ERP, which reads the + // person's role from its own tables - the token only says who the person is.) if (user.UserType == null) { await _dbContext.Entry(user) @@ -41,8 +36,6 @@ public class JwtTokenHelper new Claim("UserId", user.Id.ToString()), new Claim("UserTypeId", user.UserTypeId.ToString()), new Claim("UserTypeCode", user.UserType?.Code ?? ""), - new Claim("RoleId", user.RoleId.ToString()), - new Claim("RoleCode", user.Role?.Code ?? ""), new Claim("NIC", user.Nic ?? ""), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat,