feat: add user and role view sheets with detailed information display

- Created UserViewSheet component to display user details including username, full name, role, status, and timestamps.
- Created RoleViewSheet component to display role details including name, level, status, creation date, and associated permissions.
- Introduced permissions configuration in a new permissions.ts file, defining categories and permission codes for better management.
- Implemented middleware for authentication, ensuring access control for protected routes.
This commit is contained in:
Kalana B. Thilakarathna
2026-10-18 23:11:01 +05:30
parent 558afc6f9d
commit 0052725b88
86 changed files with 41117 additions and 939 deletions
+108 -94
View File
@@ -1,102 +1,116 @@
# Authentication — Own-User Auth via External Identity Service (AuthHex)
# Authentication & Authorization Layer
**Status**: Done
**Depends On**: — (cross-cutting infrastructure, not a numbered feature step)
## External Auth Service (AuthHex)
- Runs on `http://localhost:6703`
- Owns credentials — this app never stores passwords
- Issues RS256-signed JWTs with claims: `sub`, `role_id`, `role_level`, `username`, `role_code`
---
## Goal
Own-user (staff/API) authentication is delegated to an external identity service ("AuthHex"),
which owns credentials and signs RS256 JWTs. This app validates those tokens and maps them onto
its own `User`/`Role` tables so the existing permission-policy authorization stays unchanged.
---
## Background / Context
Previously this app verified passwords and minted its own HMAC-signed JWT locally. That's been
replaced — AuthHex now owns credentials and signing; this app only holds AuthHex's **public** key
to validate tokens, and never sees a plaintext password for this flow.
Authorization is untouched: permission policies, role levels, scope checks, and the seeded role
set all still work exactly as before, because every validated token is mapped back onto a local
`User`/`Role` row before any authorization check runs.
---
## Configuration Changes
```jsonc
"Jwt": {
"PublicKey": "<RSAKeyValue>...</RSAKeyValue>",
"Audience": "AuthHexClient",
"Issuer": "AuthHex"
},
"AuthHex": { "BaseUrl": "http://localhost:6102" }
```
`Jwt:Key` (old symmetric secret) is removed. `Jwt:PublicKey` is AuthHex's RSA public key in XML
format (not PEM).
---
## API Endpoints
| Method | Route | Notes |
|---|---|---|
| POST | /api/auth/login | Proxies to AuthHex `loginUser` |
| POST | /api/auth/register | Proxies to AuthHex `registerUser` |
| GET | /api/auth/me | `[Authorize]`, proxies to AuthHex `getUserDetails` |
| POST | /api/auth/logout | `[Authorize]`, proxies to AuthHex `LogoutUser` |
All four just forward to `POST {AuthHex:BaseUrl}/api/user` and pass AuthHex's response back
as-is. No cookie — token returned in the response body, sent back as `Authorization: Bearer`.
---
## Business Rules
1. No local password check or token issuance — both delegated to AuthHex.
2. `AddJwtBearer` validates issuer/audience/signature/lifetime against AuthHex's public key.
3. `OnTokenValidated` maps the token's `sub` claim to a local `User` (`IUserRepository`); fails
the token if no matching active user exists.
4. On success, `username`/`role_id`/`role_level` claims are attached from the **local** `User`/
`Role` row — authorization checks downstream are unaffected by anything AuthHex's own token
claims contain.
5. Only one validation mechanism runs (`AddJwtBearer`) — two ported alternatives exist but are not
registered (see Implementation Notes).
6. No refresh tokens — once the AuthHex-issued token expires, the client must log in again.
---
## Permissions to Add to Seed
None — this only changes authentication. Permission policies and role seed data are untouched.
---
## Open Questions
- Add a refresh-token flow if AuthHex doesn't offer one and re-login UX proves disruptive.
- Keep or delete the ported `AuthMiddleware`/`JwtMiddleware` if never actually needed.
- No retry/circuit-breaker if AuthHex is unreachable.
---
## Implementation Notes
## Directory Structure
```
Features/Auth/ — AuthController, AuthService, AuthDTOs (thin AuthHex proxies)
Infra/Authorization/
AuthHex/ — IAuthHexClient, AuthHexClient, AuthHexResponse
Jwt/RsaXmlKeyReader.cs — portable XML→RSA key parser (RSA.FromXmlString is Windows-only)
Middleware/ — AuthMiddleware, JwtMiddleware, JwtOptions, ApiResponse
(ported from OSMCore for reference — NOT registered)
Program.cs — AddHttpClient<IAuthHexClient>, AddJwtBearer + OnTokenValidated
(maps token → local User/Role → role_id/role_level claims)
Infra/
├── Auth/ External auth service integration
│ ├── AuthHexClient.cs HTTP proxy to AuthHex (/api/user)
│ ├── IAuthHexClient.cs
│ └── AuthHexResponse.cs Raw response shape from AuthHex
│
└── Authorization/ This app's permission enforcement
├── PermissionRequirement.cs ASP.NET Core requirement (wraps permission code string)
├── PermissionPolicyProvider.cs Builds policy dynamically for any "x.y" string
├── PermissionAuthorizationHandler.cs Evaluates requirement against DB
├── AuthorizationController.cs Exposes permission/scope check endpoints
├── CurrentUserService.cs Extracts typed claims from HttpContext.User
├── ICurrentUserService.cs
├── AuthorizationDTOs.cs
├── Permission/
│ ├── PermissionRepository.cs Queries RolePermissions + Permissions tables
│ ├── PermissionService.cs
│ ├── IPermissionRepository.cs
│ └── IPermissionService.cs
└── Scope/
├── ScopeRepository.cs Queries UserStoreScopes / UserRouteScopes / UserRegionScopes
├── ScopeAccessService.cs Resolves accessible stores via store → route → region hierarchy
├── IScopeRepository.cs
└── IScopeAccessService.cs
```
**Gotcha:** don't run more than one of `AddJwtBearer` / `AuthMiddleware` /
`JwtMiddleware.CustomMiddlewareAttribute` at once — each re-validates the same token
independently.
---
## Login Flow
```
POST /api/auth/login
→ AuthController
→ AuthService.LoginAsync
→ AuthHexClient.CallAsync("loginUser", ...)
→ POST http://localhost:6703/api/user
← RS256-signed JWT returned to client
```
---
## Request Flow
```
Incoming request with Authorization: Bearer <jwt>
│
▼
AuthMiddleware (Middleware/AuthMiddleware.cs)
- Reads Jwt:PublicKey from appsettings.json
- RSA.Create().FromXmlString(publicKeyXml)
- Validates JWT signature (RS256), issuer, audience, lifetime
- Sets HttpContext.User = principal (claims: sub, role_id, role_level, username)
│
▼
app.UseAuthorization()
- Global AuthorizeFilter: requires IsAuthenticated on every endpoint
- [Authorize("brand.view")] triggers PermissionPolicyProvider
→ builds AuthorizationPolicy with PermissionRequirement("brand.view")
→ PermissionAuthorizationHandler reads role_id claim
→ PermissionService.HasPermissionAsync(roleId, "brand.view")
→ DB: RolePermissions JOIN Permissions WHERE code = "brand.view"
│
▼
Controller
- ICurrentUserService.UserId / RoleId / RoleLevel extracted from HttpContext.User
- Scope guards: HasStoreScopeAsync / HasRouteScopeAsync / HasRegionScopeAsync
```
---
## Scope System
Scope controls which **records** a user can access (not just which actions).
| Assignment table | Access granted to |
|---|---|
| `UserStoreScopes` | Specific stores directly |
| `UserRouteScopes` | All stores on that route |
| `UserRegionScopes` | All routes → all stores in that region |
`ScopeAccessService.GetAccessibleStoreIdsAsync` merges all three levels and deduplicates.
---
## Pipeline Order (Program.cs)
```csharp
app.UseCors();
app.UseMiddleware<AuthMiddleware>(); // 1. populate HttpContext.User
app.UseAuthorization(); // 2. evaluate [Authorize] attributes
```
`UseAuthentication()` is intentionally absent — JWT validation is done manually in `AuthMiddleware`, not via ASP.NET Core's built-in bearer scheme.
---
## appsettings.json Keys
| Key | Purpose |
|---|---|
| `Jwt:PublicKey` | RSA XML public key from AuthHex (used by AuthMiddleware) |
| `Jwt:Issuer` | `AuthHex` |
| `Jwt:Audience` | `AuthHexClient` |
| `AuthHex:BaseUrl` | `http://localhost:6703` |