import { tap, expect } from '@git.zone/tstest/tapbundle'; import { CertificateHandler } from '../ts/opsserver/handlers/certificate.handler.js'; import { AcmeCertDoc, DcRouterDb } from '../ts/db/index.js'; import * as plugins from '../ts/plugins.js'; import * as interfaces from '../ts_interfaces/index.js'; type TScope = interfaces.data.TApiTokenScope; const createTestDb = async () => { const storagePath = plugins.path.join( plugins.os.tmpdir(), `dcrouter-cert-api-token-${Date.now()}-${Math.random().toString(16).slice(2)}`, ); DcRouterDb.resetInstance(); const db = DcRouterDb.getInstance({ storagePath, dbName: `dcrouter-test-${Date.now()}-${Math.random().toString(16).slice(2)}`, }); await db.start(); await db.getDb().mongoDb.createCollection('__test_init'); return { async cleanup() { await db.stop(); DcRouterDb.resetInstance(); await plugins.fs.promises.rm(storagePath, { recursive: true, force: true }); }, }; }; const makeApiTokenManager = (scopes: TScope[]) => { const token = { id: 'token-1', name: 'certificate-test-token', scopes, createdBy: 'token-user', createdAt: Date.now(), expiresAt: null, lastUsedAt: null, enabled: true, } as interfaces.data.IStoredApiToken; return { validateToken: async (rawToken: string) => rawToken === 'valid-token' ? token : null, hasScope: (storedToken: interfaces.data.IStoredApiToken, scope: TScope) => storedToken.scopes.includes(scope), }; }; const setupHandler = (scopes: TScope[]) => { const typedrouter = new plugins.typedrequest.TypedRouter(); const opsServerRef: any = { typedrouter, adminHandler: { adminIdentityGuard: { exec: async () => false, }, }, dcRouterRef: { apiTokenManager: makeApiTokenManager(scopes), certificateStatusMap: new Map(), smartProxy: { routeManager: { getRoutes: () => [] }, }, certProvisionScheduler: null, }, }; new CertificateHandler(opsServerRef); return { typedrouter, opsServerRef }; }; const fireTypedRequest = async ( router: plugins.typedrequest.TypedRouter, method: string, request: Record, ) => { return await router.routeAndAddResponse({ method, request, response: {}, correlation: { id: `${method}-${Date.now()}-${Math.random().toString(16).slice(2)}`, phase: 'request', }, } as any, { localRequest: true, skipHooks: true }) as any; }; const testDbPromise = createTestDb(); tap.test('CertificateHandler allows API-token export with certificates:read', async () => { await testDbPromise; const certDoc = new AcmeCertDoc(); certDoc.id = 'cert-1'; certDoc.domainName = 'example.com'; certDoc.created = 1; certDoc.validUntil = 2; certDoc.privateKey = '-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----'; certDoc.publicKey = '-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----'; certDoc.csr = ''; await certDoc.save(); const { typedrouter } = setupHandler(['certificates:read']); const result = await fireTypedRequest(typedrouter, 'exportCertificate', { apiToken: 'valid-token', domain: 'example.com', }); expect(result.error).toBeUndefined(); expect(result.response.success).toEqual(true); expect(result.response.cert.domainName).toEqual('example.com'); expect(result.response.cert.privateKey).toContain('BEGIN PRIVATE KEY'); expect(result.response.cert.publicKey).toContain('BEGIN CERTIFICATE'); }); tap.test('CertificateHandler rejects API-token export without certificates:read', async () => { const { typedrouter } = setupHandler(['certificates:write']); const result = await fireTypedRequest(typedrouter, 'exportCertificate', { apiToken: 'valid-token', domain: 'example.com', }); expect(result.error?.text).toEqual('insufficient scope'); }); tap.test('CertificateHandler allows API-token import with certificates:write', async () => { await testDbPromise; const { typedrouter, opsServerRef } = setupHandler(['certificates:write']); const result = await fireTypedRequest(typedrouter, 'importCertificate', { apiToken: 'valid-token', cert: { id: 'cert-2', domainName: 'imported.example.com', created: 3, validUntil: 4, privateKey: '-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----', publicKey: '-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----', csr: '', }, }); expect(result.error).toBeUndefined(); expect(result.response.success).toEqual(true); expect((await AcmeCertDoc.findByDomain('imported.example.com'))?.id).toEqual('cert-2'); expect(opsServerRef.dcRouterRef.certificateStatusMap.get('imported.example.com')?.status).toEqual('valid'); }); tap.test('cleanup test db', async () => { const testDb = await testDbPromise; await testDb.cleanup(); }); export default tap.start();