156 lines
4.8 KiB
TypeScript
156 lines
4.8 KiB
TypeScript
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<string, any>,
|
|
) => {
|
|
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();
|