geocrop-platform./apps/nextgen/server/tests/license-artifact-ingest.tes...

236 lines
7.5 KiB
JavaScript

const { pointAtDevDb } = require('./setup');
pointAtDevDb();
process.env.ENABLE_LICENSING_IN_TESTS = 'true';
const crypto = require('crypto');
const express = require('express');
const request = require('supertest');
const licensingRouter = require('../src/controllers/licensing.controller');
const LicenseCache = require('../src/services/LicenseCacheService');
const { privateKey: TEST_PRIV_KEY, publicKey: TEST_PUB_KEY } = crypto.generateKeyPairSync('ed25519', {
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
publicKeyEncoding: { type: 'spki', format: 'pem' }
});
const { privateKey: OTHER_PRIV_KEY } = crypto.generateKeyPairSync('ed25519', {
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
publicKeyEncoding: { type: 'spki', format: 'pem' }
});
const TENANT_ID = '5b39bcb5-506a-456d-b7cb-91730b397cc1';
function createSignedJwt(claims, privKey = TEST_PRIV_KEY) {
const headerB64 = Buffer.from(JSON.stringify({ alg: 'EdDSA', typ: 'JWT' })).toString('base64url');
const payloadB64 = Buffer.from(JSON.stringify(claims)).toString('base64url');
const data = Buffer.from(`${headerB64}.${payloadB64}`, 'utf8');
const sigB64 = crypto.sign(null, data, privKey).toString('base64url');
return `${headerB64}.${payloadB64}.${sigB64}`;
}
describe('Offline License Artifact Ingestion Endpoint (POST /api/license/ingest)', () => {
let app;
let originalPubKey;
beforeEach(async () => {
originalPubKey = process.env.SUPERADMIN_PUBLIC_KEY;
process.env.SUPERADMIN_PUBLIC_KEY = TEST_PUB_KEY;
await LicenseCache.del(TENANT_ID);
app = express();
app.use(express.json());
app.use('/api/license', licensingRouter);
});
afterEach(async () => {
process.env.SUPERADMIN_PUBLIC_KEY = originalPubKey;
await LicenseCache.del(TENANT_ID);
});
it('successfully verifies and ingests a valid signed .lic.json artifact', async () => {
const claims = {
iss: 'super_admin_licensing_engine',
sub: TENANT_ID,
tenant_code: 'SCH-TEST',
tenant_name: 'AFRICA-ALERT',
term: 'Term 3 Offline',
academic_year: '2026',
max_students: 2500,
max_staff: 350,
nbf: Math.floor(Date.now() / 1000) - 100,
exp: Math.floor(Date.now() / 1000) + 86400 * 90,
status: 'active'
};
const signedToken = createSignedJwt(claims);
const artifact = {
format_version: '1.0',
issued_at: new Date().toISOString(),
issued_by: 'Nextgen LMS SuperAdmin Governance Portal',
tenant: {
id: TENANT_ID,
code: 'SCH-TEST',
name: 'AFRICA-ALERT'
},
license: {
id: 'lic-offline-test-101',
license_key: 'LIC-TERM3OFFLINE-2026-TEST',
signed_license_token: signedToken,
algorithm: 'EdDSA',
term_name: 'Term 3 Offline',
academic_year: '2026',
start_date: '2026-07-01',
end_date: '2026-10-01',
grace_period_days: 7,
max_students: 2500,
max_staff: 350,
status: 'active'
},
checksum: crypto.createHash('sha256').update(signedToken).digest('hex')
};
const res = await request(app)
.post('/api/license/ingest')
.set('x-tenant-id', TENANT_ID)
.send(artifact);
expect(res.status).toBe(200);
expect(res.body.ok).toBe(true);
expect(res.body.status).toBe('ACTIVE');
expect(res.body.license.license_key).toBe('LIC-TERM3OFFLINE-2026-TEST');
expect(res.body.license.term_name).toBe('Term 3 Offline');
});
it('rejects an artifact signed with an untrusted / invalid key', async () => {
const claims = {
iss: 'fake_super_admin',
sub: TENANT_ID,
term: 'Term Forged',
exp: Math.floor(Date.now() / 1000) + 86400 * 90
};
const forgedToken = createSignedJwt(claims, OTHER_PRIV_KEY);
const forgedArtifact = {
format_version: '1.0',
license: {
signed_license_token: forgedToken,
term_name: 'Term Forged'
}
};
const res = await request(app)
.post('/api/license/ingest')
.set('x-tenant-id', TENANT_ID)
.send(forgedArtifact);
expect(res.status).toBe(422);
expect(res.body.error).toMatch(/Cryptographic Signature Verification Failed/i);
});
it('successfully decrypts and ingests an AES-256-GCM encrypted license artifact package', async () => {
const claims = {
iss: 'super_admin_licensing_engine',
sub: TENANT_ID,
tenant_code: 'SCH-ENC',
tenant_name: 'AFRICA-ALERT',
term: 'Term 1 Encrypted',
academic_year: '2026',
max_students: 5000,
max_staff: 400,
exp: Math.floor(Date.now() / 1000) + 86400 * 90,
status: 'active'
};
const token = createSignedJwt(claims);
const rawArtifact = {
tenant: { id: TENANT_ID, code: 'SCH-ENC', name: 'AFRICA-ALERT' },
license: {
id: 'lic-enc-001',
license_key: 'LIC-ENC-2026',
signed_license_token: token,
term_name: 'Term 1 Encrypted',
academic_year: '2026',
start_date: '2026-07-01',
end_date: '2026-10-01',
max_students: 5000,
max_staff: 400,
status: 'active'
}
};
const secret = process.env.LICENSE_ARTIFACT_SECRET || TEST_PUB_KEY;
const key = crypto.createHash('sha256').update(`${secret}:${TENANT_ID}`).digest();
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(JSON.stringify(rawArtifact), 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
const encryptedEnvelope = {
format_version: '2.0',
encrypted: true,
algorithm: 'AES-256-GCM + Ed25519',
tenant_id: TENANT_ID,
iv: iv.toString('hex'),
auth_tag: authTag.toString('hex'),
ciphertext: encrypted.toString('base64')
};
const res = await request(app)
.post('/api/license/ingest')
.set('x-tenant-id', TENANT_ID)
.send(encryptedEnvelope);
expect(res.status).toBe(200);
expect(res.body.ok).toBe(true);
expect(res.body.license.term_name).toBe('Term 1 Encrypted');
});
it('rejects tampered ciphertext in an encrypted license artifact package', async () => {
const encryptedEnvelope = {
format_version: '2.0',
encrypted: true,
algorithm: 'AES-256-GCM + Ed25519',
tenant_id: TENANT_ID,
iv: crypto.randomBytes(12).toString('hex'),
auth_tag: crypto.randomBytes(16).toString('hex'),
ciphertext: Buffer.from('TAMPERED_CIPHERTEXT_BYTES').toString('base64')
};
const res = await request(app)
.post('/api/license/ingest')
.set('x-tenant-id', TENANT_ID)
.send(encryptedEnvelope);
expect(res.status).toBe(422);
expect(res.body.error).toMatch(/Artifact Decryption \/ Tamper Check Failed/i);
});
it('rejects artifact when school name does not match local school identity', async () => {
const claims = {
iss: 'super_admin_licensing_engine',
sub: TENANT_ID,
tenant_name: 'Completely Different School Academy',
term: 'Term 1',
academic_year: '2026',
exp: Math.floor(Date.now() / 1000) + 86400 * 90
};
const token = createSignedJwt(claims);
const artifact = {
tenant: { id: TENANT_ID, name: 'Completely Different School Academy' },
license: {
id: 'lic-mismatch-001',
signed_license_token: token
}
};
const res = await request(app)
.post('/api/license/ingest')
.set('x-tenant-id', TENANT_ID)
.send(artifact);
expect(res.status).toBe(422);
expect(res.body.error).toMatch(/Institution Name Mismatch/i);
});
});