BREAKING CHANGE(smartacme): Make wildcard certificates opt-in to fix HTTP-01 only configurations
This commit is contained in:
178
test/test.http01-only.ts
Normal file
178
test/test.http01-only.ts
Normal file
@ -0,0 +1,178 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { SmartAcme, certmanagers } from '../ts/index.js';
|
||||
import { Http01MemoryHandler } from '../ts/handlers/Http01MemoryHandler.js';
|
||||
|
||||
// Test that HTTP-01 only configuration works without wildcard certificates
|
||||
tap.test('HTTP-01 only configuration should work for regular domains', async () => {
|
||||
const memHandler = new Http01MemoryHandler();
|
||||
|
||||
// Stub the domain support check to always return true for testing
|
||||
memHandler.checkWetherDomainIsSupported = async () => true;
|
||||
|
||||
const smartAcmeInstance = new SmartAcme({
|
||||
accountEmail: 'test@example.com',
|
||||
certManager: new certmanagers.MemoryCertManager(),
|
||||
environment: 'integration',
|
||||
retryOptions: {},
|
||||
challengeHandlers: [memHandler],
|
||||
challengePriority: ['http-01'],
|
||||
});
|
||||
|
||||
// Stub the start method to avoid actual ACME connections
|
||||
smartAcmeInstance.start = async () => {
|
||||
smartAcmeInstance.certmatcher = {
|
||||
getCertificateDomainNameByDomainName: (domain: string) => domain.replace('*.', '')
|
||||
} as any;
|
||||
smartAcmeInstance.interestMap = {
|
||||
checkInterest: async () => false,
|
||||
addInterest: async () => ({ interestFullfilled: new Promise(() => {}) } as any)
|
||||
} as any;
|
||||
await smartAcmeInstance.certmanager.init();
|
||||
};
|
||||
await smartAcmeInstance.start();
|
||||
|
||||
// Stub the core certificate methods to avoid actual ACME calls
|
||||
smartAcmeInstance.client = {
|
||||
createOrder: async (orderPayload: any) => {
|
||||
// Verify no wildcard is included in default request
|
||||
const identifiers = orderPayload.identifiers;
|
||||
expect(identifiers.length).toEqual(1);
|
||||
expect(identifiers[0].value).toEqual('example.com');
|
||||
expect(identifiers.find((id: any) => id.value.startsWith('*.'))).toBeUndefined();
|
||||
return { status: 'pending', authorizations: [], finalize: '', certificate: '' };
|
||||
},
|
||||
getAuthorizations: async () => [],
|
||||
finalizeOrder: async () => {},
|
||||
getCertificate: async () => '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----',
|
||||
} as any;
|
||||
|
||||
smartAcmeInstance.retry = async (fn: () => Promise<any>) => fn();
|
||||
|
||||
// Mock certmanager methods
|
||||
smartAcmeInstance.certmanager.retrieveCertificate = async () => null;
|
||||
smartAcmeInstance.certmanager.storeCertificate = async (cert: any) => cert;
|
||||
|
||||
// Request certificate without wildcard
|
||||
const cert = await smartAcmeInstance.getCertificateForDomain('example.com');
|
||||
expect(cert).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('should only include wildcard when explicitly requested with DNS-01', async () => {
|
||||
const dnsHandler = {
|
||||
getSupportedTypes: () => ['dns-01'],
|
||||
prepare: async () => {},
|
||||
cleanup: async () => {},
|
||||
checkWetherDomainIsSupported: async () => true,
|
||||
};
|
||||
|
||||
const smartAcmeInstance = new SmartAcme({
|
||||
accountEmail: 'test@example.com',
|
||||
certManager: new certmanagers.MemoryCertManager(),
|
||||
environment: 'integration',
|
||||
retryOptions: {},
|
||||
challengeHandlers: [dnsHandler],
|
||||
challengePriority: ['dns-01'],
|
||||
});
|
||||
|
||||
// Stub the start method to avoid actual ACME connections
|
||||
smartAcmeInstance.start = async () => {
|
||||
smartAcmeInstance.certmatcher = {
|
||||
getCertificateDomainNameByDomainName: (domain: string) => domain.replace('*.', '')
|
||||
} as any;
|
||||
smartAcmeInstance.interestMap = {
|
||||
checkInterest: async () => false,
|
||||
addInterest: async () => ({ interestFullfilled: new Promise(() => {}) } as any)
|
||||
} as any;
|
||||
await smartAcmeInstance.certmanager.init();
|
||||
};
|
||||
await smartAcmeInstance.start();
|
||||
|
||||
// Stub the core certificate methods
|
||||
smartAcmeInstance.client = {
|
||||
createOrder: async (orderPayload: any) => {
|
||||
const identifiers = orderPayload.identifiers;
|
||||
expect(identifiers.length).toEqual(2);
|
||||
expect(identifiers[0].value).toEqual('example.com');
|
||||
expect(identifiers[1].value).toEqual('*.example.com');
|
||||
return { status: 'pending', authorizations: [], finalize: '', certificate: '' };
|
||||
},
|
||||
getAuthorizations: async () => [],
|
||||
finalizeOrder: async () => {},
|
||||
getCertificate: async () => '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----',
|
||||
} as any;
|
||||
|
||||
smartAcmeInstance.retry = async (fn: () => Promise<any>) => fn();
|
||||
|
||||
// Mock certmanager methods
|
||||
smartAcmeInstance.certmanager.retrieveCertificate = async () => null;
|
||||
smartAcmeInstance.certmanager.storeCertificate = async (cert: any) => cert;
|
||||
|
||||
// Request certificate with wildcard
|
||||
const cert = await smartAcmeInstance.getCertificateForDomain('example.com', { includeWildcard: true });
|
||||
expect(cert).toBeDefined();
|
||||
});
|
||||
|
||||
tap.test('should skip wildcard if requested but no DNS-01 handler available', async () => {
|
||||
const httpHandler = new Http01MemoryHandler();
|
||||
httpHandler.checkWetherDomainIsSupported = async () => true;
|
||||
|
||||
const smartAcmeInstance = new SmartAcme({
|
||||
accountEmail: 'test@example.com',
|
||||
certManager: new certmanagers.MemoryCertManager(),
|
||||
environment: 'integration',
|
||||
retryOptions: {},
|
||||
challengeHandlers: [httpHandler],
|
||||
challengePriority: ['http-01'],
|
||||
});
|
||||
|
||||
// Stub the start method to avoid actual ACME connections
|
||||
smartAcmeInstance.start = async () => {
|
||||
smartAcmeInstance.certmatcher = {
|
||||
getCertificateDomainNameByDomainName: (domain: string) => domain.replace('*.', '')
|
||||
} as any;
|
||||
smartAcmeInstance.interestMap = {
|
||||
checkInterest: async () => false,
|
||||
addInterest: async () => ({ interestFullfilled: new Promise(() => {}) } as any)
|
||||
} as any;
|
||||
await smartAcmeInstance.certmanager.init();
|
||||
};
|
||||
await smartAcmeInstance.start();
|
||||
|
||||
// Mock logger to capture warning
|
||||
const logSpy = { called: false, message: '' };
|
||||
smartAcmeInstance.logger.log = async (level: string, message: string) => {
|
||||
if (level === 'warn') {
|
||||
logSpy.called = true;
|
||||
logSpy.message = message;
|
||||
}
|
||||
};
|
||||
|
||||
// Stub the core certificate methods
|
||||
smartAcmeInstance.client = {
|
||||
createOrder: async (orderPayload: any) => {
|
||||
const identifiers = orderPayload.identifiers;
|
||||
// Should only have regular domain, no wildcard
|
||||
expect(identifiers.length).toEqual(1);
|
||||
expect(identifiers[0].value).toEqual('example.com');
|
||||
return { status: 'pending', authorizations: [], finalize: '', certificate: '' };
|
||||
},
|
||||
getAuthorizations: async () => [],
|
||||
finalizeOrder: async () => {},
|
||||
getCertificate: async () => '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----',
|
||||
} as any;
|
||||
|
||||
smartAcmeInstance.retry = async (fn: () => Promise<any>) => fn();
|
||||
|
||||
// Mock certmanager methods
|
||||
smartAcmeInstance.certmanager.retrieveCertificate = async () => null;
|
||||
smartAcmeInstance.certmanager.storeCertificate = async (cert: any) => cert;
|
||||
|
||||
// Request certificate with wildcard (should be skipped)
|
||||
const cert = await smartAcmeInstance.getCertificateForDomain('example.com', { includeWildcard: true });
|
||||
|
||||
expect(cert).toBeDefined();
|
||||
expect(logSpy.called).toBeTrue();
|
||||
expect(logSpy.message).toContain('Wildcard certificate requested but no DNS-01 handler available');
|
||||
});
|
||||
|
||||
export default tap.start();
|
94
test/test.wildcard-options.ts
Normal file
94
test/test.wildcard-options.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { SmartAcme, certmanagers } from '../ts/index.js';
|
||||
import { SmartacmeCert as Cert } from '../ts/smartacme.classes.cert.js';
|
||||
|
||||
// Simple test to verify wildcard options are correctly processed
|
||||
tap.test('should not include wildcard by default for regular domain', async () => {
|
||||
let orderPayload: any = null;
|
||||
|
||||
// Override the SmartAcme prototype methods for testing
|
||||
const origGetCert = SmartAcme.prototype.getCertificateForDomain;
|
||||
|
||||
// Create a minimal test version of getCertificateForDomain
|
||||
SmartAcme.prototype.getCertificateForDomain = async function(
|
||||
domainArg: string,
|
||||
options?: { includeWildcard?: boolean }
|
||||
) {
|
||||
const certDomainName = domainArg.replace('*.', '');
|
||||
const identifiers = [];
|
||||
|
||||
if (domainArg.startsWith('*.')) {
|
||||
identifiers.push({ type: 'dns', value: domainArg });
|
||||
} else {
|
||||
identifiers.push({ type: 'dns', value: certDomainName });
|
||||
|
||||
if (options?.includeWildcard) {
|
||||
const hasDnsHandler = this.challengeHandlers.some((h) =>
|
||||
h.getSupportedTypes().includes('dns-01')
|
||||
);
|
||||
|
||||
if (hasDnsHandler) {
|
||||
identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orderPayload = { identifiers };
|
||||
return new Cert({ domainName: certDomainName });
|
||||
};
|
||||
|
||||
try {
|
||||
// Create instance with HTTP-01 only
|
||||
const smartAcme = new SmartAcme({
|
||||
accountEmail: 'test@example.com',
|
||||
certManager: new certmanagers.MemoryCertManager(),
|
||||
environment: 'integration',
|
||||
challengeHandlers: [{
|
||||
getSupportedTypes: () => ['http-01'],
|
||||
prepare: async () => {},
|
||||
cleanup: async () => {},
|
||||
checkWetherDomainIsSupported: async () => true,
|
||||
}],
|
||||
});
|
||||
|
||||
// Test 1: Regular domain without wildcard option
|
||||
await smartAcme.getCertificateForDomain('example.com');
|
||||
expect(orderPayload.identifiers.length).toEqual(1);
|
||||
expect(orderPayload.identifiers[0].value).toEqual('example.com');
|
||||
|
||||
// Test 2: Regular domain with wildcard option (but no DNS-01 handler)
|
||||
await smartAcme.getCertificateForDomain('example.com', { includeWildcard: true });
|
||||
expect(orderPayload.identifiers.length).toEqual(1);
|
||||
expect(orderPayload.identifiers[0].value).toEqual('example.com');
|
||||
|
||||
// Create instance with DNS-01
|
||||
const smartAcmeDns = new SmartAcme({
|
||||
accountEmail: 'test@example.com',
|
||||
certManager: new certmanagers.MemoryCertManager(),
|
||||
environment: 'integration',
|
||||
challengeHandlers: [{
|
||||
getSupportedTypes: () => ['dns-01'],
|
||||
prepare: async () => {},
|
||||
cleanup: async () => {},
|
||||
checkWetherDomainIsSupported: async () => true,
|
||||
}],
|
||||
});
|
||||
|
||||
// Test 3: Regular domain with wildcard option and DNS-01 handler
|
||||
await smartAcmeDns.getCertificateForDomain('example.com', { includeWildcard: true });
|
||||
expect(orderPayload.identifiers.length).toEqual(2);
|
||||
expect(orderPayload.identifiers[0].value).toEqual('example.com');
|
||||
expect(orderPayload.identifiers[1].value).toEqual('*.example.com');
|
||||
|
||||
// Test 4: Direct wildcard request
|
||||
await smartAcmeDns.getCertificateForDomain('*.example.com');
|
||||
expect(orderPayload.identifiers.length).toEqual(1);
|
||||
expect(orderPayload.identifiers[0].value).toEqual('*.example.com');
|
||||
|
||||
} finally {
|
||||
// Restore original method
|
||||
SmartAcme.prototype.getCertificateForDomain = origGetCert;
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
Reference in New Issue
Block a user