Merge pull request 'Dev' (#5) from dev into main
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
@@ -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`)
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace AuthHex
|
||||
{
|
||||
public AppDBContext(DbContextOptions<AppDBContext> options) : base(options) { }
|
||||
public DbSet<Models.User> Users => Set<Models.User>();
|
||||
public DbSet<Models.Role> Roles => Set<Models.Role>();
|
||||
public DbSet<Models.UserType> UserType => Set<Models.UserType>();
|
||||
public DbSet<Models.UserSession> UserSessions => Set<Models.UserSession>();
|
||||
|
||||
@@ -69,10 +68,6 @@ namespace AuthHex
|
||||
.HasForeignKey(up => up.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Configure Role primary key
|
||||
modelBuilder.Entity<Models.Role>()
|
||||
.HasKey(r => r.RoleId);
|
||||
|
||||
// Configure UserType primary key
|
||||
modelBuilder.Entity<Models.UserType>()
|
||||
.HasKey(ut => ut.UserTypeId);
|
||||
@@ -81,12 +76,6 @@ namespace AuthHex
|
||||
modelBuilder.Entity<Models.User>()
|
||||
.HasKey(u => u.Id);
|
||||
|
||||
modelBuilder.Entity<Models.User>()
|
||||
.HasOne(u => u.Role)
|
||||
.WithMany()
|
||||
.HasForeignKey(u => u.RoleId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<Models.User>()
|
||||
.HasOne(u => u.UserType)
|
||||
.WithMany()
|
||||
|
||||
@@ -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<IActionResult> RoleExecute([FromBody] ApiRequest request)
|
||||
{
|
||||
var response = await _roleManager.Execute(request, HttpContext);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using AuthHex.Models;
|
||||
|
||||
namespace AuthHex.Interfaces
|
||||
{
|
||||
public interface IRoleManageRepository
|
||||
{
|
||||
Task<Role> AddRoleAsync(Role role, CancellationToken ct = default);
|
||||
Task<Role?> GetRoleByIdAsync(Guid roleId, CancellationToken ct = default);
|
||||
Task<List<Role>> ListRolesAsync(CancellationToken ct = default);
|
||||
Task<bool> RoleInUseAsync(Guid roleId, CancellationToken ct = default);
|
||||
Task DeleteRoleAsync(Role role, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -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; }
|
||||
|
||||
|
||||
@@ -57,18 +57,15 @@ builder.Services.AddScoped<IUnitOfWork, EFUnitOfWork>();
|
||||
builder.Services.AddScoped<IUserManageRepository, UserManageRepository>();
|
||||
builder.Services.AddScoped<IRecoveryManagerRepository, RecoveryManagerRepository>();
|
||||
builder.Services.AddScoped<IAltOptionManagerRepository, AltOptionManagerRepository>();
|
||||
builder.Services.AddScoped<IRoleManageRepository, RoleManageRepository>();
|
||||
builder.Services.AddScoped<UserHelper>();
|
||||
builder.Services.AddScoped<UserManagerService>();
|
||||
builder.Services.AddScoped<RecoveryManagerService>();
|
||||
builder.Services.AddScoped<AuthHex.Services.AltOptionManager.AltOptionManagerService>();
|
||||
builder.Services.AddScoped<AuthHex.Services.RoleManager.RoleManagerService>();
|
||||
builder.Services.AddHttpClient<ThirdPartyService>();
|
||||
builder.Services.AddScoped<JwtTokenHelper>();
|
||||
builder.Services.AddScoped<AuthHex.Services.FunctionHandler.UserManager>();
|
||||
builder.Services.AddScoped<AuthHex.Services.FunctionHandler.RecoveryManager>();
|
||||
builder.Services.AddScoped<AuthHex.Services.FunctionHandler.AltOptionManager>();
|
||||
builder.Services.AddScoped<AuthHex.Services.FunctionHandler.RoleManager>();
|
||||
|
||||
var configuredOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? Array.Empty<string>();
|
||||
var allowedOrigins = configuredOrigins
|
||||
|
||||
@@ -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<Role> AddRoleAsync(Role role, CancellationToken ct = default)
|
||||
{
|
||||
_dbContext.Roles.Add(role);
|
||||
return Task.FromResult(role);
|
||||
}
|
||||
|
||||
public async Task<Role?> GetRoleByIdAsync(Guid roleId, CancellationToken ct = default)
|
||||
{
|
||||
return await _dbContext.Roles.FirstOrDefaultAsync(r => r.RoleId == roleId, ct);
|
||||
}
|
||||
|
||||
public async Task<List<Role>> ListRolesAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await _dbContext.Roles.OrderBy(r => r.Code).ToListAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,6 @@ namespace AuthHex.Repos
|
||||
public async Task<User?> GetUserByIdAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
return await _dbContext.Users
|
||||
.Include(u => u.Role)
|
||||
.Include(u => u.UserType)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId, ct);
|
||||
}
|
||||
|
||||
@@ -377,7 +377,6 @@ public class AltOptionManagerService
|
||||
user.EmailVerified,
|
||||
user.MobileNumberVerified,
|
||||
user.IsMfaEnabled,
|
||||
RoleId = user.RoleId,
|
||||
UserTypeId = user.UserTypeId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
using AuthHex.Models.IOs;
|
||||
using AuthHex.Services.RoleManager;
|
||||
|
||||
namespace AuthHex.Services.FunctionHandler
|
||||
{
|
||||
public class RoleManager
|
||||
{
|
||||
public readonly Dictionary<string, Func<object, HttpContext, Task<object>>> _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<ApiResponse> 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<object> 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<object> 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<object> ListRoles(HttpContext httpContext)
|
||||
{
|
||||
var roles = await _repository.ListRolesAsync();
|
||||
return roles.Select(ToDto).ToList();
|
||||
}
|
||||
|
||||
public async Task<object> 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<object> 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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -22,13 +22,8 @@ public class JwtTokenHelper
|
||||
|
||||
public async Task<string> 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,
|
||||
|
||||
Reference in New Issue
Block a user