RBAC Improvements
Caching improvements Maker-Checker Implementation
This commit is contained in:
+192
@@ -0,0 +1,192 @@
|
||||
# PostFundManagement (PFM) Platform — Architectural Specification
|
||||
|
||||
> **Document Version**: 2.0
|
||||
> **Target Runtimes**: .NET 10.0 (C# 13) | React 19 / TypeScript 5.8 / Vite
|
||||
> **Scope**: System Architecture, Security Topology, RBAC Enforcement, and Maker-Checker Pattern
|
||||
|
||||
---
|
||||
|
||||
## 1. System Topology & Architectural Layers
|
||||
|
||||
The PFM platform is constructed using **Clean Architecture** and **Domain-Driven Design (DDD)** principles to guarantee strict decoupling of business rules, persistence concerns, and user interfaces.
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ PRESENTATION TIER │
|
||||
│ PostFundManagement.ConsoleUI │ PostFundManagement.PartnerUI │
|
||||
│ (Internal Staff & Governance) │ (External Recipient Portal) │
|
||||
│ React 19 + TypeScript + Vite │ React 19 + WebGL (OGL) + Vite │
|
||||
└───────────────────────────────────┬────────────────────────────────────┘
|
||||
│ HTTPS / REST (v1/api/*) + OIDC PKCE
|
||||
┌───────────────────────────────────▼────────────────────────────────────┐
|
||||
│ API GATEWAY │
|
||||
│ PostFundManagement.Api (ASP.NET Core Minimal APIs / Carter) │
|
||||
│ Authentication: JWT Bearer + OpenTelemetry Observability │
|
||||
└───────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────────────────▼────────────────────────────────────┐
|
||||
│ APPLICATION LAYER │
|
||||
│ PostFundManagement.Application │
|
||||
│ CQRS (Mediator), Business Validators, Domain Services, Quartz Jobs │
|
||||
└──────────────────┬─────────────────────────────────┬───────────────────┘
|
||||
│ │
|
||||
┌──────────────────▼──────────────────┐ ┌──────────▼───────────────────┐
|
||||
│ DOMAIN LAYER │ │ INFRASTRUCTURE LAYER │
|
||||
│ PostFundManagement.Domain │ │ PostFundManagement.Infrastr.│
|
||||
│ Entities, Value Objects, Enums, │ │ PostgreSQL (EF Core 10), │
|
||||
│ Domain Invariants, Audit Models │ │ Skoruba STS, S3 Garage, │
|
||||
└─────────────────────────────────────┘ │ Redis HybridCache │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Security & Identity Architecture
|
||||
|
||||
Authentication and authorization are separated cleanly across system boundaries:
|
||||
|
||||
* **Security Token Service (STS)**: Duende IdentityServer / Skoruba Administration (`https://sts.security.khongisa.co.za`) issues cryptographically signed JWT access tokens using OpenID Connect (OIDC) with Proof Key for Code Exchange (PKCE).
|
||||
* **Audience & Scopes**: Web clients request standard OIDC scopes (`openid`, `profile`, `roles`, `email`, `address`) along with the target API resource scope (`pfm-api-dev`).
|
||||
* **Tenant Isolation**: Every authenticated principal carries a `tenant_id` claim. Multitenant query filters in EF Core enforce cross-tenant partition boundaries at the data access layer.
|
||||
* **Partner Isolation**: Grantees and external recipients possess the `partner` role. The `ConsoleUI` enforces immediate lockout for `partner` roles, restricting external users exclusively to `PartnerUI`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Role-Based Access Control (RBAC) Architecture
|
||||
|
||||
PFM implements a fine-grained, claim-based RBAC model where system permissions are decomposed into **15 operational domains** and **5 progressive action capabilities**.
|
||||
|
||||
### 3.1 Role Claims Inventory
|
||||
|
||||
| Claim Key | Operational Domain | Functional Scope |
|
||||
| :--- | :--- | :--- |
|
||||
| `dashboard` | Executive Dashboard | Macro portfolio KPIs, disbursed capital, and ISO 31000 risk heatmap |
|
||||
| `governance` | Governance & Structure | Holding entities, funder portfolios, programmes, rules, and instruments |
|
||||
| `projects_milestones` | Projects & Milestones | Work breakdown structure (WBS), contractual milestones, and activities |
|
||||
| `grant_financials` | Grants & Financials | Statutory funding awards, budget lines, contracts, and disbursement tranches |
|
||||
| `evidence_risks` | M&E, Evidence & Risks | Deliverable evidence verification queue, indicators, and risk registers |
|
||||
| `beneficiaries` | Beneficiaries & Impact | Demographic targets, POPIA compliance items, and outcome deliverables |
|
||||
| `change_management` | Change Management | Project issues, contractual change requests, and budget variations |
|
||||
| `site_visits` | Site Visits | Physical field inspections, checklist observations, and inspector findings |
|
||||
| `settings_invites` | Settings Invites | Onboarding invitations and candidate notifications dispatch |
|
||||
| `settings_users` | Settings Users | User provisioning, role assignments, and account lockouts |
|
||||
| `settings_auditlog` | Settings Audit Log | Immutable audit ledger and system telemetry records |
|
||||
| `settings_notifications`| Settings Notifications | SMTP engines, notification channels, and dispatch telemetry |
|
||||
| `tenant_self_service` | Tenant Self-Service | Tenant profile configuration and delegated sub-user management |
|
||||
| `tenant_management` | Tenant Management | Multi-tenant directory, partition allocation, and solution ownership |
|
||||
| `unrestricted` | System Administration | Root administrative bypass across all system partitions |
|
||||
|
||||
### 3.2 Claim Actions & Capability Hierarchy
|
||||
|
||||
Action capabilities within a claim domain follow a strict subset hierarchy:
|
||||
|
||||
$$\text{view} \subset \text{add} \subset \text{update} \quad \text{and} \quad \text{view} \subset \text{export}$$
|
||||
|
||||
* **`view`**: Read-only access to entity lists, detailed records, metrics, and progress scores.
|
||||
* **`add`**: Can create new records, edit draft records, submit milestone evidence, and change workflow statuses *other than approval* (includes `view`).
|
||||
* **`update`**: Authoritative governance capability. Can execute **approval** status transitions (`Approved`, `Active` contracts) on records (includes `view` and `add`).
|
||||
* **`export`**: Can generate and download structured spreadsheets (`.xlsx`) and formal PDF reports (includes `view`).
|
||||
* **`all`**: Comprehensive authority (`view`, `add`, `update`, `export`) over the claim domain.
|
||||
|
||||
### 3.3 Authoritative Role-to-Claims Matrix
|
||||
|
||||
| Role | Assigned Claim Domains | Standard Capabilities | Record Approval Authority (`update`) |
|
||||
| :--- | :--- | :--- | :---: |
|
||||
| **`Admin`** | `unrestricted` | `all` | **Yes (System-wide)** |
|
||||
| **`Executive`** | `dashboard`, `governance`, `projects_milestones`, `grant_financials`, `evidence_risks`, `tenant_self_service` | `all` | **Yes** |
|
||||
| **`Manager`** | `dashboard`, `projects_milestones`, `grant_financials`, `evidence_risks`, `beneficiaries`, `change_management`, `site_visits`, `settings_invites`, `settings_users`, `settings_auditlog`, `settings_notifications`, `tenant_self_service` | `all` | **Yes** |
|
||||
| **`Supervisor`** | `dashboard`, `projects_milestones`, `grant_financials`, `evidence_risks`, `beneficiaries`, `change_management`, `site_visits`, `settings_invites`, `settings_users` | `view, add, update, export`<br>*(Dashboard: `view, export`)* | **Yes** |
|
||||
| **`Contributor`** | `projects_milestones`, `grant_financials`, `evidence_risks`, `beneficiaries`, `change_management`, `site_visits`, `settings_invites` | **`view, add`** | ❌ **Strictly Prohibited** |
|
||||
|
||||
---
|
||||
|
||||
## 4. Maker-Checker Pattern Architecture
|
||||
|
||||
To eliminate fraud risk, self-dealing, and conflicts of interest, PFM enforces the **Maker-Checker Pattern** (Segregation of Duties / Dual Authorization).
|
||||
|
||||
### 4.1 Core Principle
|
||||
> **The user who creates or drafts a record (the "Maker") is strictly prohibited from verifying or approving that record (the "Checker"), regardless of their administrative role or RBAC claim privileges.**
|
||||
|
||||
Even if an `Executive`, `Manager`, or `Supervisor` possesses the `update` claim action on a domain, they cannot approve a record if they are identified as the record's creator.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Maker (User A)<br/>Creates Record"] --> B["Record Persisted<br/>with createdBy = User A"]
|
||||
B --> C{"Checker Evaluation<br/>(Approver Login)"}
|
||||
C -->|Checker == User A| D["Approval BLOCKED<br/>Maker-Checker Violation"]
|
||||
C -->|Checker == User B<br/>with 'update' claim| E["Approval GRANTED<br/>Status -> Approved"]
|
||||
```
|
||||
|
||||
### 4.2 Record Attribution Model
|
||||
Every operational domain entity immutably captures the creator identity:
|
||||
* `createdBy`: Cryptographic user identifier (Guid / Subject ID) populated from the verified token claim `context.GetUserId()` upon creation.
|
||||
* `approvedBy`: User identifier of the independent verifier populated upon successful transition to `Approved`.
|
||||
|
||||
### 4.3 Client-Side Enforcement Hooks & Helpers
|
||||
In `PostFundManagement.ConsoleUI`, the Maker-Checker rule is evaluated dynamically via [`useRbac()`](file:///c:/Users/Admin/source/repos/PostFundManagement/PostFundManagement.ConsoleUI/src/auth/rbac.ts):
|
||||
|
||||
* **`isMaker(createdBy)`**: Compares `createdBy` against current user `user.id`, `sub`, and `user.email`.
|
||||
* **`canApproveRecord(claim, createdBy)`**: Evaluates:
|
||||
$$\text{canApproveRecord} = \text{canApprove}(\text{claim}) \land \neg\text{isMaker}(\text{createdBy})$$
|
||||
* **`getApprovalRestrictionReason(claim, createdBy)`**: Surfaces explanatory feedback:
|
||||
- `"Requires update/approval permission"` if the claim lacks `update`.
|
||||
- `"Maker-Checker policy: Record creators cannot approve their own records"` if `isMaker` is true.
|
||||
|
||||
### 4.4 UI Guard Implementations
|
||||
1. **Status Dropdowns**: The `Approved` option in status `<select>` controls is disabled with contextual labels:
|
||||
```tsx
|
||||
<option value={ApprovalStatus.Approved} disabled={!canApproveRecord('projects_milestones', record.createdBy)}>
|
||||
Approved {isMaker(record.createdBy) ? '(Maker-Checker: Cannot approve own record)' : ''}
|
||||
</option>
|
||||
```
|
||||
2. **Action Buttons**: Fast-track "Approve" buttons (e.g. Budget Approval, Disbursement Authorisation, Evidence Review) are hidden or accompanied by a Maker-Checker indicator badge when viewed by the creator.
|
||||
3. **Export Controls**: `<ExportButton>` enforces domain claim checks explicitly (`claim="grant_financials"`), rejecting unauthenticated exports.
|
||||
|
||||
---
|
||||
|
||||
## 5. Caching Strategy, Mutation Versioning & Performance Optimization
|
||||
|
||||
To achieve sub-50ms page transitions while guaranteeing data freshness across mutations in a distributed architecture, PFM employs a multi-tiered caching topology.
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ REACT QUERY CACHE (In-Memory Browser Tier) │
|
||||
│ staleTime: 2m (dynamic) | 5m (taxonomy) | gcTime: 10m │
|
||||
└───────────────────────────────────┬────────────────────────────────────┘
|
||||
│ Cache Miss / Background Refetch
|
||||
┌───────────────────────────────────▼────────────────────────────────────┐
|
||||
│ AXIOS CLIENT INTERCEPTOR & MUTATION VERSIONING (_v) │
|
||||
│ Reads/Browsing: attaches active `_v` epoch token │
|
||||
│ Mutations (POST/PUT/PATCH/DELETE): auto-bumps epoch -> new `_v` token │
|
||||
└───────────────────────────────────┬────────────────────────────────────┘
|
||||
│ HTTP GET /v1/api/*?_v=<epoch>
|
||||
┌───────────────────────────────────▼────────────────────────────────────┐
|
||||
│ ASP.NET CORE OUTPUTCACHE (Redis / Memory Tier) │
|
||||
│ Base Policy: 2-minute expiration partitioned by URI + QueryString │
|
||||
│ Hits same `_v` -> 2ms response | New `_v` -> DB query & cache refresh │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.1 The OutputCache Stale Data Challenge
|
||||
ASP.NET Core Minimal APIs in `PostFundManagement.Api` attach `.CacheOutput()` to GET endpoints, using a 2-minute base sliding expiration backed by Redis / Memory cache.
|
||||
|
||||
Because OutputCache keys are generated from the HTTP Method, Host, Path, and **Query String**, a naive client refetch after a mutation (e.g. `queryClient.invalidateQueries({ queryKey: ['projects'] })`) calls `GET /v1/api/projects`. OutputCache intercepts this identical URI, returning the pre-mutation response cached earlier. The updated project status or approved milestone does not reflect in the UI until the 2-minute cache expires.
|
||||
|
||||
### 5.2 Client-Side Mutation Epoch Versioning
|
||||
PFM solves this without modifying backend endpoints via **Client-Side Mutation Versioning**:
|
||||
1. **Epoch Tracking (`activeMutationEpoch`)**: `httpClient.ts` tracks an active mutation timestamp initialized from `sessionStorage` or `Date.now()`.
|
||||
2. **Automatic Mutation Interceptor**: Every successful mutating HTTP response (`POST`, `PUT`, `PATCH`, `DELETE` with HTTP 2xx) triggers `bumpMutationEpoch()`.
|
||||
3. **Query Parameter Injection**: Outgoing `GET` requests to internal APIs automatically append `_v: getMutationEpoch()`.
|
||||
4. **Instant Invalidation**: When a mutation succeeds, the epoch is bumped. Subsequent React Query refetches send the new `_v`, producing an immediate **cache miss** in ASP.NET Core OutputCache. The backend queries PostgreSQL, returns fresh data, and caches the new state under the new `_v` key.
|
||||
5. **Cache Hit Snappiness**: Navigations between pages while no mutations have occurred reuse the stable `_v`, yielding sub-5ms responses from Redis/Memory OutputCache and 0ms from React Query in-memory cache.
|
||||
6. **Explicit Freshness (`_t`)**: When `Cache-Control: no-cache` is specified or during explicit refresh operations, a unique timestamp `_t` is attached to force a guaranteed fresh fetch.
|
||||
|
||||
### 5.3 RBAC Startup Pipeline Acceleration
|
||||
Following the introduction of fine-grained RBAC and Maker-Checker checks, startup performance was streamlined to eliminate sequential network waterfalls:
|
||||
* **Cache-First Role Claims**: `fetchAndCacheSystemRoles` and `fetchAndCacheRoleClaims` check in-memory and `localStorage` caches *before* dispatching network calls.
|
||||
* **Parallel User Resolution**: `getUserRolesFromApi` and `getUserClaimsFromApi` are resolved concurrently using `Promise.allSettled`.
|
||||
* **Parallel Claim Resolution**: Claims across all assigned roles are retrieved concurrently rather than in serial loops.
|
||||
* **Immediate Non-Blocking Session Render**: If valid session tokens and user profiles exist in `localStorage`, `AuthProvider` immediately unblocks UI rendering (`isLoading: false`) and runs tenant lockout checks, account synchronization, and claim revalidation asynchronously in the background.
|
||||
* **In-Memory User Sync & Lockout Caching**: `isUserSyncedInBrowser` and `verifyTenantLockout` maintain in-memory references to eliminate synchronous `JSON.parse` operations from the request pipeline.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user