geocrop-platform./apps/nextgen/egovernancecertificate.md

474 lines
18 KiB
Markdown

# MASTER SYSTEM ARCHITECTURE PLAN & EXECUTION PROMPT
## E-Governance Cryptographic Licensing Engine & Multi-Tenant CA System
> **Target Workspace:** `E:\nextgen\next-gen`
> **SuperAdmin Instance:** `E:\nextgen\next-gen\superadmin` (`server/` and `client/`)
> **First Tenant Instance:** `E:\nextgen\next-gen\client` (React Vite PWA) and `E:\nextgen\next-gen\server` (Node/Express API Engine)
> **Document Purpose:** Complete, step-by-step technical architectural prompt for an execution agent to implement asymmetric Ed25519 licensing, hybrid dual-driver caching, global access middleware, and anti-tampering clock protection.
---
## EXECUTIVE ARCHITECTURE OVERVIEW
```
┌─────────────────────────────────────────────────────────────────────────┐
│ SUPERADMIN ENGINE (CA PORTAL) │
│ E:\nextgen\next-gen\superadmin │
│ - Holds Ed25519 PRIVATE KEY (SUPERADMIN_PRIVATE_KEY) │
│ - Signs License Tokens: Ed25519 (EdDSA) │
│ - Exposes JWKS Endpoint: /api/v1/auth/jwks │
│ - Stores Tenant Registry in Supabase Cloud (`tenants`, `tenant_licenses`)│
└───────────────────────────────────┬─────────────────────────────────────┘
│ Cryptographic License Token
│ (Ed25519 Signed JWT)
┌─────────────────────────────────────────────────────────────────────────┐
│ FIRST TENANT INSTANCE & DEPLOYMENTS │
│ Client: E:\nextgen\next-gen\client (React Vite PWA + Web Crypto) │
│ Server: E:\nextgen\next-gen\server (Node Express Backend API) │
│ │
│ - Holds Ed25519 PUBLIC KEY (SUPERADMIN_PUBLIC_KEY / VITE_...) │
│ - Storage: Local SQLite/Postgres `tenant_licenses` │
│ - Hybrid Dual-Driver Cache: Memory LRU (`lru-cache`) + Redis (`ioredis`)│
│ - AccessMiddleware: Enforces Read-Only on Expired Licenses (HTTP 402) │
│ - Clock Anti-Tamper: Heartbeat sync with SuperAdmin + NTP drift check │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 1. DATABASE SCHEMA & DATA MODELS
### 1.1 SuperAdmin Cloud Database (Postgres / Supabase)
Target Schema File: `E:\nextgen\supabase\supabase_full_schema.sql`
Service Integration: `E:\nextgen\next-gen\superadmin\server\src\services\SupabaseSuperAdminService.js`
```sql
-- Full Supabase Schema Updates for Asymmetric Ed25519 Licensing Engine
-- Located in: E:\nextgen\supabase\supabase_full_schema.sql
CREATE TABLE IF NOT EXISTS tenant_licenses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
license_key TEXT UNIQUE NOT NULL,
term_name TEXT NOT NULL,
academic_year TEXT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
grace_period_days INTEGER DEFAULT 7,
max_students INTEGER DEFAULT 1000,
max_staff INTEGER DEFAULT 200,
signed_license_token TEXT,
algorithm TEXT DEFAULT 'EdDSA',
status TEXT DEFAULT 'active',
override_reason TEXT,
issued_by TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
);
-- Idempotent Column Additions & Performance Indexing
ALTER TABLE tenant_licenses ADD COLUMN IF NOT EXISTS signed_license_token TEXT;
ALTER TABLE tenant_licenses ADD COLUMN IF NOT EXISTS algorithm TEXT DEFAULT 'EdDSA';
CREATE INDEX IF NOT EXISTS idx_tenant_licenses_active
ON tenant_licenses(tenant_id) WHERE status = 'active';
```
CREATE INDEX IF NOT EXISTS idx_tenant_licenses_active
ON tenant_licenses(tenant_id) WHERE is_active = TRUE;
```
### 1.2 Tenant Local Database (SQLite / Knex)
Location: `E:\nextgen\next-gen\server\src\database\` & `E:\nextgen\next-gen\server\data\school.db`
```sql
-- SQLite Compatible Tenant License Table
CREATE TABLE IF NOT EXISTS tenant_licenses (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
term_name TEXT NOT NULL,
academic_year TEXT NOT NULL,
start_date TEXT NOT NULL,
end_date TEXT NOT NULL,
grace_period_days INTEGER DEFAULT 7,
max_students INTEGER DEFAULT 1000,
max_staff INTEGER DEFAULT 200,
license_key TEXT NOT NULL,
signed_license_token TEXT NOT NULL,
status TEXT DEFAULT 'ACTIVE',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
---
## 2. CRYPTOGRAPHIC LICENSING ENGINE (SUPERADMIN CA)
### 2.1 Ed25519 Asymmetric Key Generation & Signing Service
File to Update: `E:\nextgen\next-gen\superadmin\server\src\services\LicenseTokenGenerator.js`
```javascript
/**
* LicenseTokenGenerator.js
* Acts as the Certificate Authority (CA) using Ed25519 (EdDSA) asymmetric signing.
*/
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
class LicenseTokenGenerator {
/**
* Generates or retrieves the Ed25519 Key Pair
*/
static getKeyPair() {
const privateKeyPem = process.env.SUPERADMIN_PRIVATE_KEY;
const publicKeyPem = process.env.SUPERADMIN_PUBLIC_KEY;
if (privateKeyPem && publicKeyPem) {
return { privateKey: privateKeyPem, publicKey: publicKeyPem };
}
// Fallback keypair generation for development if env variables are not supplied
const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519', {
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
publicKeyEncoding: { type: 'spki', format: 'pem' }
});
return { privateKey, publicKey };
}
/**
* Generate an Ed25519 cryptographically signed license token
*/
static generateToken(payload) {
const { privateKey } = this.getKeyPair();
const nowSeconds = Math.floor(Date.now() / 1000);
const nbf = payload.nbf || Math.floor(new Date(payload.start_date).getTime() / 1000);
const exp = payload.exp || Math.floor(new Date(payload.end_date).getTime() / 1000);
const licensePayload = {
iss: 'super_admin_licensing_engine',
sub: payload.tenant_id,
tenant_code: payload.tenant_code,
term: payload.term_name,
academic_year: payload.academic_year,
max_students: payload.max_students || 1000,
max_staff: payload.max_staff || 200,
nbf: nbf,
exp: exp,
status: 'active',
iat: nowSeconds
};
// Sign payload using Ed25519 (EdDSA algorithm in JWT standard)
const token = jwt.sign(licensePayload, privateKey, {
algorithm: 'EdDSA'
});
const keyFormatted = `LIC-${(payload.term_name || 'TERM').toUpperCase().replace(/\s+/g, '')}-${payload.academic_year}-${token.slice(-16).toUpperCase()}`;
return {
token,
license_key: keyFormatted,
payload: licensePayload
};
}
/**
* Verify an Ed25519 token using the Public Key
*/
static verifyToken(token) {
const { publicKey } = this.getKeyPair();
try {
const decoded = jwt.verify(token, publicKey, { algorithms: ['EdDSA'] });
return { isValid: true, payload: decoded };
} catch (err) {
return { isValid: false, error: err.message };
}
}
}
module.exports = LicenseTokenGenerator;
```
### 2.2 SuperAdmin JWKS & Public Key Distribution Endpoint
File to Update: `E:\nextgen\next-gen\superadmin\server\src\routes\superadmin.routes.js`
Add route `/api/v1/auth/jwks` to expose the Ed25519 Public Key for online tenant bootstrapping:
```javascript
router.get('/v1/auth/jwks', (req, res) => {
const { publicKey } = LicenseTokenGenerator.getKeyPair();
res.json({
keys: [
{
kty: 'OKP',
crv: 'Ed25519',
use: 'sig',
alg: 'EdDSA',
pubKeyPem: publicKey
}
]
});
});
```
---
## 3. HYBRID DUAL-DRIVER CACHING LAYER (TENANT BACKEND)
File to Create: `E:\nextgen\next-gen\server\src\services\LicenseCacheService.js`
```javascript
/**
* LicenseCacheService.js
* Hybrid Dual-Driver Cache (In-Memory LRU default + Redis Adapter)
*/
const { LRUCache } = require('lru-cache');
const redis = require('redis');
// In-Memory LRU Cache Instance (default zero-dependency fallback)
const localMemoryCache = new LRUCache({
max: 500, // Maximum 500 cached tenant tokens
ttl: 1000 * 60 * 60 * 2 // 2 hours default local memory check interval
});
let redisClient = null;
if (process.env.REDIS_URL) {
redisClient = redis.createClient({ url: process.env.REDIS_URL });
redisClient.on('error', (err) => console.error('Redis License Cache Error:', err));
redisClient.connect().catch(() => {
console.warn('⚠️ Could not connect to Redis; defaulting to in-memory LRU cache.');
});
}
const LicenseCache = {
async get(tenantId) {
try {
if (redisClient && redisClient.isOpen) {
const data = await redisClient.get(`license:tenant:${tenantId}`);
return data ? JSON.parse(data) : null;
}
} catch (e) {
console.warn('Redis GET failed, falling back to LRU cache:', e.message);
}
return localMemoryCache.get(tenantId) || null;
},
async set(tenantId, payload, ttlInSeconds) {
try {
if (redisClient && redisClient.isOpen) {
await redisClient.set(`license:tenant:${tenantId}`, JSON.stringify(payload), {
EX: Math.max(ttlInSeconds, 60)
});
return;
}
} catch (e) {
console.warn('Redis SET failed, falling back to LRU cache:', e.message);
}
localMemoryCache.set(tenantId, payload, { ttl: Math.max(ttlInSeconds, 60) * 1000 });
},
async del(tenantId) {
if (redisClient && redisClient.isOpen) {
await redisClient.del(`license:tenant:${tenantId}`);
}
localMemoryCache.delete(tenantId);
}
};
module.exports = LicenseCache;
```
---
## 4. GLOBAL ACCESSMIDDLEWARE & CLOCK DRIFT PROTECTION
File to Update: `E:\nextgen\next-gen\server\src\middleware\licensingMiddleware.js`
```javascript
/**
* licensingMiddleware.js
* Intercepts incoming requests, verifies Ed25519 tokens via Hybrid Cache,
* checks system clock drift, and enforces Read-Only restriction (HTTP 402) on expired licenses.
*/
const jwt = require('jsonwebtoken');
const Database = require('better-sqlite3');
const path = require('path');
const LicenseCache = require('../services/LicenseCacheService');
// Public Key injected via env or fetched from JWKS
const SUPERADMIN_PUBLIC_KEY = process.env.SUPERADMIN_PUBLIC_KEY || `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----`;
let lastSuperAdminTimeSync = Date.now();
let serverTimeDriftOffsetMs = 0;
// Track trusted time sync with SuperAdmin
function updateSystemClockSync(superAdminTimestamp) {
if (superAdminTimestamp) {
const remoteTime = new Date(superAdminTimestamp).getTime();
serverTimeDriftOffsetMs = Math.abs(Date.now() - remoteTime);
lastSuperAdminTimeSync = Date.now();
}
}
function verifyEd25519Token(token) {
try {
const decoded = jwt.verify(token, SUPERADMIN_PUBLIC_KEY, { algorithms: ['EdDSA'] });
return { isValid: true, payload: decoded };
} catch (err) {
return { isValid: false, error: err.message };
}
}
async function verifyLicenseMiddleware(req, res, next) {
const tenantId =
req.headers['x-tenant-id'] ||
req.headers['x-tenant-code'] ||
process.env.TENANT_ID ||
'5b39bcb5-506a-456d-b7cb-91730b397cc1';
req.tenantId = tenantId;
// Bypass routes (Auth, SysAdmin, Health, Billing)
const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/refresh', '/api/sysadmin', '/api/health', '/api/billing'];
if (PUBLIC_PATHS.some(p => req.path.startsWith(p)) || req.user?.role === 'systems_admin') {
return next();
}
// Anti-Clock-Tampering Guard: Lock mutations if system clock has drifted > 5 minutes (300,000 ms)
if (serverTimeDriftOffsetMs > 300000) {
console.error(`🚨 System Clock Drift Detected: ${serverTimeDriftOffsetMs}ms variance.`);
return blockMutationRequests(req, res, 'CLOCK_DRIFT_DETECTED', 'System clock tampering or significant time drift detected.');
}
try {
// 1. Query Hybrid Cache (Memory LRU / Redis)
let cachedPayload = await LicenseCache.get(tenantId);
// 2. Cache Miss Fallback: Read DB raw signed token & verify Ed25519 signature
if (!cachedPayload) {
const dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/school.db');
const db = new Database(dbPath);
const row = db.prepare('SELECT * FROM tenant_licenses WHERE tenant_id = ? AND status = "ACTIVE" ORDER FROM rowid DESC LIMIT 1').get(tenantId);
db.close();
if (!row || !row.signed_license_token) {
return blockMutationRequests(req, res, 'NO_LICENSE', 'No active license token found for tenant.');
}
const verification = verifyEd25519Token(row.signed_license_token);
if (!verification.isValid) {
return blockMutationRequests(req, res, 'INVALID_SIGNATURE', 'License signature verification failed.');
}
cachedPayload = verification.payload;
const nowSec = Math.floor(Date.now() / 1000);
const remainingTTL = (cachedPayload.exp || 0) - nowSec;
if (remainingTTL > 0) {
await LicenseCache.set(tenantId, cachedPayload, remainingTTL);
}
}
// 3. Temporal Bound Check
const currentSeconds = Math.floor(Date.now() / 1000);
if (currentSeconds < cachedPayload.nbf || currentSeconds > cachedPayload.exp) {
return blockMutationRequests(req, res, 'LICENSE_EXPIRED', 'Your termly license has expired.');
}
// License is valid
res.setHeader('X-License-Status', 'ACTIVE');
return next();
} catch (err) {
console.error('License Middleware Error:', err);
return blockMutationRequests(req, res, 'EVALUATION_ERROR', err.message);
}
}
function blockMutationRequests(req, res, code, message) {
const isReadOnly = ['GET', 'HEAD', 'OPTIONS'].includes(req.method.toUpperCase());
if (isReadOnly) {
res.setHeader('X-License-Status', 'Expired-Read-Only');
return req.next ? req.next() : res.status(200);
}
return res.status(402).json({
error: 'Payment Required / Term License Expired',
code: code || 'LICENSE_EXPIRED',
message: message || 'Your termly license has expired. Please contact billing.',
billing_url: '/billing'
});
}
module.exports = {
verifyLicenseMiddleware,
updateSystemClockSync
};
```
---
## 5. CLIENT-SIDE PWA OFFLINE VERIFICATION (REACT VITE)
Location: `E:\nextgen\next-gen\client\src\services\offlineLicenseVerifier.js`
```javascript
/**
* offlineLicenseVerifier.js
* Uses Browser Native Web Crypto API to verify Ed25519 license tokens offline in React PWA.
*/
export async function verifyLicenseOffline(signedToken, publicKeyPem) {
try {
const [headerB64, payloadB64, signatureB64] = signedToken.split('.');
const dataToVerify = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
// Decode base64url signature
const signatureBin = Uint8Array.from(atob(signatureB64.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
const payloadJson = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));
// Simple temporal validation
const nowSeconds = Math.floor(Date.now() / 1000);
if (nowSeconds < payloadJson.nbf || nowSeconds > payloadJson.exp) {
return { isValid: false, reason: 'EXPIRED', payload: payloadJson };
}
return { isValid: true, payload: payloadJson };
} catch (e) {
return { isValid: false, reason: 'INVALID_FORMAT', error: e.message };
}
}
```
---
## 6. STEP-BY-STEP EXECUTION AGENT CHECKLIST
1. **Update Supabase Database Schema (`E:\nextgen\supabase\supabase_full_schema.sql`)**:
- Ensure `tenant_licenses` table includes `signed_license_token TEXT` and `algorithm TEXT DEFAULT 'EdDSA'`.
- Apply schema updates to Supabase PostgreSQL cloud instance via Supabase CLI or SQL Editor:
```bash
supabase db push --schema-file E:\nextgen\supabase\supabase_full_schema.sql
```
2. **Update SuperAdmin CA Generator (`E:\nextgen\next-gen\superadmin\server\src\services\LicenseTokenGenerator.js`)**:
- Replace symmetric HS256 with Ed25519 (EdDSA) signing using `crypto.generateKeyPairSync('ed25519')` or PEM environment keys.
3. **Expose JWKS Endpoint (`E:\nextgen\next-gen\superadmin\server\src\routes\superadmin.routes.js`)**:
- Add `/api/v1/auth/jwks` route returning public keys.
4. **Implement Hybrid Dual-Driver Cache (`E:\nextgen\next-gen\server\src\services\LicenseCacheService.js`)**:
- Implement in-memory LRU (`lru-cache`) with optional Redis fallback.
5. **Update Tenant AccessMiddleware (`E:\nextgen\next-gen\server\src\middleware\licensingMiddleware.js`)**:
- Integrate `LicenseCacheService`.
- Add Ed25519 asymmetric verification.
- Enforce HTTP 402 Payment Required for non-GET/HEAD/OPTIONS requests on expired licenses.
- Add 5-minute system clock drift threshold check.
6. **Configure First Tenant Environment (`E:\nextgen\next-gen\.env` & `E:\nextgen\next-gen\server\.env`)**:
- Populate `SUPERADMIN_PUBLIC_KEY` and `VITE_SUPERADMIN_PUBLIC_KEY`.
7. **Run Verification Commands**:
- Test license creation in SuperAdmin UI and confirm sync to Supabase `tenant_licenses` table.
- Test GET vs POST endpoints on `E:\nextgen\next-gen\server` under active vs expired license tokens.