BREAKING CHANGE(smartacme): Make wildcard certificates opt-in to fix HTTP-01 only configurations
This commit is contained in:
		
							
								
								
									
										17
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,5 +1,22 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## 2025-05-19 - 8.0.0 - BREAKING CHANGE(smartacme)
 | 
			
		||||
Make wildcard certificates opt-in to fix HTTP-01 only configurations
 | 
			
		||||
 | 
			
		||||
- BREAKING CHANGE: Wildcard certificates are no longer automatically requested for all domains
 | 
			
		||||
- Added 'includeWildcard' option to getCertificateForDomain() to explicitly request wildcard certificates
 | 
			
		||||
- HTTP-01 only configurations now work correctly as they do not try to request wildcard certificates automatically
 | 
			
		||||
- Updated certificate CSR generation to match the requested domain configuration
 | 
			
		||||
 | 
			
		||||
## 2025-05-19 - 7.4.0 - feat(smartacme)
 | 
			
		||||
Make wildcard certificates opt-in to fix HTTP-01 only configurations
 | 
			
		||||
 | 
			
		||||
- BREAKING CHANGE: Wildcard certificates are no longer automatically requested for all domains
 | 
			
		||||
- Added `includeWildcard` option to `getCertificateForDomain()` to explicitly request wildcards
 | 
			
		||||
- HTTP-01 only configurations now work correctly as they no longer attempt wildcard certificates
 | 
			
		||||
- Wildcard certificates require DNS-01 handler and must be explicitly requested
 | 
			
		||||
- Updated certificate CSR generation to match the requested domain configuration
 | 
			
		||||
 | 
			
		||||
## 2025-05-18 - 7.3.4 - fix(smartacme)
 | 
			
		||||
Refine documentation and tests for improved clarity in ACME certificate management
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@push.rocks/smartacme",
 | 
			
		||||
  "version": "7.3.4",
 | 
			
		||||
  "version": "7.4.0",
 | 
			
		||||
  "private": false,
 | 
			
		||||
  "description": "A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.",
 | 
			
		||||
  "main": "dist_ts/index.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,13 @@
 | 
			
		||||
 - this repo is dependent on letsencrypt and its limits
 | 
			
		||||
 - to simpify the outside API, smartacme is stateful, meaning it works with a mongodb and a collection called 'SmartacmeCert'.
 | 
			
		||||
 - to simpify the outside API, smartacme is stateful, meaning it works with a mongodb and a collection called 'SmartacmeCert'.
 | 
			
		||||
 | 
			
		||||
## Certificate Request Behavior
 | 
			
		||||
 | 
			
		||||
As of v7.4.0, SmartAcme no longer automatically requests wildcard certificates for all domain requests. This change was made to fix issues with HTTP-01 only configurations which cannot validate wildcard domains.
 | 
			
		||||
 | 
			
		||||
- By default, `getCertificateForDomain('example.com')` only requests a certificate for `example.com`
 | 
			
		||||
- To request both regular and wildcard certificates, use `getCertificateForDomain('example.com', { includeWildcard: true })`
 | 
			
		||||
- Wildcard certificates require a DNS-01 challenge handler to be configured
 | 
			
		||||
- Direct wildcard requests like `getCertificateForDomain('*.example.com')` only request the wildcard certificate
 | 
			
		||||
 | 
			
		||||
This change ensures HTTP-01 only configurations work properly while still allowing wildcard certificates when needed and supported.
 | 
			
		||||
							
								
								
									
										10
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								readme.md
									
									
									
									
									
								
							@@ -196,8 +196,13 @@ async function main() {
 | 
			
		||||
  await smartAcmeInstance.start();
 | 
			
		||||
 | 
			
		||||
  const myDomain = 'example.com';
 | 
			
		||||
  // Get certificate for domain (no wildcard)
 | 
			
		||||
  const myCert = await smartAcmeInstance.getCertificateForDomain(myDomain);
 | 
			
		||||
  console.log('Certificate:', myCert);
 | 
			
		||||
  
 | 
			
		||||
  // Get certificate with wildcard (requires DNS-01 handler)
 | 
			
		||||
  const certWithWildcard = await smartAcmeInstance.getCertificateForDomain(myDomain, { includeWildcard: true });
 | 
			
		||||
  console.log('Certificate with wildcard:', certWithWildcard);
 | 
			
		||||
 | 
			
		||||
  await smartAcmeInstance.stop();
 | 
			
		||||
}
 | 
			
		||||
@@ -306,7 +311,10 @@ The certificate object obtained from the `getCertificateForDomain` method has th
 | 
			
		||||
 | 
			
		||||
- **start()**: Initializes the SmartAcme instance, sets up the ACME client, and registers the account with Let's Encrypt.
 | 
			
		||||
- **stop()**: Closes the MongoDB connection and performs any necessary cleanup.
 | 
			
		||||
- **getCertificateForDomain(domainArg: string)**: Retrieves or obtains a certificate for the specified domain name. If a valid certificate exists in the database, it is returned. Otherwise, a new certificate is requested and stored.
 | 
			
		||||
