Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
3fa34fa373 | |||
086eea1aa2 | |||
dcc89f0088 | |||
695a515990 | |||
01f7018540 | |||
3cee6c534a | |||
47d1609a49 | |||
d69eb73afc | |||
0a1d617ce3 | |||
f78b50757c | |||
0e6bbc5be6 | |||
10511b4293 | |||
d456876de7 | |||
fe495a5f03 |
57
changelog.md
57
changelog.md
@ -1,5 +1,62 @@
|
|||||||
# Changelog
|
# 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
|
||||||
|
|
||||||
|
- Enhanced the README with detailed usage, configuration, and example sections
|
||||||
|
- Refined test cases for certificate matching and challenge handlers across DNS-01 and HTTP-01
|
||||||
|
- Updated TypeScript definitions and inline comments for better developer experience
|
||||||
|
|
||||||
|
## 2025-05-05 - 7.3.3 - fix(SmartAcme)
|
||||||
|
Remove duplicate challengeHandlers declaration from SmartAcme class
|
||||||
|
|
||||||
|
- Eliminated the redundant private declaration of challengeHandlers since it is already defined as a public property
|
||||||
|
- Ensures a single source of truth and clearer interface for challenge handler configuration
|
||||||
|
|
||||||
|
## 2025-05-05 - 7.3.2 - fix(test)
|
||||||
|
Add missing checkWetherDomainIsSupported implementation to DummyHandler for interface compliance in tests
|
||||||
|
|
||||||
|
- Implemented the missing checkWetherDomainIsSupported method in the DummyHandler to satisfy IChallengeHandler interface requirements
|
||||||
|
- Ensured that tests now correctly instantiate the DummyHandler without interface errors
|
||||||
|
|
||||||
|
## 2025-05-05 - 7.3.1 - fix(core)
|
||||||
|
Refactor import paths and update dependency references
|
||||||
|
|
||||||
|
- Replaced deprecated 'smartacme.plugins.js' with the new 'plugins.js' across cert managers, handlers, and core classes
|
||||||
|
- Added missing dependencies (@push.rocks/smartfile and @push.rocks/smartnetwork) in package.json
|
||||||
|
- Updated HTTP challenge handlers to include domain support checks via checkWetherDomainIsSupported
|
||||||
|
- Adjusted import paths in MongoCertManager, MemoryCertManager, and DNS-01 handler for consistency
|
||||||
|
|
||||||
|
## 2025-05-05 - 7.3.0 - feat(index)
|
||||||
|
Bump @tsclass/tsclass to 9.2.0 and update module exports to include handlers
|
||||||
|
|
||||||
|
- Upgrade @tsclass/tsclass dependency from 9.1.0 to 9.2.0 in package.json
|
||||||
|
- Add explicit export of handlers in ts/index.ts to improve module accessibility
|
||||||
|
|
||||||
|
## 2025-05-05 - 7.2.5 - fix(smartacme)
|
||||||
|
Refactor module exports and update wildcard certificate support documentation
|
||||||
|
|
||||||
|
- Updated readme.plan.md to streamline and remove obsolete wildcard plan details
|
||||||
|
- Normalized certmanager imports by consolidating exports in ts/index.ts and updating tests accordingly
|
||||||
|
- Reordered ISmartAcmeOptions interface properties for clarity (accountEmail moved to the top)
|
||||||
|
|
||||||
## 2025-05-04 - 7.2.4 - fix(test)
|
## 2025-05-04 - 7.2.4 - fix(test)
|
||||||
Refactor wildcard certificate test to properly stub SmartAcme.start and getCertificateForDomain for robust integration.
|
Refactor wildcard certificate test to properly stub SmartAcme.start and getCertificateForDomain for robust integration.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartacme",
|
"name": "@push.rocks/smartacme",
|
||||||
"version": "7.2.4",
|
"version": "8.0.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.",
|
"description": "A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@ -45,13 +45,15 @@
|
|||||||
"@push.rocks/smartdata": "^5.15.1",
|
"@push.rocks/smartdata": "^5.15.1",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartdns": "^6.2.2",
|
"@push.rocks/smartdns": "^6.2.2",
|
||||||
|
"@push.rocks/smartfile": "^11.2.0",
|
||||||
"@push.rocks/smartlog": "^3.0.7",
|
"@push.rocks/smartlog": "^3.0.7",
|
||||||
|
"@push.rocks/smartnetwork": "^4.0.1",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartrequest": "^2.1.0",
|
"@push.rocks/smartrequest": "^2.1.0",
|
||||||
"@push.rocks/smartstring": "^4.0.15",
|
"@push.rocks/smartstring": "^4.0.15",
|
||||||
"@push.rocks/smarttime": "^4.1.1",
|
"@push.rocks/smarttime": "^4.1.1",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@tsclass/tsclass": "^9.1.0",
|
"@tsclass/tsclass": "^9.2.0",
|
||||||
"acme-client": "^5.4.0"
|
"acme-client": "^5.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
125
pnpm-lock.yaml
generated
125
pnpm-lock.yaml
generated
@ -26,9 +26,15 @@ importers:
|
|||||||
'@push.rocks/smartdns':
|
'@push.rocks/smartdns':
|
||||||
specifier: ^6.2.2
|
specifier: ^6.2.2
|
||||||
version: 6.2.2
|
version: 6.2.2
|
||||||
|
'@push.rocks/smartfile':
|
||||||
|
specifier: ^11.2.0
|
||||||
|
version: 11.2.0
|
||||||
'@push.rocks/smartlog':
|
'@push.rocks/smartlog':
|
||||||
specifier: ^3.0.7
|
specifier: ^3.0.7
|
||||||
version: 3.0.7
|
version: 3.0.7
|
||||||
|
'@push.rocks/smartnetwork':
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1
|
||||||
'@push.rocks/smartpromise':
|
'@push.rocks/smartpromise':
|
||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
@ -45,8 +51,8 @@ importers:
|
|||||||
specifier: ^3.0.9
|
specifier: ^3.0.9
|
||||||
version: 3.0.9
|
version: 3.0.9
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.1.0
|
specifier: ^9.2.0
|
||||||
version: 9.1.0
|
version: 9.2.0
|
||||||
acme-client:
|
acme-client:
|
||||||
specifier: ^5.4.0
|
specifier: ^5.4.0
|
||||||
version: 5.4.0
|
version: 5.4.0
|
||||||
@ -878,6 +884,9 @@ packages:
|
|||||||
'@push.rocks/smartnetwork@3.0.2':
|
'@push.rocks/smartnetwork@3.0.2':
|
||||||
resolution: {integrity: sha512-s6CNGzQ1n/d/6cOKXbxeW6/tO//dr1woLqI01g7XhqTriw0nsm2G2kWaZh2J0VOguGNWBgQVCIpR0LjdRNWb3g==}
|
resolution: {integrity: sha512-s6CNGzQ1n/d/6cOKXbxeW6/tO//dr1woLqI01g7XhqTriw0nsm2G2kWaZh2J0VOguGNWBgQVCIpR0LjdRNWb3g==}
|
||||||
|
|
||||||
|
'@push.rocks/smartnetwork@4.0.1':
|
||||||
|
resolution: {integrity: sha512-zLH88bKY6/cK6vVnCW4Fsugu4T+l6OerWWappit+BecdnQ6vrgShXSAa13JIkkWkWcs4dxEirlEfycQEEQw8BQ==}
|
||||||
|
|
||||||
'@push.rocks/smartnpm@2.0.4':
|
'@push.rocks/smartnpm@2.0.4':
|
||||||
resolution: {integrity: sha512-ljRPqnUsXzL5qnuAEt5POy0NnfKs7eYPuuJPJjYiK9VUdP/CyF4h14qTB4H816vNEuF7VU/ASRtz0qDlXmrztg==}
|
resolution: {integrity: sha512-ljRPqnUsXzL5qnuAEt5POy0NnfKs7eYPuuJPJjYiK9VUdP/CyF4h14qTB4H816vNEuF7VU/ASRtz0qDlXmrztg==}
|
||||||
|
|
||||||
@ -896,6 +905,9 @@ packages:
|
|||||||
'@push.rocks/smartpdf@3.2.2':
|
'@push.rocks/smartpdf@3.2.2':
|
||||||
resolution: {integrity: sha512-SKGNHz7HsgU6uVSVrRCL13kIeAFMvd4oQBLI3VmPcMkxXfWNPJkb6jKknqP8bhobWA/ryJS+3Dj///UELUvVKQ==}
|
resolution: {integrity: sha512-SKGNHz7HsgU6uVSVrRCL13kIeAFMvd4oQBLI3VmPcMkxXfWNPJkb6jKknqP8bhobWA/ryJS+3Dj///UELUvVKQ==}
|
||||||
|
|
||||||
|
'@push.rocks/smartping@1.0.8':
|
||||||
|
resolution: {integrity: sha512-Fvx1Db6hSsDOI6pdiCuS9GjtOX8ugx865YQrPg5vK2iw6Qj/srwyXcWLFYt+19WVKtvtWDJIAKbW+q3bXFsCeA==}
|
||||||
|
|
||||||
'@push.rocks/smartpromise@4.2.3':
|
'@push.rocks/smartpromise@4.2.3':
|
||||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||||
|
|
||||||
@ -1342,8 +1354,8 @@ packages:
|
|||||||
'@tsclass/tsclass@8.2.1':
|
'@tsclass/tsclass@8.2.1':
|
||||||
resolution: {integrity: sha512-bRDCfJTipsTcK6eEokWdsOR1mGCQFeM7zTg6PRHzbxTWQcWQD9AhEr2q3CrPcmAbvIS7fvkO6/pU/mPm1MZxhQ==}
|
resolution: {integrity: sha512-bRDCfJTipsTcK6eEokWdsOR1mGCQFeM7zTg6PRHzbxTWQcWQD9AhEr2q3CrPcmAbvIS7fvkO6/pU/mPm1MZxhQ==}
|
||||||
|
|
||||||
'@tsclass/tsclass@9.1.0':
|
'@tsclass/tsclass@9.2.0':
|
||||||
resolution: {integrity: sha512-PkG1bXK/bqVtxaRHje+iJHjtcdRHLHrNTOkzqh+jv2A7mgiyNo2YBJIl4eEJLkw1X3FwEFU4vCAtsegSmJgRug==}
|
resolution: {integrity: sha512-A6ULEkQfYgOnCKQVQRt26O7PRzFo4PE2EoD25RAtnuFuVrNwGynYC20Vee2c8KAOyI7nQ/LaREki9KAX4AHOHQ==}
|
||||||
|
|
||||||
'@types/accepts@1.3.7':
|
'@types/accepts@1.3.7':
|
||||||
resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
|
resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
|
||||||
@ -1402,6 +1414,9 @@ packages:
|
|||||||
'@types/default-gateway@3.0.1':
|
'@types/default-gateway@3.0.1':
|
||||||
resolution: {integrity: sha512-tpu0hp+AOIzwdAHyZPzLE5pCf9uT0pb+xZ76T4S7MrY2YTVq918Q7Q2VQ3KCVQqYxM7nxuCK/SL3X97jBEIeKQ==}
|
resolution: {integrity: sha512-tpu0hp+AOIzwdAHyZPzLE5pCf9uT0pb+xZ76T4S7MrY2YTVq918Q7Q2VQ3KCVQqYxM7nxuCK/SL3X97jBEIeKQ==}
|
||||||
|
|
||||||
|
'@types/default-gateway@7.2.2':
|
||||||
|
resolution: {integrity: sha512-35C93fYQlnLKLASkMPoxRvok4fENwB3By9clRLd2I/08n/XRl0pCdf7EB17K5oMMwZu8NBYA8i66jH5r/LYBKA==}
|
||||||
|
|
||||||
'@types/dns-packet@5.6.5':
|
'@types/dns-packet@5.6.5':
|
||||||
resolution: {integrity: sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==}
|
resolution: {integrity: sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==}
|
||||||
|
|
||||||
@ -1932,6 +1947,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
clone-regexp@3.0.0:
|
||||||
|
resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
clone@2.1.2:
|
clone@2.1.2:
|
||||||
resolution: {integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=}
|
resolution: {integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
@ -2000,6 +2019,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
convert-hrtime@5.0.0:
|
||||||
|
resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
@ -2518,6 +2541,10 @@ packages:
|
|||||||
function-bind@1.1.2:
|
function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
|
function-timeout@0.1.1:
|
||||||
|
resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
get-caller-file@2.0.5:
|
get-caller-file@2.0.5:
|
||||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||||
engines: {node: 6.* || 8.* || >= 10.*}
|
engines: {node: 6.* || 8.* || >= 10.*}
|
||||||
@ -2583,6 +2610,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
|
resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
|
got@13.0.0:
|
||||||
|
resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
graceful-fs@4.2.10:
|
graceful-fs@4.2.10:
|
||||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||||
|
|
||||||
@ -2803,6 +2834,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-4B4XA2HEIm/PY+OSpeMBXr8pGWBYbXuHgjMAqrwbLO3CPTCAd9ArEJzBUKGZtk9viY6+aSfadGnWyjY3ydYZkw==}
|
resolution: {integrity: sha512-4B4XA2HEIm/PY+OSpeMBXr8pGWBYbXuHgjMAqrwbLO3CPTCAd9ArEJzBUKGZtk9viY6+aSfadGnWyjY3ydYZkw==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
|
is-ip@5.0.1:
|
||||||
|
resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
is-nan@1.3.2:
|
is-nan@1.3.2:
|
||||||
resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
|
resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2827,6 +2862,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
|
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-regexp@3.1.0:
|
||||||
|
resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
is-stream@2.0.1:
|
is-stream@2.0.1:
|
||||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -3688,6 +3727,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==}
|
resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
|
public-ip@7.0.1:
|
||||||
|
resolution: {integrity: sha512-DdNcqcIbI0wEeCBcqX+bmZpUCvrDMJHXE553zgyG1MZ8S1a/iCCxmK9iTjjql+SpHSv4cZkmRv5/zGYW93AlCw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
pump@2.0.1:
|
pump@2.0.1:
|
||||||
resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==}
|
resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==}
|
||||||
|
|
||||||
@ -4073,6 +4116,10 @@ packages:
|
|||||||
stubborn-fs@1.2.5:
|
stubborn-fs@1.2.5:
|
||||||
resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==}
|
resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==}
|
||||||
|
|
||||||
|
super-regex@0.2.0:
|
||||||
|
resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
|
||||||
supports-color@5.5.0:
|
supports-color@5.5.0:
|
||||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -4118,6 +4165,10 @@ packages:
|
|||||||
through2@4.0.2:
|
through2@4.0.2:
|
||||||
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
||||||
|
|
||||||
|
time-span@5.1.0:
|
||||||
|
resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
tiny-worker@2.3.0:
|
tiny-worker@2.3.0:
|
||||||
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
|
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
|
||||||
|
|
||||||
@ -4527,7 +4578,7 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 2.1.0
|
'@push.rocks/smartrequest': 2.1.0
|
||||||
'@push.rocks/smartstring': 4.0.15
|
'@push.rocks/smartstring': 4.0.15
|
||||||
'@tsclass/tsclass': 9.1.0
|
'@tsclass/tsclass': 9.2.0
|
||||||
cloudflare: 4.2.0
|
cloudflare: 4.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
@ -5981,6 +6032,16 @@ snapshots:
|
|||||||
public-ip: 6.0.2
|
public-ip: 6.0.2
|
||||||
systeminformation: 5.25.11
|
systeminformation: 5.25.11
|
||||||
|
|
||||||
|
'@push.rocks/smartnetwork@4.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/smartping': 1.0.8
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
'@push.rocks/smartstring': 4.0.15
|
||||||
|
'@types/default-gateway': 7.2.2
|
||||||
|
isopen: 1.3.0
|
||||||
|
public-ip: 7.0.1
|
||||||
|
systeminformation: 5.25.11
|
||||||
|
|
||||||
'@push.rocks/smartnpm@2.0.4':
|
'@push.rocks/smartnpm@2.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/consolecolor': 2.0.2
|
'@push.rocks/consolecolor': 2.0.2
|
||||||
@ -6041,6 +6102,11 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
|
'@push.rocks/smartping@1.0.8':
|
||||||
|
dependencies:
|
||||||
|
'@types/ping': 0.4.4
|
||||||
|
ping: 0.4.4
|
||||||
|
|
||||||
'@push.rocks/smartpromise@4.2.3': {}
|
'@push.rocks/smartpromise@4.2.3': {}
|
||||||
|
|
||||||
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.7.3)':
|
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.7.3)':
|
||||||
@ -6848,7 +6914,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 4.40.0
|
type-fest: 4.40.0
|
||||||
|
|
||||||
'@tsclass/tsclass@9.1.0':
|
'@tsclass/tsclass@9.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 4.40.1
|
type-fest: 4.40.1
|
||||||
|
|
||||||
@ -6918,6 +6984,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/default-gateway@3.0.1': {}
|
'@types/default-gateway@3.0.1': {}
|
||||||
|
|
||||||
|
'@types/default-gateway@7.2.2': {}
|
||||||
|
|
||||||
'@types/dns-packet@5.6.5':
|
'@types/dns-packet@5.6.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.3
|
'@types/node': 22.15.3
|
||||||
@ -7538,6 +7606,10 @@ snapshots:
|
|||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
wrap-ansi: 7.0.0
|
wrap-ansi: 7.0.0
|
||||||
|
|
||||||
|
clone-regexp@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
is-regexp: 3.1.0
|
||||||
|
|
||||||
clone@2.1.2: {}
|
clone@2.1.2: {}
|
||||||
|
|
||||||
cloudflare@4.2.0:
|
cloudflare@4.2.0:
|
||||||
@ -7614,6 +7686,8 @@ snapshots:
|
|||||||
|
|
||||||
content-type@1.0.5: {}
|
content-type@1.0.5: {}
|
||||||
|
|
||||||
|
convert-hrtime@5.0.0: {}
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cookie-signature@1.0.6: {}
|
cookie-signature@1.0.6: {}
|
||||||
@ -8185,6 +8259,8 @@ snapshots:
|
|||||||
|
|
||||||
function-bind@1.1.2: {}
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
|
function-timeout@0.1.1: {}
|
||||||
|
|
||||||
get-caller-file@2.0.5: {}
|
get-caller-file@2.0.5: {}
|
||||||
|
|
||||||
get-intrinsic@1.2.2:
|
get-intrinsic@1.2.2:
|
||||||
@ -8295,6 +8371,20 @@ snapshots:
|
|||||||
p-cancelable: 3.0.0
|
p-cancelable: 3.0.0
|
||||||
responselike: 3.0.0
|
responselike: 3.0.0
|
||||||
|
|
||||||
|
got@13.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@sindresorhus/is': 5.6.0
|
||||||
|
'@szmarczak/http-timer': 5.0.1
|
||||||
|
cacheable-lookup: 7.0.0
|
||||||
|
cacheable-request: 10.2.14
|
||||||
|
decompress-response: 6.0.0
|
||||||
|
form-data-encoder: 2.1.4
|
||||||
|
get-stream: 6.0.1
|
||||||
|
http2-wrapper: 2.2.1
|
||||||
|
lowercase-keys: 3.0.0
|
||||||
|
p-cancelable: 3.0.0
|
||||||
|
responselike: 3.0.0
|
||||||
|
|
||||||
graceful-fs@4.2.10: {}
|
graceful-fs@4.2.10: {}
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
@ -8531,6 +8621,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ip-regex: 5.0.0
|
ip-regex: 5.0.0
|
||||||
|
|
||||||
|
is-ip@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
ip-regex: 5.0.0
|
||||||
|
super-regex: 0.2.0
|
||||||
|
|
||||||
is-nan@1.3.2:
|
is-nan@1.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@ -8551,6 +8646,8 @@ snapshots:
|
|||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
is-regexp@3.1.0: {}
|
||||||
|
|
||||||
is-stream@2.0.1: {}
|
is-stream@2.0.1: {}
|
||||||
|
|
||||||
is-stream@4.0.1: {}
|
is-stream@4.0.1: {}
|
||||||
@ -9590,6 +9687,12 @@ snapshots:
|
|||||||
got: 12.6.1
|
got: 12.6.1
|
||||||
is-ip: 4.0.0
|
is-ip: 4.0.0
|
||||||
|
|
||||||
|
public-ip@7.0.1:
|
||||||
|
dependencies:
|
||||||
|
dns-socket: 4.2.2
|
||||||
|
got: 13.0.0
|
||||||
|
is-ip: 5.0.1
|
||||||
|
|
||||||
pump@2.0.1:
|
pump@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
end-of-stream: 1.4.4
|
end-of-stream: 1.4.4
|
||||||
@ -10093,6 +10196,12 @@ snapshots:
|
|||||||
|
|
||||||
stubborn-fs@1.2.5: {}
|
stubborn-fs@1.2.5: {}
|
||||||
|
|
||||||
|
super-regex@0.2.0:
|
||||||
|
dependencies:
|
||||||
|
clone-regexp: 3.0.0
|
||||||
|
function-timeout: 0.1.1
|
||||||
|
time-span: 5.1.0
|
||||||
|
|
||||||
supports-color@5.5.0:
|
supports-color@5.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 3.0.0
|
has-flag: 3.0.0
|
||||||
@ -10158,6 +10267,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
|
time-span@5.1.0:
|
||||||
|
dependencies:
|
||||||
|
convert-hrtime: 5.0.0
|
||||||
|
|
||||||
tiny-worker@2.3.0:
|
tiny-worker@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esm: 3.2.25
|
esm: 3.2.25
|
||||||
|
@ -1,2 +1,13 @@
|
|||||||
- this repo is dependent on letsencrypt and its limits
|
- 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();
|
await smartAcmeInstance.start();
|
||||||
|
|
||||||
const myDomain = 'example.com';
|
const myDomain = 'example.com';
|
||||||
|
// Get certificate for domain (no wildcard)
|
||||||
const myCert = await smartAcmeInstance.getCertificateForDomain(myDomain);
|
const myCert = await smartAcmeInstance.getCertificateForDomain(myDomain);
|
||||||
console.log('Certificate:', myCert);
|
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();
|
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.
|
- **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.
|
- **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
|
### Handling Domain Matching
|
||||||
|
|
||||||
|
@ -1,27 +1,3 @@
|
|||||||
# Plan: Add wildcard domain support to SmartAcme
|
## Plan
|
||||||
|
|
||||||
## Goal
|
Move the
|
||||||
- Enable SmartAcme to accept wildcard domain inputs like `*.domain.com` or `*.sub.example.com` and correctly request and match wildcard certificates.
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
1. [x] Extend SmartacmeCertMatcher:
|
|
||||||
- [x] Update `getCertificateDomainNameByDomainName()` to handle wildcard prefixes:
|
|
||||||
- If input starts with `*.` strip the prefix and return the base domain.
|
|
||||||
- For example:
|
|
||||||
- `*.example.com` → `example.com`
|
|
||||||
- `*.sub.example.com` → `sub.example.com`
|
|
||||||
- `*.a.b.example.com` → `a.b.example.com`
|
|
||||||
- [x] Ensure existing logic for non-wildcards remains unchanged.
|
|
||||||
2. [x] Update `SmartAcme.getCertificateForDomain()`:
|
|
||||||
- [x] Detect wildcard inputs (`domainArg.startsWith('*.')`).
|
|
||||||
- [x] For wildcard cases, enforce DNS-01 challenge only (throw error if handlers don't support DNS-01).
|
|
||||||
- [x] Use the matcher result to request wildcard certificate identifiers (e.g., `value: '*.baseDomain'`).
|
|
||||||
3. [x] Update tests:
|
|
||||||
- [x] Add unit tests in `test/test.certmatcher.ts` for wildcard handling:
|
|
||||||
- `*.example.com` → `example.com`
|
|
||||||
- `*.sub.example.com` → `sub.example.com`
|
|
||||||
- `*.a.b.example.com` → `a.b.example.com`
|
|
||||||
- [x] Add integration stub in `test/test.smartacme.ts` for wildcard input in integration mode:
|
|
||||||
- Call `getCertificateForDomain('*.domain.com')` and expect returned cert `domainName` equals `*.domain.com`.
|
|
||||||
4. [x] Update documentation (README.md) if needed.
|
|
||||||
5. [x] Run CI (`pnpm build` & `pnpm test`) and fix any regressions.
|
|
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();
|
@ -1,7 +1,7 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import { Qenv } from '@push.rocks/qenv';
|
import { Qenv } from '@push.rocks/qenv';
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||||
import { SmartAcme, MongoCertManager, MemoryCertManager } from '../ts/index.js';
|
import { SmartAcme, certmanagers } from '../ts/index.js';
|
||||||
import { Dns01Handler } from '../ts/handlers/Dns01Handler.js';
|
import { Dns01Handler } from '../ts/handlers/Dns01Handler.js';
|
||||||
|
|
||||||
// Load environment variables for credentials (stored under .nogit/)
|
// Load environment variables for credentials (stored under .nogit/)
|
||||||
@ -21,7 +21,7 @@ tap.test('create SmartAcme instance with DNS-01 handler and start', async () =>
|
|||||||
smartAcmeInstance = new SmartAcme({
|
smartAcmeInstance = new SmartAcme({
|
||||||
accountEmail: 'domains@lossless.org',
|
accountEmail: 'domains@lossless.org',
|
||||||
// certManager: new MongoCertManager({ mongoDbName, mongoDbPass, mongoDbUrl }),
|
// certManager: new MongoCertManager({ mongoDbName, mongoDbPass, mongoDbUrl }),
|
||||||
certManager: new MemoryCertManager(),
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
retryOptions: {},
|
retryOptions: {},
|
||||||
challengeHandlers: [new Dns01Handler(cfAccount)],
|
challengeHandlers: [new Dns01Handler(cfAccount)],
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import { SmartAcme, MemoryCertManager } from '../ts/index.js';
|
import { SmartAcme, certmanagers } from '../ts/index.js';
|
||||||
import { Cert } from '../ts/index.js';
|
import { Cert } from '../ts/index.js';
|
||||||
import type { IChallengeHandler } from '../ts/handlers/IChallengeHandler.js';
|
import type { IChallengeHandler } from '../ts/handlers/IChallengeHandler.js';
|
||||||
|
|
||||||
@ -8,12 +8,13 @@ class DummyHandler implements IChallengeHandler<any> {
|
|||||||
getSupportedTypes(): string[] { return ['dns-01']; }
|
getSupportedTypes(): string[] { return ['dns-01']; }
|
||||||
async prepare(_: any): Promise<void> { /* no-op */ }
|
async prepare(_: any): Promise<void> { /* no-op */ }
|
||||||
async cleanup(_: any): Promise<void> { /* no-op */ }
|
async cleanup(_: any): Promise<void> { /* no-op */ }
|
||||||
|
async checkWetherDomainIsSupported(_: string): Promise<boolean> { return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
tap.test('constructor throws without challengeHandlers', async () => {
|
tap.test('constructor throws without challengeHandlers', async () => {
|
||||||
expect(() => new SmartAcme({
|
expect(() => new SmartAcme({
|
||||||
accountEmail: 'test@example.com',
|
accountEmail: 'test@example.com',
|
||||||
certManager: new MemoryCertManager(),
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
retryOptions: {},
|
retryOptions: {},
|
||||||
} as any)).toThrow();
|
} as any)).toThrow();
|
||||||
@ -22,7 +23,7 @@ tap.test('constructor throws without challengeHandlers', async () => {
|
|||||||
tap.test('constructor accepts valid challengeHandlers', async () => {
|
tap.test('constructor accepts valid challengeHandlers', async () => {
|
||||||
const sa = new SmartAcme({
|
const sa = new SmartAcme({
|
||||||
accountEmail: 'test@example.com',
|
accountEmail: 'test@example.com',
|
||||||
certManager: new MemoryCertManager(),
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
retryOptions: {},
|
retryOptions: {},
|
||||||
challengeHandlers: [new DummyHandler()],
|
challengeHandlers: [new DummyHandler()],
|
||||||
@ -41,7 +42,7 @@ tap.test('get wildcard certificate stub in integration mode', async () => {
|
|||||||
};
|
};
|
||||||
const sa = new SmartAcme({
|
const sa = new SmartAcme({
|
||||||
accountEmail: 'domains@lossless.org',
|
accountEmail: 'domains@lossless.org',
|
||||||
certManager: new MemoryCertManager(),
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
retryOptions: {},
|
retryOptions: {},
|
||||||
challengeHandlers: [new DummyHandler()],
|
challengeHandlers: [new DummyHandler()],
|
||||||
|
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 = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartacme',
|
name: '@push.rocks/smartacme',
|
||||||
version: '7.2.4',
|
version: '8.0.0',
|
||||||
description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
|
description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from '../smartacme.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import type { ICertManager } from '../interfaces/certmanager.js';
|
import type { ICertManager } from '../interfaces/certmanager.js';
|
||||||
import { SmartacmeCert } from '../smartacme.classes.cert.js';
|
import { SmartacmeCert } from '../smartacme.classes.cert.js';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from '../smartacme.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import type { ICertManager } from '../interfaces/certmanager.js';
|
import type { ICertManager } from '../interfaces/certmanager.js';
|
||||||
import { SmartacmeCert } from '../smartacme.classes.cert.js';
|
import { SmartacmeCert } from '../smartacme.classes.cert.js';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from '../smartacme.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import type { IChallengeHandler } from './IChallengeHandler.js';
|
import type { IChallengeHandler } from './IChallengeHandler.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { promises as fs } from 'fs';
|
import * as plugins from '../plugins.js';
|
||||||
import * as path from 'path';
|
|
||||||
import type { IChallengeHandler } from './IChallengeHandler.js';
|
import type { IChallengeHandler } from './IChallengeHandler.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +18,9 @@ export class Http01Webroot implements IChallengeHandler<{
|
|||||||
keyAuthorization: string;
|
keyAuthorization: string;
|
||||||
webPath: string;
|
webPath: string;
|
||||||
}> {
|
}> {
|
||||||
|
private smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
|
||||||
|
private smartdnsClient = new plugins.smartdnsClient.Smartdns({});
|
||||||
|
|
||||||
private webroot: string;
|
private webroot: string;
|
||||||
|
|
||||||
constructor(options: Http01WebrootOptions) {
|
constructor(options: Http01WebrootOptions) {
|
||||||
@ -31,10 +33,10 @@ export class Http01Webroot implements IChallengeHandler<{
|
|||||||
|
|
||||||
public async prepare(ch: { token: string; keyAuthorization: string; webPath: string }): Promise<void> {
|
public async prepare(ch: { token: string; keyAuthorization: string; webPath: string }): Promise<void> {
|
||||||
const relWebPath = ch.webPath.replace(/^\/+/, '');
|
const relWebPath = ch.webPath.replace(/^\/+/, '');
|
||||||
const filePath = path.join(this.webroot, relWebPath);
|
const filePath = plugins.path.join(this.webroot, relWebPath);
|
||||||
const dir = path.dirname(filePath);
|
const dir = plugins.path.dirname(filePath);
|
||||||
await fs.mkdir(dir, { recursive: true });
|
await plugins.fs.promises.mkdir(dir, { recursive: true });
|
||||||
await fs.writeFile(filePath, ch.keyAuthorization, 'utf8');
|
await plugins.fs.promises.writeFile(filePath, ch.keyAuthorization, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async verify(ch: { webPath: string; keyAuthorization: string }): Promise<void> {
|
public async verify(ch: { webPath: string; keyAuthorization: string }): Promise<void> {
|
||||||
@ -44,11 +46,24 @@ export class Http01Webroot implements IChallengeHandler<{
|
|||||||
|
|
||||||
public async cleanup(ch: { token: string; webPath: string }): Promise<void> {
|
public async cleanup(ch: { token: string; webPath: string }): Promise<void> {
|
||||||
const relWebPath = ch.webPath.replace(/^\/+/, '');
|
const relWebPath = ch.webPath.replace(/^\/+/, '');
|
||||||
const filePath = path.join(this.webroot, relWebPath);
|
const filePath = plugins.path.join(this.webroot, relWebPath);
|
||||||
try {
|
try {
|
||||||
await fs.unlink(filePath);
|
await plugins.fs.promises.unlink(filePath);
|
||||||
} catch {
|
} catch {
|
||||||
// ignore missing file
|
// ignore missing file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async checkWetherDomainIsSupported(domainArg: string): Promise<boolean> {
|
||||||
|
const publicIps = await this.smartnetworkInstance.getPublicIps();
|
||||||
|
const aRecords = await this.smartdnsClient.getRecordsA(domainArg);
|
||||||
|
const aaaaRecords = await this.smartdnsClient.getRecordsAAAA(domainArg);
|
||||||
|
if (aRecords.length && aRecords.some(record => record.value !== publicIps.v4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (aaaaRecords.length && aaaaRecords.some(record => record.value !== publicIps.v6)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
import type { IChallengeHandler } from './IChallengeHandler.js';
|
import type { IChallengeHandler } from './IChallengeHandler.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13,6 +14,8 @@ export interface Http01MemoryHandlerChallenge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Http01MemoryHandler implements IChallengeHandler<Http01MemoryHandlerChallenge> {
|
export class Http01MemoryHandler implements IChallengeHandler<Http01MemoryHandlerChallenge> {
|
||||||
|
private smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
|
||||||
|
private smartdnsClient = new plugins.smartdnsClient.Smartdns({});
|
||||||
private store: Map<string, string> = new Map();
|
private store: Map<string, string> = new Map();
|
||||||
|
|
||||||
public getSupportedTypes(): string[] {
|
public getSupportedTypes(): string[] {
|
||||||
@ -64,4 +67,17 @@ export class Http01MemoryHandler implements IChallengeHandler<Http01MemoryHandle
|
|||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
return res.end();
|
return res.end();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public async checkWetherDomainIsSupported(domainArg: string): Promise<boolean> {
|
||||||
|
const publicIps = await this.smartnetworkInstance.getPublicIps();
|
||||||
|
const aRecords = await this.smartdnsClient.getRecordsA(domainArg);
|
||||||
|
const aaaaRecords = await this.smartdnsClient.getRecordsAAAA(domainArg);
|
||||||
|
if (aRecords.length && aRecords.some((record) => record.value !== publicIps.v4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (aaaaRecords.length && aaaaRecords.some((record) => record.value !== publicIps.v6)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,4 +19,6 @@ export interface IChallengeHandler<T> {
|
|||||||
* Clean up resources: remove DNS record, stop server.
|
* Clean up resources: remove DNS record, stop server.
|
||||||
*/
|
*/
|
||||||
cleanup(ch: T): Promise<void>;
|
cleanup(ch: T): Promise<void>;
|
||||||
|
|
||||||
|
checkWetherDomainIsSupported(domainArg: string): Promise<boolean>;
|
||||||
}
|
}
|
@ -1,4 +1,11 @@
|
|||||||
export * from './smartacme.classes.smartacme.js';
|
export * from './smartacme.classes.smartacme.js';
|
||||||
export { SmartacmeCert as Cert } from './smartacme.classes.cert.js';
|
export { SmartacmeCert as Cert } from './smartacme.classes.cert.js';
|
||||||
export type { ICertManager } from './interfaces/certmanager.js';
|
export type { ICertManager } from './interfaces/certmanager.js';
|
||||||
export { MemoryCertManager, MongoCertManager } from './certmanagers/index.js';
|
|
||||||
|
// certmanagers
|
||||||
|
import * as certmanagers from './certmanagers/index.js';
|
||||||
|
export { certmanagers };
|
||||||
|
|
||||||
|
// handlers
|
||||||
|
import * as handlers from './handlers/index.js';
|
||||||
|
export { handlers };
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
// node native
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export { fs, path };
|
||||||
|
|
||||||
// @apiclient.xyz scope
|
// @apiclient.xyz scope
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||||
|
|
||||||
@ -13,7 +19,9 @@ import * as lik from '@push.rocks/lik';
|
|||||||
import * as smartdata from '@push.rocks/smartdata';
|
import * as smartdata from '@push.rocks/smartdata';
|
||||||
import * as smartdelay from '@push.rocks/smartdelay';
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
import * as smartdnsClient from '@push.rocks/smartdns/client';
|
import * as smartdnsClient from '@push.rocks/smartdns/client';
|
||||||
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
import * as smartlog from '@push.rocks/smartlog';
|
import * as smartlog from '@push.rocks/smartlog';
|
||||||
|
import * as smartnetwork from '@push.rocks/smartnetwork';
|
||||||
import * as smartpromise from '@push.rocks/smartpromise';
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
import * as smartrequest from '@push.rocks/smartrequest';
|
import * as smartrequest from '@push.rocks/smartrequest';
|
||||||
import * as smartunique from '@push.rocks/smartunique';
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
@ -25,7 +33,9 @@ export {
|
|||||||
smartdata,
|
smartdata,
|
||||||
smartdelay,
|
smartdelay,
|
||||||
smartdnsClient,
|
smartdnsClient,
|
||||||
|
smartfile,
|
||||||
smartlog,
|
smartlog,
|
||||||
|
smartnetwork,
|
||||||
smartpromise,
|
smartpromise,
|
||||||
smartrequest,
|
smartrequest,
|
||||||
smartunique,
|
smartunique,
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './smartacme.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plain certificate record.
|
* Plain certificate record.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './smartacme.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as interfaces from './interfaces/index.js';
|
import * as interfaces from './interfaces/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './smartacme.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import type { ICertManager } from './interfaces/certmanager.js';
|
import type { ICertManager } from './interfaces/certmanager.js';
|
||||||
import { SmartacmeCertMatcher } from './smartacme.classes.certmatcher.js';
|
import { SmartacmeCertMatcher } from './smartacme.classes.certmatcher.js';
|
||||||
import { commitinfo } from './00_commitinfo_data.js';
|
import { commitinfo } from './00_commitinfo_data.js';
|
||||||
@ -8,8 +8,8 @@ import { SmartacmeCert } from './smartacme.classes.cert.js';
|
|||||||
* the options for the class @see SmartAcme
|
* the options for the class @see SmartAcme
|
||||||
*/
|
*/
|
||||||
export interface ISmartAcmeOptions {
|
export interface ISmartAcmeOptions {
|
||||||
accountPrivateKey?: string;
|
|
||||||
accountEmail: string;
|
accountEmail: string;
|
||||||
|
accountPrivateKey?: string;
|
||||||
/**
|
/**
|
||||||
* Certificate storage manager (e.g., Mongo or in-memory).
|
* Certificate storage manager (e.g., Mongo or in-memory).
|
||||||
*/
|
*/
|
||||||
@ -64,13 +64,15 @@ export class SmartAcme {
|
|||||||
|
|
||||||
// certificate manager for persistence (implements ICertManager)
|
// certificate manager for persistence (implements ICertManager)
|
||||||
public certmanager: ICertManager;
|
public certmanager: ICertManager;
|
||||||
|
// configured pluggable ACME challenge handlers
|
||||||
|
public challengeHandlers: plugins.handlers.IChallengeHandler<any>[];
|
||||||
|
|
||||||
|
|
||||||
private certmatcher: SmartacmeCertMatcher;
|
private certmatcher: SmartacmeCertMatcher;
|
||||||
// retry/backoff configuration (resolved with defaults)
|
// retry/backoff configuration (resolved with defaults)
|
||||||
private retryOptions: { retries: number; factor: number; minTimeoutMs: number; maxTimeoutMs: number };
|
private retryOptions: { retries: number; factor: number; minTimeoutMs: number; maxTimeoutMs: number };
|
||||||
// track pending DNS challenges for graceful shutdown
|
// track pending DNS challenges for graceful shutdown
|
||||||
private pendingChallenges: plugins.tsclass.network.IDnsChallenge[] = [];
|
private pendingChallenges: plugins.tsclass.network.IDnsChallenge[] = [];
|
||||||
// configured pluggable ACME challenge handlers
|
|
||||||
private challengeHandlers: plugins.handlers.IChallengeHandler<any>[];
|
|
||||||
// priority order of challenge types
|
// priority order of challenge types
|
||||||
private challengePriority: string[];
|
private challengePriority: string[];
|
||||||
// Map for coordinating concurrent certificate requests
|
// Map for coordinating concurrent certificate requests
|
||||||
@ -219,8 +221,12 @@ export class SmartAcme {
|
|||||||
* * retrieve it from the databse and return it
|
* * retrieve it from the databse and return it
|
||||||
*
|
*
|
||||||
* @param domainArg
|
* @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').
|
// Determine if this is a wildcard request (e.g., '*.example.com').
|
||||||
const isWildcardRequest = domainArg.startsWith('*.');
|
const isWildcardRequest = domainArg.startsWith('*.');
|
||||||
// Determine the base domain for certificate retrieval/issuance.
|
// Determine the base domain for certificate retrieval/issuance.
|
||||||
@ -257,12 +263,32 @@ export class SmartAcme {
|
|||||||
// lets make sure others get the same interest
|
// lets make sure others get the same interest
|
||||||
const currentDomainInterst = await this.interestMap.addInterest(certDomainName);
|
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 */
|
/* Place new order with retry */
|
||||||
const order = await this.retry(() => this.client.createOrder({
|
const order = await this.retry(() => this.client.createOrder({
|
||||||
identifiers: [
|
identifiers,
|
||||||
{ type: 'dns', value: certDomainName },
|
|
||||||
{ type: 'dns', value: `*.${certDomainName}` },
|
|
||||||
],
|
|
||||||
}), 'createOrder');
|
}), 'createOrder');
|
||||||
|
|
||||||
/* Get authorizations and select challenges */
|
/* Get authorizations and select challenges */
|
||||||
@ -357,9 +383,25 @@ export class SmartAcme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Finalize order */
|
/* 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({
|
const [key, csr] = await plugins.acme.forge.createCsr({
|
||||||
commonName: `*.${certDomainName}`,
|
commonName,
|
||||||
altNames: [certDomainName],
|
altNames: csrDomains,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.retry(() => this.client.finalizeOrder(order, csr), 'finalizeOrder');
|
await this.retry(() => this.client.finalizeOrder(order, csr), 'finalizeOrder');
|
||||||
|
Reference in New Issue
Block a user