- **getCertificateForDomain(domainArg: string, options?: { includeWildcard?: boolean })**: Retrieves or obtains a certificate for the specified domain name. If a valid certificate exists in the database, it is returned. Otherwise, a new certificate is requested and stored. 
 | 
			
		||||
  - By default, only a certificate for the exact domain is requested
 | 
			
		||||
  - Set `includeWildcard: true` to also request a wildcard certificate (requires DNS-01 handler)
 | 
			
		||||
  - When requesting a wildcard directly (e.g., `*.example.com`), only the wildcard certificate is requested
 | 
			
		||||
 | 
			
		||||
### Handling Domain Matching
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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();
 | 
			
		||||
@@ -3,6 +3,6 @@
 | 
			
		||||
 */
 | 
			
		||||
export const commitinfo = {
 | 
			
		||||
  name: '@push.rocks/smartacme',
 | 
			
		||||
  version: '7.3.4',
 | 
			
		||||
  version: '8.0.0',
 | 
			
		||||
  description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -221,8 +221,12 @@ export class SmartAcme {
 | 
			
		||||
   * * retrieve it from the databse and return it
 | 
			
		||||
   *
 | 
			
		||||
   * @param domainArg
 | 
			
		||||
   * @param options Optional configuration for certificate generation
 | 
			
		||||
   */
 | 
			
		||||
  public async getCertificateForDomain(domainArg: string): Promise<SmartacmeCert> {
 | 
			
		||||
  public async getCertificateForDomain(
 | 
			
		||||
    domainArg: string,
 | 
			
		||||
    options?: { includeWildcard?: boolean }
 | 
			
		||||
  ): Promise<SmartacmeCert> {
 | 
			
		||||
    // Determine if this is a wildcard request (e.g., '*.example.com').
 | 
			
		||||
    const isWildcardRequest = domainArg.startsWith('*.');
 | 
			
		||||
    // Determine the base domain for certificate retrieval/issuance.
 | 
			
		||||
@@ -259,12 +263,32 @@ export class SmartAcme {
 | 
			
		||||
    // lets make sure others get the same interest
 | 
			
		||||
    const currentDomainInterst = await this.interestMap.addInterest(certDomainName);
 | 
			
		||||
 | 
			
		||||
    // Build identifiers array based on request
 | 
			
		||||
    const identifiers = [];
 | 
			
		||||
    
 | 
			
		||||
    if (isWildcardRequest) {
 | 
			
		||||
      // If requesting a wildcard directly, only add the wildcard
 | 
			
		||||
      identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
 | 
			
		||||
    } else {
 | 
			
		||||
      // Add the regular domain
 | 
			
		||||
      identifiers.push({ type: 'dns', value: certDomainName });
 | 
			
		||||
      
 | 
			
		||||
      // Only add wildcard if explicitly requested
 | 
			
		||||
      if (options?.includeWildcard) {
 | 
			
		||||
        const hasDnsHandler = this.challengeHandlers.some((h) =>
 | 
			
		||||
          h.getSupportedTypes().includes('dns-01'),
 | 
			
		||||
        );
 | 
			
		||||
        if (!hasDnsHandler) {
 | 
			
		||||
          this.logger.log('warn', 'Wildcard certificate requested but no DNS-01 handler available. Skipping wildcard.');
 | 
			
		||||
        } else {
 | 
			
		||||
          identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Place new order with retry */
 | 
			
		||||
    const order = await this.retry(() => this.client.createOrder({
 | 
			
		||||
      identifiers: [
 | 
			
		||||
        { type: 'dns', value: certDomainName },
 | 
			
		||||
        { type: 'dns', value: `*.${certDomainName}` },
 | 
			
		||||
      ],
 | 
			
		||||
      identifiers,
 | 
			
		||||
    }), 'createOrder');
 | 
			
		||||
 | 
			
		||||
    /* Get authorizations and select challenges */
 | 
			
		||||
@@ -359,9 +383,25 @@ export class SmartAcme {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Finalize order */
 | 
			
		||||
    const csrDomains = [];
 | 
			
		||||
    let commonName: string;
 | 
			
		||||
    
 | 
			
		||||
    if (isWildcardRequest) {
 | 
			
		||||
      // For wildcard requests, use wildcard as common name
 | 
			
		||||
      commonName = `*.${certDomainName}`;
 | 
			
		||||
      csrDomains.push(certDomainName); // Add base domain as alt name
 | 
			
		||||
    } else {
 | 
			
		||||
      // For regular requests, use base domain as common name
 | 
			
		||||
      commonName = certDomainName;
 | 
			
		||||
      if (options?.includeWildcard && identifiers.some(id => id.value === `*.${certDomainName}`)) {
 | 
			
		||||
        // If wildcard was successfully added, include it as alt name
 | 
			
		||||
        csrDomains.push(`*.${certDomainName}`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const [key, csr] = await plugins.acme.forge.createCsr({
 | 
			
		||||
      commonName: `*.${certDomainName}`,
 | 
			
		||||
      altNames: [certDomainName],
 | 
			
		||||
      commonName,
 | 
			
		||||
      altNames: csrDomains,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await this.retry(() => this.client.finalizeOrder(order, csr), 'finalizeOrder');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user