Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
01f7018540 | |||
3cee6c534a | |||
47d1609a49 | |||
d69eb73afc | |||
0a1d617ce3 | |||
f78b50757c | |||
0e6bbc5be6 | |||
10511b4293 | |||
d456876de7 | |||
fe495a5f03 | |||
88ba970494 | |||
1e7e1739b8 | |||
0c6da9ff74 | |||
1698abef16 | |||
a0f6a14b63 | |||
876d876661 | |||
ae212c53d5 | |||
b9866c2ced | |||
c863c7295d | |||
b8bb4af184 | |||
6fedf0505e | |||
f814038a6a | |||
9dc8c1d8a3 | |||
758c6c6b5d | |||
6363ec4be6 | |||
6a53346d14 | |||
fc420eb615 | |||
9f66a0487f | |||
40cae220d0 |
104
changelog.md
104
changelog.md
@ -1,5 +1,109 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
Refactor wildcard certificate test to properly stub SmartAcme.start and getCertificateForDomain for robust integration.
|
||||||
|
|
||||||
|
- Temporarily override SmartAcme.start and getCertificateForDomain to simulate wildcard certificate behavior.
|
||||||
|
- Restore original prototype methods post-test to prevent side effects.
|
||||||
|
- Improve test clarity for wildcard certificate integration.
|
||||||
|
|
||||||
|
## 2025-05-01 - 7.2.3 - fix(docs)
|
||||||
|
Improve certificate manager documentation with detailed examples and custom implementation guide
|
||||||
|
|
||||||
|
- Added usage examples for MemoryCertManager and MongoCertManager
|
||||||
|
- Provided a custom ICertManager implementation guide
|
||||||
|
- Enhanced overall documentation clarity for certificate storage configuration
|
||||||
|
|
||||||
|
## 2025-05-01 - 7.2.2 - fix(readme)
|
||||||
|
Update readme documentation: switch installation instructions to pnpm and clarify usage with MongoCertManager and updated SmartAcme options
|
||||||
|
|
||||||
|
- Replaced npm/yarn commands with pnpm commands for installation and testing.
|
||||||
|
- Added guidance to ensure the project is set up for TypeScript and ECMAScript Modules.
|
||||||
|
- Updated usage examples to include initialization of MongoCertManager instead of legacy mongoDescriptor.
|
||||||
|
- Revised challenge handlers examples to reference the current API signatures.
|
||||||
|
|
||||||
|
## 2025-05-01 - 7.2.1 - fix(smartacme)
|
||||||
|
Centralize interest map coordination and remove redundant interestMap from cert managers
|
||||||
|
|
||||||
|
- Removed interestMap property and related logic from MemoryCertManager and MongoCertManager
|
||||||
|
- Refactored SmartAcme to instantiate its own interestMap for coordinating certificate requests
|
||||||
|
- Updated getCertificateForDomain to use the new interestMap for checking and adding certificate interests
|
||||||
|
|
||||||
|
## 2025-05-01 - 7.2.0 - feat(core)
|
||||||
|
Refactor SmartAcme core to centralize interest coordination and update dependencies
|
||||||
|
|
||||||
|
- Moved interest coordination mechanism out of ICertManager implementations and into SmartAcme core
|
||||||
|
- Updated certificate managers (MemoryCertManager and MongoCertManager) to remove redundant interestMap handling
|
||||||
|
- Upgraded @push.rocks/tapbundle from 6.0.1 to 6.0.3 in package.json
|
||||||
|
- Revised readme.plan.md to reflect the new interest coordination approach
|
||||||
|
|
||||||
|
## 2025-04-30 - 7.1.0 - feat(certmanagers/integration)
|
||||||
|
Add optional wipe methods to certificate managers and update integration tests, plus bump tapbundle dependency
|
||||||
|
|
||||||
|
- Introduce wipe() in ICertManager to support integration testing by clearing stored certificates
|
||||||
|
- Implement wipe() in MemoryCertManager and MongoCertManager for resetting internal state
|
||||||
|
- Refactor SmartAcme constructor to consider wiping certificates in integration mode (commented out for now)
|
||||||
|
- Update integration test assertions and add console logging for domain certificate retrieval
|
||||||
|
- Upgrade @push.rocks/tapbundle from ^6.0.0 to ^6.0.1
|
||||||
|
|
||||||
|
## 2025-04-30 - 7.0.0 - BREAKING CHANGE(SmartAcme (Cert Management))
|
||||||
|
Refactor certificate management and challenge handling API to use a unified certManager interface, remove legacy storage, and update challenge workflows.
|
||||||
|
|
||||||
|
- Introduce ICertManager interface with MemoryCertManager and MongoCertManager implementations.
|
||||||
|
- Remove the legacy SmartacmeCertManager and update SmartAcme to require a certManager option instead of mongoDescriptor.
|
||||||
|
- Adjust certificate renewal logic to delete and store certificates through the new certManager API.
|
||||||
|
- Refine DNS-01 challenge handling by removing in-handler DNS propagation waiting and relying on external checks.
|
||||||
|
- Increase retry settings for robustness during challenge verification and certificate issuance.
|
||||||
|
- Update integration and unit tests to use the new certManager configuration.
|
||||||
|
|
||||||
|
## 2025-04-30 - 6.2.0 - feat(handlers)
|
||||||
|
Add in-memory HTTP-01 challenge handler and rename file-based handler to Http01Webroot
|
||||||
|
|
||||||
|
- Renamed Http01Handler to Http01Webroot in both implementation and documentation
|
||||||
|
- Introduced Http01MemoryHandler for diskless HTTP-01 challenges
|
||||||
|
- Updated tests and README examples to reflect handler name changes and new feature
|
||||||
|
|
||||||
|
## 2025-04-30 - 6.1.3 - fix(Dns01Handler)
|
||||||
|
Update dependency versions and refine Dns01Handler implementation
|
||||||
|
|
||||||
|
- Bump '@apiclient.xyz/cloudflare' to ^6.4.1 and '@tsclass/tsclass' to ^9.1.0 in package.json
|
||||||
|
- Remove duplicate Cloudflare import in smartacme.plugins.ts
|
||||||
|
- Refactor Dns01Handler to use IConvenientDnsProvider and add checkWetherDomainIsSupported method
|
||||||
|
- Align devDependencies versions for improved consistency
|
||||||
|
|
||||||
## 2025-04-27 - 6.1.2 - fix(repo)
|
## 2025-04-27 - 6.1.2 - fix(repo)
|
||||||
Update repository metadata by replacing the LICENSE file with a license.md file for improved consistency.
|
Update repository metadata by replacing the LICENSE file with a license.md file for improved consistency.
|
||||||
|
|
||||||
|
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartacme",
|
"name": "@push.rocks/smartacme",
|
||||||
"version": "6.1.2",
|
"version": "7.3.3",
|
||||||
"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",
|
||||||
@ -40,27 +40,29 @@
|
|||||||
"homepage": "https://code.foss.global/push.rocks/smartacme#readme",
|
"homepage": "https://code.foss.global/push.rocks/smartacme#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedserver": "^3.0.74",
|
"@api.global/typedserver": "^3.0.74",
|
||||||
|
"@apiclient.xyz/cloudflare": "^6.4.1",
|
||||||
"@push.rocks/lik": "^6.2.2",
|
"@push.rocks/lik": "^6.2.2",
|
||||||
"@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.0.0",
|
"@tsclass/tsclass": "^9.2.0",
|
||||||
"acme-client": "^5.4.0"
|
"acme-client": "^5.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apiclient.xyz/cloudflare": "^6.3.2",
|
|
||||||
"@git.zone/tsbuild": "^2.3.2",
|
"@git.zone/tsbuild": "^2.3.2",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^1.3.3",
|
||||||
"@git.zone/tstest": "^1.0.96",
|
"@git.zone/tstest": "^1.0.96",
|
||||||
"@push.rocks/qenv": "^6.1.0",
|
"@push.rocks/qenv": "^6.1.0",
|
||||||
"@push.rocks/tapbundle": "^5.6.3",
|
"@push.rocks/tapbundle": "^6.0.3",
|
||||||
"@types/node": "^22.15.2"
|
"@types/node": "^22.15.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
267
pnpm-lock.yaml
generated
267
pnpm-lock.yaml
generated
@ -11,6 +11,9 @@ importers:
|
|||||||
'@api.global/typedserver':
|
'@api.global/typedserver':
|
||||||
specifier: ^3.0.74
|
specifier: ^3.0.74
|
||||||
version: 3.0.74
|
version: 3.0.74
|
||||||
|
'@apiclient.xyz/cloudflare':
|
||||||
|
specifier: ^6.4.1
|
||||||
|
version: 6.4.1
|
||||||
'@push.rocks/lik':
|
'@push.rocks/lik':
|
||||||
specifier: ^6.2.2
|
specifier: ^6.2.2
|
||||||
version: 6.2.2
|
version: 6.2.2
|
||||||
@ -23,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
|
||||||
@ -42,15 +51,12 @@ importers:
|
|||||||
specifier: ^3.0.9
|
specifier: ^3.0.9
|
||||||
version: 3.0.9
|
version: 3.0.9
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.0.0
|
specifier: ^9.2.0
|
||||||
version: 9.0.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
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@apiclient.xyz/cloudflare':
|
|
||||||
specifier: ^6.3.2
|
|
||||||
version: 6.3.2
|
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
specifier: ^2.3.2
|
specifier: ^2.3.2
|
||||||
version: 2.3.2
|
version: 2.3.2
|
||||||
@ -64,11 +70,11 @@ importers:
|
|||||||
specifier: ^6.1.0
|
specifier: ^6.1.0
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
'@push.rocks/tapbundle':
|
'@push.rocks/tapbundle':
|
||||||
specifier: ^5.6.3
|
specifier: ^6.0.3
|
||||||
version: 5.6.3(@aws-sdk/credential-providers@3.797.0)(socks@2.8.4)
|
version: 6.0.3(@aws-sdk/credential-providers@3.797.0)(socks@2.8.4)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.15.2
|
specifier: ^22.15.3
|
||||||
version: 22.15.2
|
version: 22.15.3
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@ -87,8 +93,8 @@ packages:
|
|||||||
'@api.global/typedsocket@3.0.1':
|
'@api.global/typedsocket@3.0.1':
|
||||||
resolution: {integrity: sha512-xojiAVNXtHoxkpBo8U2HHJG8FrVXXuLvDNndSHXwx4C9VslUwDn5zSCI+PdBl8iAg+ZuBmKjqkpZZ9sL6DC5yQ==}
|
resolution: {integrity: sha512-xojiAVNXtHoxkpBo8U2HHJG8FrVXXuLvDNndSHXwx4C9VslUwDn5zSCI+PdBl8iAg+ZuBmKjqkpZZ9sL6DC5yQ==}
|
||||||
|
|
||||||
'@apiclient.xyz/cloudflare@6.3.2':
|
'@apiclient.xyz/cloudflare@6.4.1':
|
||||||
resolution: {integrity: sha512-u5ud25tR1epNVgAPtL2t1qZ7FOGsLhID4zAzwcIQQTqmBb43US0fkI/I+JjIW0uyHi12AI4gWez2ke2nAR4+pw==}
|
resolution: {integrity: sha512-RYFphnbunjK+Imq/3ynIQpAvIGBJ38kqSZ2nrpTm26zsBIxW7S6xEe3zhXfVMtUIgC99OL3Xr/SGXl3CNBwCug==}
|
||||||
|
|
||||||
'@aws-crypto/crc32@5.2.0':
|
'@aws-crypto/crc32@5.2.0':
|
||||||
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
|
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
|
||||||
@ -821,6 +827,9 @@ packages:
|
|||||||
'@push.rocks/smartexpect@1.6.1':
|
'@push.rocks/smartexpect@1.6.1':
|
||||||
resolution: {integrity: sha512-NFQXEPkGiMNxyvFwKyzDWe3ADYdf8KNvIcV7TGNZZT3uPQtk65te4Q+a1cWErjP/61yE9XdYiQA66QQp+TV9IQ==}
|
resolution: {integrity: sha512-NFQXEPkGiMNxyvFwKyzDWe3ADYdf8KNvIcV7TGNZZT3uPQtk65te4Q+a1cWErjP/61yE9XdYiQA66QQp+TV9IQ==}
|
||||||
|
|
||||||
|
'@push.rocks/smartexpect@2.4.2':
|
||||||
|
resolution: {integrity: sha512-L+aS1n5rWhf/yOh5R3zPgwycYtDr5FfrDWgasy6ShhN6Zbn/z/AOPbWcF/OpeTmx0XabWB2h5d4xBcCKLl47cQ==}
|
||||||
|
|
||||||
'@push.rocks/smartfeed@1.0.11':
|
'@push.rocks/smartfeed@1.0.11':
|
||||||
resolution: {integrity: sha512-02uhXxQamgfBo3T12FsAdfyElnpoWuDUb08B2AE60DbIaukVx/7Mi17xwobApY1flNSr5StZDt8N8vxPhBhIXw==}
|
resolution: {integrity: sha512-02uhXxQamgfBo3T12FsAdfyElnpoWuDUb08B2AE60DbIaukVx/7Mi17xwobApY1flNSr5StZDt8N8vxPhBhIXw==}
|
||||||
|
|
||||||
@ -875,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==}
|
||||||
|
|
||||||
@ -893,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==}
|
||||||
|
|
||||||
@ -956,6 +971,9 @@ packages:
|
|||||||
'@push.rocks/tapbundle@5.6.3':
|
'@push.rocks/tapbundle@5.6.3':
|
||||||
resolution: {integrity: sha512-hFzsf59rg1K70i45llj7PCyyCZp7JW19XRR+Q1gge1T0pBN8Wi53aYqP/2qtxdMiNVe2s3ESp6VJZv3sLOMYPQ==}
|
resolution: {integrity: sha512-hFzsf59rg1K70i45llj7PCyyCZp7JW19XRR+Q1gge1T0pBN8Wi53aYqP/2qtxdMiNVe2s3ESp6VJZv3sLOMYPQ==}
|
||||||
|
|
||||||
|
'@push.rocks/tapbundle@6.0.3':
|
||||||
|
resolution: {integrity: sha512-SuP14V6TPdtd1y1CYTvwTKJdpHa7EzY55NfaaEMxW4oRKvHgJiOiPEiR/IrtL9tSiDMSfrx12waTMgZheYaBug==}
|
||||||
|
|
||||||
'@push.rocks/taskbuffer@3.1.7':
|
'@push.rocks/taskbuffer@3.1.7':
|
||||||
resolution: {integrity: sha512-QktGVJPucqQmW/QNGnscf4FAigT1H7JWKFGFdRuDEaOHKFh9qN+PXG3QY7DtZ4jfXdGLxPN4yAufDuPSAJYFnw==}
|
resolution: {integrity: sha512-QktGVJPucqQmW/QNGnscf4FAigT1H7JWKFGFdRuDEaOHKFh9qN+PXG3QY7DtZ4jfXdGLxPN4yAufDuPSAJYFnw==}
|
||||||
|
|
||||||
@ -1336,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.0.0':
|
'@tsclass/tsclass@9.2.0':
|
||||||
resolution: {integrity: sha512-QuV2WKzi3p1ONq0UR+hNulG62D6vRPJxOXunWvN9zpWx6Uj70DKntMu8nqEIWUPgL3UKIPe7GN8l6mPCdxdcEg==}
|
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==}
|
||||||
@ -1396,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==}
|
||||||
|
|
||||||
@ -1499,8 +1520,8 @@ packages:
|
|||||||
'@types/node@18.19.87':
|
'@types/node@18.19.87':
|
||||||
resolution: {integrity: sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==}
|
resolution: {integrity: sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==}
|
||||||
|
|
||||||
'@types/node@22.15.2':
|
'@types/node@22.15.3':
|
||||||
resolution: {integrity: sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==}
|
resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==}
|
||||||
|
|
||||||
'@types/parse5@6.0.3':
|
'@types/parse5@6.0.3':
|
||||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||||
@ -1926,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'}
|
||||||
@ -1994,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==}
|
||||||
|
|
||||||
@ -2512,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.*}
|
||||||
@ -2577,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==}
|
||||||
|
|
||||||
@ -2797,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'}
|
||||||
@ -2821,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'}
|
||||||
@ -3682,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==}
|
||||||
|
|
||||||
@ -4067,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'}
|
||||||
@ -4112,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==}
|
||||||
|
|
||||||
@ -4189,6 +4246,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==}
|
resolution: {integrity: sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
type-fest@4.40.1:
|
||||||
|
resolution: {integrity: sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
type-is@1.6.18:
|
type-is@1.6.18:
|
||||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@ -4510,14 +4571,14 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@apiclient.xyz/cloudflare@6.3.2':
|
'@apiclient.xyz/cloudflare@6.4.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.0.7
|
'@push.rocks/smartlog': 3.0.7
|
||||||
'@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': 5.0.0
|
'@tsclass/tsclass': 9.2.0
|
||||||
cloudflare: 4.2.0
|
cloudflare: 4.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
@ -5399,7 +5460,7 @@ snapshots:
|
|||||||
'@jest/schemas': 29.6.3
|
'@jest/schemas': 29.6.3
|
||||||
'@types/istanbul-lib-coverage': 2.0.6
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
'@types/istanbul-reports': 3.0.4
|
'@types/istanbul-reports': 3.0.4
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
'@types/yargs': 17.0.33
|
'@types/yargs': 17.0.33
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
|
|
||||||
@ -5820,6 +5881,12 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
|
|
||||||
|
'@push.rocks/smartexpect@2.4.2':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
fast-deep-equal: 3.1.3
|
||||||
|
|
||||||
'@push.rocks/smartfeed@1.0.11':
|
'@push.rocks/smartfeed@1.0.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tsclass/tsclass': 3.0.48
|
'@tsclass/tsclass': 3.0.48
|
||||||
@ -5965,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
|
||||||
@ -6025,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)':
|
||||||
@ -6224,6 +6306,38 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
|
'@push.rocks/tapbundle@6.0.3(@aws-sdk/credential-providers@3.797.0)(socks@2.8.4)':
|
||||||
|
dependencies:
|
||||||
|
'@open-wc/testing': 4.0.0
|
||||||
|
'@push.rocks/consolecolor': 2.0.2
|
||||||
|
'@push.rocks/qenv': 6.1.0
|
||||||
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
'@push.rocks/smartenv': 5.0.12
|
||||||
|
'@push.rocks/smartexpect': 2.4.2
|
||||||
|
'@push.rocks/smartfile': 11.2.0
|
||||||
|
'@push.rocks/smartjson': 5.0.20
|
||||||
|
'@push.rocks/smartmongo': 2.0.12(@aws-sdk/credential-providers@3.797.0)(socks@2.8.4)
|
||||||
|
'@push.rocks/smartpath': 5.0.18
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
'@push.rocks/smartrequest': 2.1.0
|
||||||
|
'@push.rocks/smarts3': 2.2.5
|
||||||
|
'@push.rocks/smartshell': 3.2.3
|
||||||
|
'@push.rocks/smarttime': 4.1.1
|
||||||
|
expect: 29.7.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@aws-sdk/credential-providers'
|
||||||
|
- '@mongodb-js/zstd'
|
||||||
|
- aws-crt
|
||||||
|
- bufferutil
|
||||||
|
- gcp-metadata
|
||||||
|
- kerberos
|
||||||
|
- mongodb-client-encryption
|
||||||
|
- snappy
|
||||||
|
- socks
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
'@push.rocks/taskbuffer@3.1.7':
|
'@push.rocks/taskbuffer@3.1.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
@ -6800,24 +6914,24 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 4.40.0
|
type-fest: 4.40.0
|
||||||
|
|
||||||
'@tsclass/tsclass@9.0.0':
|
'@tsclass/tsclass@9.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 4.40.0
|
type-fest: 4.40.1
|
||||||
|
|
||||||
'@types/accepts@1.3.7':
|
'@types/accepts@1.3.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/babel__code-frame@7.0.6': {}
|
'@types/babel__code-frame@7.0.6': {}
|
||||||
|
|
||||||
'@types/bn.js@5.1.6':
|
'@types/bn.js@5.1.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/body-parser@1.19.5':
|
'@types/body-parser@1.19.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/buffer-json@2.0.3': {}
|
'@types/buffer-json@2.0.3': {}
|
||||||
|
|
||||||
@ -6833,17 +6947,17 @@ snapshots:
|
|||||||
|
|
||||||
'@types/clean-css@4.2.11':
|
'@types/clean-css@4.2.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
|
|
||||||
'@types/co-body@6.1.3':
|
'@types/co-body@6.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
'@types/qs': 6.9.18
|
'@types/qs': 6.9.18
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/content-disposition@0.5.8': {}
|
'@types/content-disposition@0.5.8': {}
|
||||||
|
|
||||||
@ -6854,11 +6968,11 @@ snapshots:
|
|||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/express': 5.0.1
|
'@types/express': 5.0.1
|
||||||
'@types/keygrip': 1.0.6
|
'@types/keygrip': 1.0.6
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/cors@2.8.17':
|
'@types/cors@2.8.17':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/debounce@1.2.4': {}
|
'@types/debounce@1.2.4': {}
|
||||||
|
|
||||||
@ -6870,9 +6984,11 @@ 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.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/elliptic@6.4.18':
|
'@types/elliptic@6.4.18':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6880,7 +6996,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/express-serve-static-core@5.0.6':
|
'@types/express-serve-static-core@5.0.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
'@types/qs': 6.9.18
|
'@types/qs': 6.9.18
|
||||||
'@types/range-parser': 1.2.7
|
'@types/range-parser': 1.2.7
|
||||||
'@types/send': 0.17.4
|
'@types/send': 0.17.4
|
||||||
@ -6897,30 +7013,30 @@ snapshots:
|
|||||||
|
|
||||||
'@types/from2@2.3.5':
|
'@types/from2@2.3.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/fs-extra@11.0.4':
|
'@types/fs-extra@11.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/jsonfile': 6.1.4
|
'@types/jsonfile': 6.1.4
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/fs-extra@9.0.13':
|
'@types/fs-extra@9.0.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/glob@7.2.0':
|
'@types/glob@7.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimatch': 5.1.2
|
'@types/minimatch': 5.1.2
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/glob@8.1.0':
|
'@types/glob@8.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimatch': 5.1.2
|
'@types/minimatch': 5.1.2
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/gunzip-maybe@1.4.2':
|
'@types/gunzip-maybe@1.4.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6954,7 +7070,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/jsonfile@6.1.4':
|
'@types/jsonfile@6.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/keygrip@1.0.6': {}
|
'@types/keygrip@1.0.6': {}
|
||||||
|
|
||||||
@ -6971,7 +7087,7 @@ snapshots:
|
|||||||
'@types/http-errors': 2.0.4
|
'@types/http-errors': 2.0.4
|
||||||
'@types/keygrip': 1.0.6
|
'@types/keygrip': 1.0.6
|
||||||
'@types/koa-compose': 3.2.8
|
'@types/koa-compose': 3.2.8
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6989,18 +7105,18 @@ snapshots:
|
|||||||
|
|
||||||
'@types/node-fetch@2.6.12':
|
'@types/node-fetch@2.6.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
form-data: 4.0.2
|
form-data: 4.0.2
|
||||||
|
|
||||||
'@types/node-forge@1.3.11':
|
'@types/node-forge@1.3.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/node@18.19.87':
|
'@types/node@18.19.87':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 5.26.5
|
undici-types: 5.26.5
|
||||||
|
|
||||||
'@types/node@22.15.2':
|
'@types/node@22.15.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
|
|
||||||
@ -7018,19 +7134,19 @@ snapshots:
|
|||||||
|
|
||||||
'@types/s3rver@3.7.4':
|
'@types/s3rver@3.7.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/semver@7.7.0': {}
|
'@types/semver@7.7.0': {}
|
||||||
|
|
||||||
'@types/send@0.17.4':
|
'@types/send@0.17.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mime': 1.3.5
|
'@types/mime': 1.3.5
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/serve-static@1.15.7':
|
'@types/serve-static@1.15.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/http-errors': 2.0.4
|
'@types/http-errors': 2.0.4
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
'@types/send': 0.17.4
|
'@types/send': 0.17.4
|
||||||
|
|
||||||
'@types/sinon-chai@3.2.12':
|
'@types/sinon-chai@3.2.12':
|
||||||
@ -7050,11 +7166,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/tar-stream@2.2.3':
|
'@types/tar-stream@2.2.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/through2@2.0.41':
|
'@types/through2@2.0.41':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/triple-beam@1.3.5': {}
|
'@types/triple-beam@1.3.5': {}
|
||||||
|
|
||||||
@ -7078,18 +7194,18 @@ snapshots:
|
|||||||
|
|
||||||
'@types/whatwg-url@8.2.2':
|
'@types/whatwg-url@8.2.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
'@types/webidl-conversions': 7.0.3
|
'@types/webidl-conversions': 7.0.3
|
||||||
|
|
||||||
'@types/which@3.0.4': {}
|
'@types/which@3.0.4': {}
|
||||||
|
|
||||||
'@types/ws@7.4.7':
|
'@types/ws@7.4.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
|
|
||||||
'@types/yargs-parser@21.0.3': {}
|
'@types/yargs-parser@21.0.3': {}
|
||||||
|
|
||||||
@ -7099,7 +7215,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
@ -7490,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:
|
||||||
@ -7566,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: {}
|
||||||
@ -7775,7 +7897,7 @@ snapshots:
|
|||||||
engine.io@6.6.4:
|
engine.io@6.6.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/cors': 2.8.17
|
'@types/cors': 2.8.17
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
accepts: 1.3.8
|
accepts: 1.3.8
|
||||||
base64id: 2.0.0
|
base64id: 2.0.0
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
@ -8137,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:
|
||||||
@ -8247,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: {}
|
||||||
@ -8483,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
|
||||||
@ -8503,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: {}
|
||||||
@ -8579,7 +8724,7 @@ snapshots:
|
|||||||
jest-util@29.7.0:
|
jest-util@29.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
'@types/node': 22.15.2
|
'@types/node': 22.15.3
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 3.9.0
|
ci-info: 3.9.0
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@ -9542,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
|
||||||
@ -10045,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
|
||||||
@ -10110,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
|
||||||
@ -10172,6 +10333,8 @@ snapshots:
|
|||||||
|
|
||||||
type-fest@4.40.0: {}
|
type-fest@4.40.0: {}
|
||||||
|
|
||||||
|
type-fest@4.40.1: {}
|
||||||
|
|
||||||
type-is@1.6.18:
|
type-is@1.6.18:
|
||||||
dependencies:
|
dependencies:
|
||||||
media-typer: 0.3.0
|
media-typer: 0.3.0
|
||||||
|
190
readme.md
190
readme.md
@ -4,19 +4,26 @@ A TypeScript-based ACME client with an easy yet powerful interface for LetsEncry
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
To install `@push.rocks/smartacme`, you can use npm or yarn. Run one of the following commands in your project directory:
|
Using pnpm as the package manager:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @push.rocks/smartacme --save
|
pnpm add @push.rocks/smartacme
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
Ensure your project is set up to use TypeScript and ECMAScript Modules (ESM).
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
Tests are written using `@push.rocks/tapbundle` and can be run with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn add @push.rocks/smartacme
|
pnpm test
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure your project is set up to use TypeScript and supports ECMAScript Modules (ESM).
|
To run a specific test file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tsx test/<test-file>.ts
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -42,28 +49,31 @@ Ensure your project includes the necessary TypeScript configuration and dependen
|
|||||||
Start by importing the `SmartAcme` class and any built-in handlers you plan to use. For example, to use DNS-01 via Cloudflare:
|
Start by importing the `SmartAcme` class and any built-in handlers you plan to use. For example, to use DNS-01 via Cloudflare:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartAcme } from '@push.rocks/smartacme';
|
import { SmartAcme, MongoCertManager } from '@push.rocks/smartacme';
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||||
import { Dns01Handler } from '@push.rocks/smartacme/ts/handlers/Dns01Handler.js';
|
import { Dns01Handler } from '@push.rocks/smartacme/ts/handlers/Dns01Handler.js';
|
||||||
|
|
||||||
// Create a Cloudflare account client with your API token
|
// Create a Cloudflare account client with your API token
|
||||||
const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_TOKEN');
|
const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_TOKEN');
|
||||||
|
|
||||||
// Instantiate SmartAcme with one or more ACME challenge handlers
|
// Initialize a certificate manager (e.g., MongoDB)
|
||||||
const smartAcmeInstance = new SmartAcme({
|
const certManager = new MongoCertManager({
|
||||||
accountEmail: 'youremail@example.com',
|
|
||||||
mongoDescriptor: {
|
|
||||||
mongoDbUrl: 'mongodb://yourmongoURL',
|
mongoDbUrl: 'mongodb://yourmongoURL',
|
||||||
mongoDbName: 'yourDbName',
|
mongoDbName: 'yourDbName',
|
||||||
mongoDbPass: 'yourDbPassword',
|
mongoDbPass: 'yourDbPassword',
|
||||||
},
|
});
|
||||||
|
|
||||||
|
// Instantiate SmartAcme with the certManager and challenge handlers
|
||||||
|
const smartAcmeInstance = new SmartAcme({
|
||||||
|
accountEmail: 'youremail@example.com',
|
||||||
|
certManager,
|
||||||
environment: 'integration', // 'production' to request real certificates
|
environment: 'integration', // 'production' to request real certificates
|
||||||
retryOptions: {}, // optional retry/backoff settings
|
retryOptions: {}, // optional retry/backoff settings
|
||||||
challengeHandlers: [
|
challengeHandlers: [ // pluggable ACME challenge handlers
|
||||||
new Dns01Handler(cfAccount),
|
new Dns01Handler(cfAccount),
|
||||||
// you can add more handlers, e.g. Http01Handler
|
// add more handlers as needed (e.g., Http01Webroot, Http01MemoryHandler)
|
||||||
],
|
],
|
||||||
challengePriority: ['dns-01'], // optional ordering of challenge types
|
challengePriority: ['dns-01'], // optional challenge ordering
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -91,14 +101,64 @@ SmartAcme uses pluggable ACME challenge handlers (see built-in handlers below) t
|
|||||||
|
|
||||||
### Managing Certificates
|
### Managing Certificates
|
||||||
|
|
||||||
The library automatically handles fetching, renewing, and storing your certificates in a MongoDB database specified in your configuration. Ensure your MongoDB instance is accessible and properly configured for use with SmartAcme.
|
The library automatically handles fetching, renewing, and storing your certificates in a MongoDB database specified via a certificate manager. Ensure your MongoDB instance is accessible and properly configured for use with SmartAcme.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const mongoDescriptor = {
|
import { MongoCertManager } from '@push.rocks/smartacme';
|
||||||
|
|
||||||
|
const certManager = new MongoCertManager({
|
||||||
mongoDbUrl: 'mongodb://yourmongoURL',
|
mongoDbUrl: 'mongodb://yourmongoURL',
|
||||||
mongoDbName: 'yourDbName',
|
mongoDbName: 'yourDbName',
|
||||||
mongoDbPass: 'yourDbPassword',
|
mongoDbPass: 'yourDbPassword',
|
||||||
};
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
SmartAcme uses the `ICertManager` interface for certificate storage. Two built-in implementations are available:
|
||||||
|
|
||||||
|
- **MemoryCertManager**
|
||||||
|
- In-memory storage, suitable for testing or ephemeral use.
|
||||||
|
- Import example:
|
||||||
|
```typescript
|
||||||
|
import { MemoryCertManager } from '@push.rocks/smartacme';
|
||||||
|
const certManager = new MemoryCertManager();
|
||||||
|
```
|
||||||
|
|
||||||
|
- **MongoCertManager**
|
||||||
|
- Persistent storage in MongoDB (collection: `SmartacmeCert`).
|
||||||
|
- Import example:
|
||||||
|
```typescript
|
||||||
|
import { MongoCertManager } from '@push.rocks/smartacme';
|
||||||
|
const certManager = new MongoCertManager({
|
||||||
|
mongoDbUrl: 'mongodb://yourmongoURL',
|
||||||
|
mongoDbName: 'yourDbName',
|
||||||
|
mongoDbPass: 'yourDbPassword',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Certificate Managers
|
||||||
|
|
||||||
|
To implement a custom certificate manager, implement the `ICertManager` interface and pass it to `SmartAcme`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { ICertManager, Cert as SmartacmeCert } from '@push.rocks/smartacme';
|
||||||
|
import { SmartAcme } from '@push.rocks/smartacme';
|
||||||
|
|
||||||
|
class MyCustomCertManager implements ICertManager {
|
||||||
|
async init(): Promise<void> { /* setup storage */ }
|
||||||
|
async get(domainName: string): Promise<SmartacmeCert | null> { /* lookup cert */ }
|
||||||
|
async put(cert: SmartacmeCert): Promise<SmartacmeCert> { /* store cert */ }
|
||||||
|
async delete(domainName: string): Promise<void> { /* remove cert */ }
|
||||||
|
async close?(): Promise<void> { /* optional cleanup */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use your custom manager:
|
||||||
|
const customManager = new MyCustomCertManager();
|
||||||
|
const smartAcme = new SmartAcme({
|
||||||
|
accountEmail: 'youremail@example.com',
|
||||||
|
certManager: customManager,
|
||||||
|
environment: 'integration',
|
||||||
|
challengeHandlers: [], // add your handlers
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Environmental Considerations
|
### Environmental Considerations
|
||||||
@ -110,21 +170,25 @@ When creating an instance of `SmartAcme`, you can specify an `environment` optio
|
|||||||
Below is a complete example demonstrating how to use `@push.rocks/smartacme` to obtain and manage an ACME certificate with Let's Encrypt using a DNS-01 handler:
|
Below is a complete example demonstrating how to use `@push.rocks/smartacme` to obtain and manage an ACME certificate with Let's Encrypt using a DNS-01 handler:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartAcme } from '@push.rocks/smartacme';
|
import { SmartAcme, MongoCertManager } from '@push.rocks/smartacme';
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||||
import { Qenv } from '@push.rocks/qenv';
|
import { Qenv } from '@push.rocks/qenv';
|
||||||
|
import { Dns01Handler } from '@push.rocks/smartacme/ts/handlers/Dns01Handler.js';
|
||||||
|
|
||||||
const qenv = new Qenv('./', './.nogit/');
|
const qenv = new Qenv('./', './.nogit/');
|
||||||
const cloudflareAccount = new cloudflare.CloudflareAccount(qenv.getEnvVarOnDemand('CF_TOKEN'));
|
const cloudflareAccount = new cloudflare.CloudflareAccount(qenv.getEnvVarOnDemand('CF_TOKEN'));
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const smartAcmeInstance = new SmartAcme({
|
// Initialize MongoDB certificate manager
|
||||||
accountEmail: 'youremail@example.com',
|
const certManager = new MongoCertManager({
|
||||||
mongoDescriptor: {
|
|
||||||
mongoDbUrl: qenv.getEnvVarRequired('MONGODB_URL'),
|
mongoDbUrl: qenv.getEnvVarRequired('MONGODB_URL'),
|
||||||
mongoDbName: qenv.getEnvVarRequired('MONGODB_DATABASE'),
|
mongoDbName: qenv.getEnvVarRequired('MONGODB_DATABASE'),
|
||||||
mongoDbPass: qenv.getEnvVarRequired('MONGODB_PASSWORD'),
|
mongoDbPass: qenv.getEnvVarRequired('MONGODB_PASSWORD'),
|
||||||
},
|
});
|
||||||
|
|
||||||
|
const smartAcmeInstance = new SmartAcme({
|
||||||
|
accountEmail: 'youremail@example.com',
|
||||||
|
certManager,
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
challengeHandlers: [new Dns01Handler(cloudflareAccount)],
|
challengeHandlers: [new Dns01Handler(cloudflareAccount)],
|
||||||
});
|
});
|
||||||
@ -143,7 +207,7 @@ async function main() {
|
|||||||
|
|
||||||
## Built-in Challenge Handlers
|
## Built-in Challenge Handlers
|
||||||
|
|
||||||
This module includes two out-of-the-box ACME challenge handlers:
|
This module includes three out-of-the-box ACME challenge handlers:
|
||||||
|
|
||||||
- **Dns01Handler**
|
- **Dns01Handler**
|
||||||
- Uses a Cloudflare account (from `@apiclient.xyz/cloudflare`) and Smartdns client to set and remove DNS TXT records, then wait for propagation.
|
- Uses a Cloudflare account (from `@apiclient.xyz/cloudflare`) and Smartdns client to set and remove DNS TXT records, then wait for propagation.
|
||||||
@ -158,18 +222,31 @@ async function main() {
|
|||||||
const dnsHandler = new Dns01Handler(cfAccount);
|
const dnsHandler = new Dns01Handler(cfAccount);
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Http01Handler**
|
- **Http01Webroot**
|
||||||
- Writes ACME HTTP-01 challenge files under a file-system webroot (`/.well-known/acme-challenge/`), and removes them on cleanup.
|
- Writes ACME HTTP-01 challenge files under a file-system webroot (`/.well-known/acme-challenge/`), and removes them on cleanup.
|
||||||
- Import path:
|
- Import path:
|
||||||
```typescript
|
```typescript
|
||||||
import { Http01Handler } from '@push.rocks/smartacme/ts/handlers/Http01Handler.js';
|
import { Http01Webroot } from '@push.rocks/smartacme/ts/handlers/Http01Handler.js';
|
||||||
```
|
```
|
||||||
- Example:
|
- Example:
|
||||||
```typescript
|
```typescript
|
||||||
const httpHandler = new Http01Handler({ webroot: '/var/www/html' });
|
const httpHandler = new Http01Webroot({ webroot: '/var/www/html' });
|
||||||
```
|
```
|
||||||
|
|
||||||
Both handlers implement the `IChallengeHandler<T>` interface and can be combined in the `challengeHandlers` array.
|
- **Http01MemoryHandler**
|
||||||
|
- In-memory HTTP-01 challenge handler that stores and serves ACME tokens without disk I/O.
|
||||||
|
- Import path:
|
||||||
|
```typescript
|
||||||
|
import { Http01MemoryHandler } from '@push.rocks/smartacme/ts/handlers/Http01MemoryHandler.js';
|
||||||
|
```
|
||||||
|
- Example (Express integration):
|
||||||
|
```typescript
|
||||||
|
import { Http01MemoryHandler } from '@push.rocks/smartacme/ts/handlers/Http01MemoryHandler.js';
|
||||||
|
const memoryHandler = new Http01MemoryHandler();
|
||||||
|
app.use((req, res, next) => memoryHandler.handleRequest(req, res, next));
|
||||||
|
```
|
||||||
|
|
||||||
|
All handlers implement the `IChallengeHandler<T>` interface and can be combined in the `challengeHandlers` array.
|
||||||
|
|
||||||
## Creating Custom Handlers
|
## Creating Custom Handlers
|
||||||
|
|
||||||
@ -209,7 +286,7 @@ async function main() {
|
|||||||
challengePriority: ['my-01'],
|
challengePriority: ['my-01'],
|
||||||
});
|
});
|
||||||
|
|
||||||
In this example, `Qenv` is used to manage environment variables, and `cloudflare` library is used to handle DNS challenges required by Let's Encrypt ACME protocol. The `setChallenge` and `removeChallenge` methods are essential for automating the DNS challenge process, which is a key part of domain validation.
|
In this example, `Qenv` is used to manage environment variables, and the Cloudflare library is used to handle DNS challenges through the built-in `Dns01Handler` plugin.
|
||||||
|
|
||||||
## Additional Details
|
## Additional Details
|
||||||
|
|
||||||
@ -230,8 +307,6 @@ 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)**: 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.
|
||||||
- **setChallenge(dnsChallenge: any)**: Automates the process of setting DNS challenge records.
|
|
||||||
- **removeChallenge(dnsChallenge: any)**: Automates the process of removing DNS challenge records.
|
|
||||||
|
|
||||||
### Handling Domain Matching
|
### Handling Domain Matching
|
||||||
|
|
||||||
@ -247,60 +322,13 @@ console.log('Certificate Domain Name:', certDomainName); // Output: example.com
|
|||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
Automated tests can be added to ensure that the setup and functions work as expected. Using a testing framework such as `tap` and mock services for DNS providers (e.g., Cloudflare), you can simulate the process of obtaining and managing certificates without the need for actual domain ownership.
|
Sample tests are provided in the `test` directory. They demonstrate core functionality using the `MemoryCertManager` and built-in challenge handlers. To run all tests, use:
|
||||||
|
|
||||||
```typescript
|
```bash
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
pnpm test
|
||||||
import { Qenv } from '@push.rocks/qenv';
|
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
|
||||||
import * as smartacme from '@push.rocks/smartacme';
|
|
||||||
|
|
||||||
const testQenv = new Qenv('./', './.nogit/');
|
|
||||||
const testCloudflare = new cloudflare.CloudflareAccount(testQenv.getEnvVarOnDemand('CF_TOKEN'));
|
|
||||||
|
|
||||||
let smartAcmeInstance: smartacme.SmartAcme;
|
|
||||||
|
|
||||||
tap.test('should create a valid instance of SmartAcme', async () => {
|
|
||||||
smartAcmeInstance = new smartacme.SmartAcme({
|
|
||||||
accountEmail: 'domains@lossless.org',
|
|
||||||
accountPrivateKey: null,
|
|
||||||
mongoDescriptor: {
|
|
||||||
mongoDbName: testQenv.getEnvVarRequired('MONGODB_DATABASE'),
|
|
||||||
mongoDbPass: testQenv.getEnvVarRequired('MONGODB_PASSWORD'),
|
|
||||||
mongoDbUrl: testQenv.getEnvVarRequired('MONGODB_URL'),
|
|
||||||
},
|
|
||||||
setChallenge: async (dnsChallenge) => {
|
|
||||||
await testCloudflare.convenience.acmeSetDnsChallenge(dnsChallenge);
|
|
||||||
},
|
|
||||||
removeChallenge: async (dnsChallenge) => {
|
|
||||||
await testCloudflare.convenience.acmeRemoveDnsChallenge(dnsChallenge);
|
|
||||||
},
|
|
||||||
environment: 'integration',
|
|
||||||
});
|
|
||||||
await smartAcmeInstance.init();
|
|
||||||
expect(smartAcmeInstance).toBeInstanceOf(smartacme.SmartAcme);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should get a domain certificate', async () => {
|
|
||||||
const certificate = await smartAcmeInstance.getCertificateForDomain('example.com');
|
|
||||||
console.log('Certificate:', certificate);
|
|
||||||
expect(certificate).toHaveProperty('domainName', 'example.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('certmatcher should correctly match domains', async () => {
|
|
||||||
const certMatcher = new smartacme.SmartacmeCertMatcher();
|
|
||||||
const matchedCert = certMatcher.getCertificateDomainNameByDomainName('subdomain.example.com');
|
|
||||||
expect(matchedCert).toBe('example.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should stop correctly', async () => {
|
|
||||||
await smartAcmeInstance.stop();
|
|
||||||
expect(smartAcmeInstance).toHaveProperty('client', null);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.start();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
This comprehensive guide ensures you can set up, manage, and test ACME certificates efficiently and effectively using `@push.rocks/smartacme`.
|
This comprehensive guide ensures you can set up, manage, and test ACME certificates efficiently and effectively using `@push.rocks/smartacme`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
3
readme.plan.md
Normal file
3
readme.plan.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## Plan
|
||||||
|
|
||||||
|
Move the
|
@ -18,4 +18,12 @@ tap.test('should return undefined for deeper domain', async () => {
|
|||||||
expect(result).toEqual(undefined);
|
expect(result).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wildcard domain handling
|
||||||
|
tap.test('should strip wildcard prefix and return base domain', async () => {
|
||||||
|
const matcher = new SmartacmeCertMatcher();
|
||||||
|
expect(matcher.getCertificateDomainNameByDomainName('*.example.com')).toEqual('example.com');
|
||||||
|
expect(matcher.getCertificateDomainNameByDomainName('*.sub.example.com')).toEqual('sub.example.com');
|
||||||
|
expect(matcher.getCertificateDomainNameByDomainName('*.a.b.example.com')).toEqual('a.b.example.com');
|
||||||
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
@ -7,15 +7,11 @@ tap.test('Dns01Handler prepare and cleanup calls Cloudflare and DNS functions',
|
|||||||
// fake Cloudflare API
|
// fake Cloudflare API
|
||||||
const fakeCF: any = {
|
const fakeCF: any = {
|
||||||
convenience: {
|
convenience: {
|
||||||
acmeSetDnsChallenge: async (ch: any) => {
|
acmeSetDnsChallenge: async (_ch: any) => {
|
||||||
setCalled = true;
|
setCalled = true;
|
||||||
expect(ch).toHaveProperty('hostName');
|
|
||||||
expect(ch).toHaveProperty('challenge');
|
|
||||||
},
|
},
|
||||||
acmeRemoveDnsChallenge: async (ch: any) => {
|
acmeRemoveDnsChallenge: async (_ch: any) => {
|
||||||
removeCalled = true;
|
removeCalled = true;
|
||||||
expect(ch).toHaveProperty('hostName');
|
|
||||||
expect(ch).toHaveProperty('challenge');
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
58
test/test.handlers-http01-memory.ts
Normal file
58
test/test.handlers-http01-memory.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
import { Http01MemoryHandler } from '../ts/handlers/Http01MemoryHandler.js';
|
||||||
|
|
||||||
|
tap.test('Http01MemoryHandler serves in-memory challenges and cleans up', async () => {
|
||||||
|
const handler = new Http01MemoryHandler();
|
||||||
|
const token = 'testtoken';
|
||||||
|
const keyAuth = 'keyAuthValue';
|
||||||
|
const webPath = `/.well-known/acme-challenge/${token}`;
|
||||||
|
const challenge = { type: 'http-01', token, keyAuthorization: keyAuth, webPath };
|
||||||
|
|
||||||
|
// Prepare challenge (store in memory)
|
||||||
|
await handler.prepare(challenge);
|
||||||
|
|
||||||
|
// Serve existing challenge without next()
|
||||||
|
const req1: any = { url: webPath };
|
||||||
|
const res1: any = {
|
||||||
|
statusCode: 0,
|
||||||
|
headers: {} as Record<string, string>,
|
||||||
|
body: '',
|
||||||
|
setHeader(name: string, value: string) { this.headers[name] = value; },
|
||||||
|
end(body?: string) { this.body = body || ''; },
|
||||||
|
};
|
||||||
|
handler.handleRequest(req1, res1);
|
||||||
|
expect(res1.statusCode).toEqual(200);
|
||||||
|
expect(res1.body).toEqual(keyAuth);
|
||||||
|
expect(res1.headers['content-type']).toEqual('text/plain');
|
||||||
|
|
||||||
|
// Cleanup challenge (remove from memory)
|
||||||
|
await handler.cleanup(challenge);
|
||||||
|
|
||||||
|
// Serve after cleanup without next() should give 404
|
||||||
|
const req2: any = { url: webPath };
|
||||||
|
const res2: any = {
|
||||||
|
statusCode: 0,
|
||||||
|
headers: {} as Record<string, string>,
|
||||||
|
body: '',
|
||||||
|
setHeader(name: string, value: string) { this.headers[name] = value; },
|
||||||
|
end(body?: string) { this.body = body || ''; },
|
||||||
|
};
|
||||||
|
handler.handleRequest(req2, res2);
|
||||||
|
expect(res2.statusCode).toEqual(404);
|
||||||
|
|
||||||
|
// Serve after cleanup with next() should call next
|
||||||
|
const req3: any = { url: webPath };
|
||||||
|
let nextCalled = false;
|
||||||
|
const next = () => { nextCalled = true; };
|
||||||
|
const res3: any = {
|
||||||
|
statusCode: 0,
|
||||||
|
headers: {} as Record<string, string>,
|
||||||
|
body: '',
|
||||||
|
setHeader(name: string, value: string) { this.headers[name] = value; },
|
||||||
|
end(body?: string) { this.body = body || ''; },
|
||||||
|
};
|
||||||
|
handler.handleRequest(req3, res3, next);
|
||||||
|
expect(nextCalled).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@ -1,13 +1,13 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import { Http01Handler } from '../ts/handlers/Http01Handler.js';
|
import { Http01Webroot } from '../ts/handlers/Http01Handler.js';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
tap.test('Http01Handler writes challenge file and removes it on cleanup', async () => {
|
tap.test('Http01Webroot writes challenge file and removes it on cleanup', async () => {
|
||||||
// create temporary webroot directory
|
// create temporary webroot directory
|
||||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'http01-'));
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'http01-'));
|
||||||
const handler = new Http01Handler({ webroot: tmpDir });
|
const handler = new Http01Webroot({ webroot: tmpDir });
|
||||||
const token = 'testtoken';
|
const token = 'testtoken';
|
||||||
const keyAuth = 'keyAuthValue';
|
const keyAuth = 'keyAuthValue';
|
||||||
const webPath = `/.well-known/acme-challenge/${token}`;
|
const webPath = `/.well-known/acme-challenge/${token}`;
|
||||||
|
@ -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 } 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/)
|
||||||
@ -14,12 +14,14 @@ const mongoDbName = (await testQenv.getEnvVarOnDemand('MONGODB_DATABASE'))!;
|
|||||||
const mongoDbPass = (await testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'))!;
|
const mongoDbPass = (await testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'))!;
|
||||||
const mongoDbUrl = (await testQenv.getEnvVarOnDemand('MONGODB_URL'))!;
|
const mongoDbUrl = (await testQenv.getEnvVarOnDemand('MONGODB_URL'))!;
|
||||||
|
|
||||||
|
|
||||||
let smartAcmeInstance: SmartAcme;
|
let smartAcmeInstance: SmartAcme;
|
||||||
|
|
||||||
tap.test('create SmartAcme instance with DNS-01 handler and start', async () => {
|
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',
|
||||||
mongoDescriptor: { mongoDbName, mongoDbPass, mongoDbUrl },
|
// certManager: new MongoCertManager({ mongoDbName, mongoDbPass, mongoDbUrl }),
|
||||||
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
retryOptions: {},
|
retryOptions: {},
|
||||||
challengeHandlers: [new Dns01Handler(cfAccount)],
|
challengeHandlers: [new Dns01Handler(cfAccount)],
|
||||||
@ -29,6 +31,10 @@ tap.test('create SmartAcme instance with DNS-01 handler and start', async () =>
|
|||||||
expect(smartAcmeInstance).toBeInstanceOf(SmartAcme);
|
expect(smartAcmeInstance).toBeInstanceOf(SmartAcme);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tap.test('should wipe the certmanager for this test', async () => {
|
||||||
|
await smartAcmeInstance.certmanager.wipe();
|
||||||
|
});
|
||||||
|
|
||||||
tap.test('get a domain certificate via DNS-01 challenge', async () => {
|
tap.test('get a domain certificate via DNS-01 challenge', async () => {
|
||||||
// Replace 'bleu.de' with your test domain if different
|
// Replace 'bleu.de' with your test domain if different
|
||||||
const domain = 'bleu.de';
|
const domain = 'bleu.de';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import { SmartAcme } from '../ts/index.js';
|
import { SmartAcme, certmanagers } 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';
|
||||||
|
|
||||||
// Dummy handler for testing
|
// Dummy handler for testing
|
||||||
@ -7,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',
|
||||||
mongoDescriptor: { mongoDbName: 'db', mongoDbPass: 'pass', mongoDbUrl: 'url' },
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
retryOptions: {},
|
retryOptions: {},
|
||||||
} as any)).toThrow();
|
} as any)).toThrow();
|
||||||
@ -21,12 +23,39 @@ 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',
|
||||||
mongoDescriptor: { mongoDbName: 'db', mongoDbPass: 'pass', mongoDbUrl: 'url' },
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
retryOptions: {},
|
retryOptions: {},
|
||||||
challengeHandlers: [new DummyHandler()],
|
challengeHandlers: [new DummyHandler()],
|
||||||
});
|
});
|
||||||
expect(sa).toBeInstanceOf(SmartAcme);
|
expect(sa).toBeInstanceOf(SmartAcme);
|
||||||
});
|
});
|
||||||
|
// Wildcard certificate stub for integration mode (unit test override)
|
||||||
|
tap.test('get wildcard certificate stub in integration mode', async () => {
|
||||||
|
// Temporarily stub SmartAcme.start and getCertificateForDomain for wildcard
|
||||||
|
const origStart = SmartAcme.prototype.start;
|
||||||
|
const origGetCert = SmartAcme.prototype.getCertificateForDomain;
|
||||||
|
try {
|
||||||
|
SmartAcme.prototype.start = async function(): Promise<void> { /* no-op */ };
|
||||||
|
SmartAcme.prototype.getCertificateForDomain = async function(domain: string) {
|
||||||
|
return new Cert({ domainName: domain });
|
||||||
|
};
|
||||||
|
const sa = new SmartAcme({
|
||||||
|
accountEmail: 'domains@lossless.org',
|
||||||
|
certManager: new certmanagers.MemoryCertManager(),
|
||||||
|
environment: 'integration',
|
||||||
|
retryOptions: {},
|
||||||
|
challengeHandlers: [new DummyHandler()],
|
||||||
|
});
|
||||||
|
await sa.start();
|
||||||
|
const domainWildcard = '*.example.com';
|
||||||
|
const cert = await sa.getCertificateForDomain(domainWildcard);
|
||||||
|
expect(cert.domainName).toEqual(domainWildcard);
|
||||||
|
await sa.stop();
|
||||||
|
} finally {
|
||||||
|
SmartAcme.prototype.start = origStart;
|
||||||
|
SmartAcme.prototype.getCertificateForDomain = origGetCert;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartacme',
|
name: '@push.rocks/smartacme',
|
||||||
version: '6.1.2',
|
version: '7.3.3',
|
||||||
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.'
|
||||||
}
|
}
|
||||||
|
2
ts/certmanagers/index.ts
Normal file
2
ts/certmanagers/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './memory.js';
|
||||||
|
export * from './mongo.js';
|
38
ts/certmanagers/memory.ts
Normal file
38
ts/certmanagers/memory.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import type { ICertManager } from '../interfaces/certmanager.js';
|
||||||
|
import { SmartacmeCert } from '../smartacme.classes.cert.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory certificate manager for mongoless mode.
|
||||||
|
* Stores certificates in memory only and does not connect to MongoDB.
|
||||||
|
*/
|
||||||
|
export class MemoryCertManager implements ICertManager {
|
||||||
|
private certs: Map<string, SmartacmeCert> = new Map();
|
||||||
|
|
||||||
|
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
// no-op for in-memory store
|
||||||
|
}
|
||||||
|
|
||||||
|
public async retrieveCertificate(domainName: string): Promise<SmartacmeCert | null> {
|
||||||
|
return this.certs.get(domainName) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async storeCertificate(cert: SmartacmeCert): Promise<void> {
|
||||||
|
this.certs.set(cert.domainName, cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteCertificate(domainName: string): Promise<void> {
|
||||||
|
this.certs.delete(domainName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wipe all certificates from the in-memory store (for testing)
|
||||||
|
*/
|
||||||
|
public async wipe(): Promise<void> {
|
||||||
|
this.certs.clear();
|
||||||
|
}
|
||||||
|
}
|
52
ts/certmanagers/mongo.ts
Normal file
52
ts/certmanagers/mongo.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import type { ICertManager } from '../interfaces/certmanager.js';
|
||||||
|
import { SmartacmeCert } from '../smartacme.classes.cert.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MongoDB-backed certificate manager using EasyStore from smartdata.
|
||||||
|
*/
|
||||||
|
export class MongoCertManager implements ICertManager {
|
||||||
|
private db: plugins.smartdata.SmartdataDb;
|
||||||
|
private store: plugins.smartdata.EasyStore<Record<string, any>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mongoDescriptor MongoDB connection settings
|
||||||
|
*/
|
||||||
|
constructor(mongoDescriptor: plugins.smartdata.IMongoDescriptor) {
|
||||||
|
this.db = new plugins.smartdata.SmartdataDb(mongoDescriptor);
|
||||||
|
// Use a single EasyStore document to hold all certs keyed by domainName
|
||||||
|
this.store = new plugins.smartdata.EasyStore<Record<string, any>>(
|
||||||
|
'smartacme-certs',
|
||||||
|
this.db,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
await this.db.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async retrieveCertificate(domainName: string): Promise<SmartacmeCert | null> {
|
||||||
|
const data = await this.store.readKey(domainName);
|
||||||
|
return data ? new SmartacmeCert(data) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async storeCertificate(cert: SmartacmeCert): Promise<void> {
|
||||||
|
// write plain object for persistence
|
||||||
|
await this.store.writeKey(cert.domainName, { ...cert });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteCertificate(domainName: string): Promise<void> {
|
||||||
|
await this.store.deleteKey(domainName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
await this.db.close();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wipe all certificates from the persistent store (for integration testing)
|
||||||
|
*/
|
||||||
|
public async wipe(): Promise<void> {
|
||||||
|
// clear all keys in the easy store
|
||||||
|
await this.store.wipe();
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,18 @@
|
|||||||
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DNS-01 challenge handler using CloudflareAccount and Smartdns.
|
* DNS-01 challenge handler using CloudflareAccount and Smartdns.
|
||||||
*/
|
*/
|
||||||
export class Dns01Handler implements IChallengeHandler<plugins.tsclass.network.IDnsChallenge> {
|
export class Dns01Handler implements IChallengeHandler<plugins.tsclass.network.IDnsChallenge> {
|
||||||
private cf: any;
|
private cf: plugins.tsclass.network.IConvenientDnsProvider;
|
||||||
private smartdns: plugins.smartdnsClient.Smartdns;
|
private smartdns: plugins.smartdnsClient.Smartdns;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
cloudflareAccount: any,
|
convenientDnsProvider: plugins.tsclass.network.IConvenientDnsProvider,
|
||||||
smartdnsInstance?: plugins.smartdnsClient.Smartdns,
|
smartdnsInstance?: plugins.smartdnsClient.Smartdns,
|
||||||
) {
|
) {
|
||||||
this.cf = cloudflareAccount;
|
this.cf = convenientDnsProvider;
|
||||||
this.smartdns = smartdnsInstance ?? new plugins.smartdnsClient.Smartdns({});
|
this.smartdns = smartdnsInstance ?? new plugins.smartdnsClient.Smartdns({});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,18 +23,14 @@ export class Dns01Handler implements IChallengeHandler<plugins.tsclass.network.I
|
|||||||
public async prepare(ch: plugins.tsclass.network.IDnsChallenge): Promise<void> {
|
public async prepare(ch: plugins.tsclass.network.IDnsChallenge): Promise<void> {
|
||||||
// set DNS TXT record
|
// set DNS TXT record
|
||||||
await this.cf.convenience.acmeSetDnsChallenge(ch);
|
await this.cf.convenience.acmeSetDnsChallenge(ch);
|
||||||
// wait for DNS propagation
|
|
||||||
await this.smartdns.checkUntilAvailable(
|
|
||||||
ch.hostName,
|
|
||||||
'TXT',
|
|
||||||
ch.challenge,
|
|
||||||
100,
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cleanup(ch: plugins.tsclass.network.IDnsChallenge): Promise<void> {
|
public async cleanup(ch: plugins.tsclass.network.IDnsChallenge): Promise<void> {
|
||||||
// remove DNS TXT record
|
// remove DNS TXT record
|
||||||
await this.cf.convenience.acmeRemoveDnsChallenge(ch);
|
await this.cf.convenience.acmeRemoveDnsChallenge(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async checkWetherDomainIsSupported(domainArg: string): Promise<boolean> {
|
||||||
|
return this.cf.convenience.isDomainSupported(domainArg);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,27 +1,29 @@
|
|||||||
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP-01 ACME challenge handler using file-system webroot.
|
* HTTP-01 ACME challenge handler using file-system webroot.
|
||||||
* Writes and removes the challenge file under <webroot>/.well-known/acme-challenge/.
|
* Writes and removes the challenge file under <webroot>/.well-known/acme-challenge/.
|
||||||
*/
|
*/
|
||||||
export interface Http01HandlerOptions {
|
export interface Http01WebrootOptions {
|
||||||
/**
|
/**
|
||||||
* Directory that serves HTTP requests for /.well-known/acme-challenge
|
* Directory that serves HTTP requests for /.well-known/acme-challenge
|
||||||
*/
|
*/
|
||||||
webroot: string;
|
webroot: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Http01Handler implements IChallengeHandler<{
|
export class Http01Webroot implements IChallengeHandler<{
|
||||||
type: string;
|
type: string;
|
||||||
token: string;
|
token: string;
|
||||||
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: Http01HandlerOptions) {
|
constructor(options: Http01WebrootOptions) {
|
||||||
this.webroot = options.webroot;
|
this.webroot = options.webroot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,10 +33,10 @@ export class Http01Handler 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 Http01Handler 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;
|
||||||
|
}
|
||||||
}
|
}
|
83
ts/handlers/Http01MemoryHandler.ts
Normal file
83
ts/handlers/Http01MemoryHandler.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import type { IChallengeHandler } from './IChallengeHandler.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP-01 ACME challenge handler using in-memory storage.
|
||||||
|
* Stores challenge tokens and key authorizations in memory
|
||||||
|
* and serves them via handleRequest for arbitrary HTTP servers.
|
||||||
|
*/
|
||||||
|
export interface Http01MemoryHandlerChallenge {
|
||||||
|
type: string;
|
||||||
|
token: string;
|
||||||
|
keyAuthorization: string;
|
||||||
|
webPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
public getSupportedTypes(): string[] {
|
||||||
|
return ['http-01'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async prepare(ch: Http01MemoryHandlerChallenge): Promise<void> {
|
||||||
|
this.store.set(ch.token, ch.keyAuthorization);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async verify(_ch: Http01MemoryHandlerChallenge): Promise<void> {
|
||||||
|
// No-op
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async cleanup(ch: Http01MemoryHandlerChallenge): Promise<void> {
|
||||||
|
this.store.delete(ch.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP request handler for serving ACME HTTP-01 challenges.
|
||||||
|
* @param req HTTP request object (should have url property)
|
||||||
|
* @param res HTTP response object
|
||||||
|
* @param next Optional next() callback for Express-style fallthrough
|
||||||
|
*/
|
||||||
|
public handleRequest(req: any, res: any, next?: () => void): void {
|
||||||
|
const url = req.url || '';
|
||||||
|
const prefix = '/.well-known/acme-challenge/';
|
||||||
|
if (!url.startsWith(prefix)) {
|
||||||
|
if (next) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
res.statusCode = 404;
|
||||||
|
return res.end();
|
||||||
|
}
|
||||||
|
const token = url.slice(prefix.length);
|
||||||
|
const keyAuth = this.store.get(token);
|
||||||
|
if (keyAuth !== undefined) {
|
||||||
|
if (typeof res.status === 'function' && typeof res.send === 'function') {
|
||||||
|
return res.status(200).send(keyAuth);
|
||||||
|
}
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader('content-type', 'text/plain');
|
||||||
|
return res.end(keyAuth);
|
||||||
|
}
|
||||||
|
if (next) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
res.statusCode = 404;
|
||||||
|
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,5 @@
|
|||||||
export type { IChallengeHandler } from './IChallengeHandler.js';
|
export type { IChallengeHandler } from './IChallengeHandler.js';
|
||||||
// Removed legacy handler adapter
|
// Removed legacy handler adapter
|
||||||
export { Dns01Handler } from './Dns01Handler.js';
|
export { Dns01Handler } from './Dns01Handler.js';
|
||||||
export { Http01Handler } from './Http01Handler.js';
|
export { Http01Webroot } from './Http01Handler.js';
|
||||||
|
export { Http01MemoryHandler } from './Http01MemoryHandler.js';
|
@ -1,2 +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';
|
||||||
|
|
||||||
|
// certmanagers
|
||||||
|
import * as certmanagers from './certmanagers/index.js';
|
||||||
|
export { certmanagers };
|
||||||
|
|
||||||
|
// handlers
|
||||||
|
import * as handlers from './handlers/index.js';
|
||||||
|
export { handlers };
|
||||||
|
36
ts/interfaces/certmanager.ts
Normal file
36
ts/interfaces/certmanager.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import type { SmartacmeCert } from '../smartacme.classes.cert.js';
|
||||||
|
|
||||||
|
// (ICertRecord removed; use SmartacmeCert directly)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for certificate storage managers.
|
||||||
|
* Users can implement this to provide custom persistence (in-memory,
|
||||||
|
* file-based, Redis, etc.).
|
||||||
|
*/
|
||||||
|
export interface ICertManager {
|
||||||
|
/**
|
||||||
|
* Initialize the store (e.g., connect to database).
|
||||||
|
*/
|
||||||
|
init(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Retrieve a certificate record by domain name.
|
||||||
|
* Returns null if none found.
|
||||||
|
*/
|
||||||
|
retrieveCertificate(domainName: string): Promise<SmartacmeCert | null>;
|
||||||
|
/**
|
||||||
|
* Store a certificate record. Fulfills any pending interests.
|
||||||
|
*/
|
||||||
|
storeCertificate(cert: SmartacmeCert): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Delete a certificate record by domain name.
|
||||||
|
*/
|
||||||
|
deleteCertificate(domainName: string): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Close the store (e.g., disconnect database).
|
||||||
|
*/
|
||||||
|
close(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Optional: wipe all stored certificates (e.g., for integration testing)
|
||||||
|
*/
|
||||||
|
wipe(): Promise<void>;
|
||||||
|
}
|
@ -1,3 +1,14 @@
|
|||||||
|
// node native
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export { fs, path };
|
||||||
|
|
||||||
|
// @apiclient.xyz scope
|
||||||
|
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||||
|
|
||||||
|
export { cloudflare };
|
||||||
|
|
||||||
// @apiglobal scope
|
// @apiglobal scope
|
||||||
import * as typedserver from '@api.global/typedserver';
|
import * as typedserver from '@api.global/typedserver';
|
||||||
|
|
||||||
@ -8,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';
|
||||||
@ -20,7 +33,9 @@ export {
|
|||||||
smartdata,
|
smartdata,
|
||||||
smartdelay,
|
smartdelay,
|
||||||
smartdnsClient,
|
smartdnsClient,
|
||||||
|
smartfile,
|
||||||
smartlog,
|
smartlog,
|
||||||
|
smartnetwork,
|
||||||
smartpromise,
|
smartpromise,
|
||||||
smartrequest,
|
smartrequest,
|
||||||
smartunique,
|
smartunique,
|
@ -1,64 +1,39 @@
|
|||||||
import * as plugins from './smartacme.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
import * as interfaces from './interfaces/index.js';
|
/**
|
||||||
|
* Plain certificate record.
|
||||||
import { SmartacmeCertManager } from './smartacme.classes.certmanager.js';
|
*/
|
||||||
|
export class SmartacmeCert {
|
||||||
import { Collection, svDb, unI } from '@push.rocks/smartdata';
|
|
||||||
|
|
||||||
@plugins.smartdata.Collection(() => {
|
|
||||||
return SmartacmeCertManager.activeDB;
|
|
||||||
})
|
|
||||||
export class SmartacmeCert
|
|
||||||
extends plugins.smartdata.SmartDataDbDoc<SmartacmeCert, plugins.tsclass.network.ICert>
|
|
||||||
implements plugins.tsclass.network.ICert
|
|
||||||
{
|
|
||||||
@unI()
|
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
@svDb()
|
|
||||||
public domainName: string;
|
public domainName: string;
|
||||||
|
|
||||||
@svDb()
|
|
||||||
public created: number;
|
public created: number;
|
||||||
|
|
||||||
@svDb()
|
|
||||||
public privateKey: string;
|
public privateKey: string;
|
||||||
|
|
||||||
@svDb()
|
|
||||||
public publicKey: string;
|
public publicKey: string;
|
||||||
|
|
||||||
@svDb()
|
|
||||||
public csr: string;
|
public csr: string;
|
||||||
|
|
||||||
@svDb()
|
|
||||||
public validUntil: number;
|
public validUntil: number;
|
||||||
|
|
||||||
|
constructor(data: Partial<SmartacmeCert> = {}) {
|
||||||
|
this.id = data.id || '';
|
||||||
|
this.domainName = data.domainName || '';
|
||||||
|
this.created = data.created || Date.now();
|
||||||
|
this.privateKey = data.privateKey || '';
|
||||||
|
this.publicKey = data.publicKey || '';
|
||||||
|
this.csr = data.csr || '';
|
||||||
|
this.validUntil = data.validUntil || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if certificate is still valid.
|
||||||
|
*/
|
||||||
public isStillValid(): boolean {
|
public isStillValid(): boolean {
|
||||||
return this.validUntil >= Date.now();
|
return this.validUntil >= Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if certificate needs renewal (e.g., expires in <10 days).
|
||||||
|
*/
|
||||||
public shouldBeRenewed(): boolean {
|
public shouldBeRenewed(): boolean {
|
||||||
const shouldBeValidAtLeastUntil =
|
const threshold = Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ days: 10 });
|
||||||
Date.now() +
|
return this.validUntil < threshold;
|
||||||
plugins.smarttime.getMilliSecondsFromUnits({
|
|
||||||
days: 10,
|
|
||||||
});
|
|
||||||
return !(this.validUntil >= shouldBeValidAtLeastUntil);
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(certDataArg: plugins.tsclass.network.ICert) {
|
|
||||||
Object.keys(certDataArg).forEach((key) => {
|
|
||||||
this[key] = certDataArg[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(optionsArg: plugins.tsclass.network.ICert) {
|
|
||||||
super();
|
|
||||||
if (optionsArg) {
|
|
||||||
Object.keys(optionsArg).forEach((key) => {
|
|
||||||
this[key] = optionsArg[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
import * as plugins from './smartacme.plugins.js';
|
|
||||||
import { SmartacmeCert } from './smartacme.classes.cert.js';
|
|
||||||
import { SmartAcme } from './smartacme.classes.smartacme.js';
|
|
||||||
|
|
||||||
import * as interfaces from './interfaces/index.js';
|
|
||||||
|
|
||||||
export class SmartacmeCertManager {
|
|
||||||
// =========
|
|
||||||
// STATIC
|
|
||||||
// =========
|
|
||||||
public static activeDB: plugins.smartdata.SmartdataDb;
|
|
||||||
|
|
||||||
// =========
|
|
||||||
// INSTANCE
|
|
||||||
// =========
|
|
||||||
private mongoDescriptor: plugins.smartdata.IMongoDescriptor;
|
|
||||||
public smartdataDb: plugins.smartdata.SmartdataDb;
|
|
||||||
|
|
||||||
public interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
smartAcmeArg: SmartAcme,
|
|
||||||
optionsArg: {
|
|
||||||
mongoDescriptor: plugins.smartdata.IMongoDescriptor;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
this.mongoDescriptor = optionsArg.mongoDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async init() {
|
|
||||||
// Smartdata DB
|
|
||||||
this.smartdataDb = new plugins.smartdata.SmartdataDb(this.mongoDescriptor);
|
|
||||||
await this.smartdataDb.init();
|
|
||||||
SmartacmeCertManager.activeDB = this.smartdataDb;
|
|
||||||
|
|
||||||
// Pending Map
|
|
||||||
this.interestMap = new plugins.lik.InterestMap((certName) => certName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* retrieves a certificate
|
|
||||||
* @returns the Cert class or null
|
|
||||||
* @param certDomainNameArg the domain Name to retrieve the vcertificate for
|
|
||||||
*/
|
|
||||||
public async retrieveCertificate(certDomainNameArg: string): Promise<SmartacmeCert> {
|
|
||||||
const existingCertificate: SmartacmeCert = await SmartacmeCert.getInstance<SmartacmeCert>({
|
|
||||||
domainName: certDomainNameArg,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingCertificate) {
|
|
||||||
return existingCertificate;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* stores the certificate
|
|
||||||
* @param optionsArg
|
|
||||||
*/
|
|
||||||
public async storeCertificate(optionsArg: plugins.tsclass.network.ICert) {
|
|
||||||
const cert = new SmartacmeCert(optionsArg);
|
|
||||||
await cert.save();
|
|
||||||
const interest = this.interestMap.findInterest(cert.domainName);
|
|
||||||
if (interest) {
|
|
||||||
interest.fullfillInterest(cert);
|
|
||||||
interest.markLost();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteCertificate(certDomainNameArg: string) {
|
|
||||||
const cert: SmartacmeCert = await SmartacmeCert.getInstance<SmartacmeCert>({
|
|
||||||
domainName: certDomainNameArg,
|
|
||||||
});
|
|
||||||
await cert.delete();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,10 +10,17 @@ export class SmartacmeCertMatcher {
|
|||||||
* for wild card certificates
|
* for wild card certificates
|
||||||
* @param domainNameArg the domainNameArg to create the scope from
|
* @param domainNameArg the domainNameArg to create the scope from
|
||||||
*/
|
*/
|
||||||
public getCertificateDomainNameByDomainName(domainNameArg: string): string {
|
public getCertificateDomainNameByDomainName(domainNameArg: string): string | undefined {
|
||||||
|
// Handle wildcard domains by stripping the '*.' prefix.
|
||||||
|
if (domainNameArg.startsWith('*.')) {
|
||||||
|
return domainNameArg.slice(2);
|
||||||
|
}
|
||||||
const originalDomain = new plugins.smartstring.Domain(domainNameArg);
|
const originalDomain = new plugins.smartstring.Domain(domainNameArg);
|
||||||
|
// For domains with up to 3 levels (no level4), return base domain.
|
||||||
if (!originalDomain.level4) {
|
if (!originalDomain.level4) {
|
||||||
return `${originalDomain.level2}.${originalDomain.level1}`;
|
return `${originalDomain.level2}.${originalDomain.level1}`;
|
||||||
}
|
}
|
||||||
|
// Deeper domains (4+ levels) are not supported.
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import * as plugins from './smartacme.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import { SmartacmeCert } from './smartacme.classes.cert.js';
|
import type { ICertManager } from './interfaces/certmanager.js';
|
||||||
import { SmartacmeCertManager } from './smartacme.classes.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';
|
||||||
|
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;
|
||||||
mongoDescriptor: plugins.smartdata.IMongoDescriptor;
|
accountPrivateKey?: string;
|
||||||
|
/**
|
||||||
|
* Certificate storage manager (e.g., Mongo or in-memory).
|
||||||
|
*/
|
||||||
|
certManager: ICertManager;
|
||||||
// Removed legacy setChallenge/removeChallenge in favor of `challengeHandlers`
|
// Removed legacy setChallenge/removeChallenge in favor of `challengeHandlers`
|
||||||
environment: 'production' | 'integration';
|
environment: 'production' | 'integration';
|
||||||
/**
|
/**
|
||||||
@ -59,17 +62,21 @@ export class SmartAcme {
|
|||||||
private privateKey: string;
|
private privateKey: string;
|
||||||
|
|
||||||
|
|
||||||
// certmanager
|
// certificate manager for persistence (implements ICertManager)
|
||||||
private certmanager: SmartacmeCertManager;
|
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
|
||||||
|
private interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
|
||||||
|
|
||||||
constructor(optionsArg: ISmartAcmeOptions) {
|
constructor(optionsArg: ISmartAcmeOptions) {
|
||||||
this.options = optionsArg;
|
this.options = optionsArg;
|
||||||
@ -78,10 +85,10 @@ export class SmartAcme {
|
|||||||
this.logger.enableConsole();
|
this.logger.enableConsole();
|
||||||
// initialize retry/backoff options
|
// initialize retry/backoff options
|
||||||
this.retryOptions = {
|
this.retryOptions = {
|
||||||
retries: optionsArg.retryOptions?.retries ?? 3,
|
retries: optionsArg.retryOptions?.retries ?? 10,
|
||||||
factor: optionsArg.retryOptions?.factor ?? 2,
|
factor: optionsArg.retryOptions?.factor ?? 4,
|
||||||
minTimeoutMs: optionsArg.retryOptions?.minTimeoutMs ?? 1000,
|
minTimeoutMs: optionsArg.retryOptions?.minTimeoutMs ?? 1000,
|
||||||
maxTimeoutMs: optionsArg.retryOptions?.maxTimeoutMs ?? 30000,
|
maxTimeoutMs: optionsArg.retryOptions?.maxTimeoutMs ?? 60000,
|
||||||
};
|
};
|
||||||
// initialize challenge handlers (must provide at least one)
|
// initialize challenge handlers (must provide at least one)
|
||||||
if (!optionsArg.challengeHandlers || optionsArg.challengeHandlers.length === 0) {
|
if (!optionsArg.challengeHandlers || optionsArg.challengeHandlers.length === 0) {
|
||||||
@ -95,6 +102,8 @@ export class SmartAcme {
|
|||||||
optionsArg.challengePriority && optionsArg.challengePriority.length > 0
|
optionsArg.challengePriority && optionsArg.challengePriority.length > 0
|
||||||
? optionsArg.challengePriority
|
? optionsArg.challengePriority
|
||||||
: this.challengeHandlers.map((h) => h.getSupportedTypes()[0]);
|
: this.challengeHandlers.map((h) => h.getSupportedTypes()[0]);
|
||||||
|
// initialize interest coordination
|
||||||
|
this.interestMap = new plugins.lik.InterestMap((domain) => domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,10 +116,11 @@ export class SmartAcme {
|
|||||||
this.privateKey =
|
this.privateKey =
|
||||||
this.options.accountPrivateKey || (await plugins.acme.forge.createPrivateKey()).toString();
|
this.options.accountPrivateKey || (await plugins.acme.forge.createPrivateKey()).toString();
|
||||||
|
|
||||||
// CertMangaer
|
// Initialize certificate manager
|
||||||
this.certmanager = new SmartacmeCertManager(this, {
|
if (!this.options.certManager) {
|
||||||
mongoDescriptor: this.options.mongoDescriptor,
|
throw new Error('You must provide a certManager via options.certManager');
|
||||||
});
|
}
|
||||||
|
this.certmanager = this.options.certManager;
|
||||||
await this.certmanager.init();
|
await this.certmanager.init();
|
||||||
|
|
||||||
// CertMatcher
|
// CertMatcher
|
||||||
@ -138,8 +148,13 @@ export class SmartAcme {
|
|||||||
process.on('SIGTERM', () => this.handleSignal('SIGTERM'));
|
process.on('SIGTERM', () => this.handleSignal('SIGTERM'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the SmartAcme instance and closes certificate store connections.
|
||||||
|
*/
|
||||||
public async stop() {
|
public async stop() {
|
||||||
await this.certmanager.smartdataDb.close();
|
if (this.certmanager && typeof (this.certmanager as any).close === 'function') {
|
||||||
|
await (this.certmanager as any).close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/** Retry helper with exponential backoff */
|
/** Retry helper with exponential backoff */
|
||||||
private async retry<T>(operation: () => Promise<T>, operationName: string = 'operation'): Promise<T> {
|
private async retry<T>(operation: () => Promise<T>, operationName: string = 'operation'): Promise<T> {
|
||||||
@ -208,24 +223,41 @@ export class SmartAcme {
|
|||||||
* @param domainArg
|
* @param domainArg
|
||||||
*/
|
*/
|
||||||
public async getCertificateForDomain(domainArg: string): Promise<SmartacmeCert> {
|
public async getCertificateForDomain(domainArg: string): 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.
|
||||||
const certDomainName = this.certmatcher.getCertificateDomainNameByDomainName(domainArg);
|
const certDomainName = this.certmatcher.getCertificateDomainNameByDomainName(domainArg);
|
||||||
|
if (!certDomainName) {
|
||||||
|
throw new Error(`Cannot determine certificate domain for ${domainArg}`);
|
||||||
|
}
|
||||||
|
// Wildcard certificates require DNS-01 challenge support.
|
||||||
|
if (isWildcardRequest) {
|
||||||
|
const hasDnsHandler = this.challengeHandlers.some((h) =>
|
||||||
|
h.getSupportedTypes().includes('dns-01'),
|
||||||
|
);
|
||||||
|
if (!hasDnsHandler) {
|
||||||
|
throw new Error('Wildcard certificate requests require a DNS-01 challenge handler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Retrieve any existing certificate record by base domain.
|
||||||
const retrievedCertificate = await this.certmanager.retrieveCertificate(certDomainName);
|
const retrievedCertificate = await this.certmanager.retrieveCertificate(certDomainName);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!retrievedCertificate &&
|
!retrievedCertificate &&
|
||||||
(await this.certmanager.interestMap.checkInterest(certDomainName))
|
(await this.interestMap.checkInterest(certDomainName))
|
||||||
) {
|
) {
|
||||||
const existingCertificateInterest = this.certmanager.interestMap.findInterest(certDomainName);
|
const existingCertificateInterest = this.interestMap.findInterest(certDomainName);
|
||||||
const certificate = existingCertificateInterest.interestFullfilled;
|
const certificate = existingCertificateInterest.interestFullfilled;
|
||||||
return certificate;
|
return certificate;
|
||||||
} else if (retrievedCertificate && !retrievedCertificate.shouldBeRenewed()) {
|
} else if (retrievedCertificate && !retrievedCertificate.shouldBeRenewed()) {
|
||||||
return retrievedCertificate;
|
return retrievedCertificate;
|
||||||
} else if (retrievedCertificate && retrievedCertificate.shouldBeRenewed()) {
|
} else if (retrievedCertificate && retrievedCertificate.shouldBeRenewed()) {
|
||||||
await retrievedCertificate.delete();
|
// Remove old certificate via certManager
|
||||||
|
await this.certmanager.deleteCertificate(certDomainName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// lets make sure others get the same interest
|
// lets make sure others get the same interest
|
||||||
const currentDomainInterst = await this.certmanager.interestMap.addInterest(certDomainName);
|
const currentDomainInterst = await this.interestMap.addInterest(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({
|
||||||
@ -277,15 +309,45 @@ export class SmartAcme {
|
|||||||
}
|
}
|
||||||
this.pendingChallenges.push(input);
|
this.pendingChallenges.push(input);
|
||||||
try {
|
try {
|
||||||
|
// Prepare the challenge (set DNS record, write file, etc.)
|
||||||
await this.retry(() => handler.prepare(input), `${type}.prepare`);
|
await this.retry(() => handler.prepare(input), `${type}.prepare`);
|
||||||
if (handler.verify) {
|
// For DNS-01, wait for propagation before verification
|
||||||
await this.retry(() => handler.verify!(input), `${type}.verify`);
|
if (type === 'dns-01') {
|
||||||
} else {
|
const dnsInput = input as { hostName: string; challenge: string };
|
||||||
await this.retry(() => this.client.verifyChallenge(authz, selectedChallengeArg), `${type}.verifyChallenge`);
|
// Wait for authoritative DNS propagation before ACME verify
|
||||||
|
await this.retry(
|
||||||
|
() => this.smartdns.checkUntilAvailable(dnsInput.hostName, 'TXT', dnsInput.challenge, 100, 5000),
|
||||||
|
`${type}.propagation`,
|
||||||
|
);
|
||||||
|
// Extra cool-down to ensure ACME server sees the new TXT record
|
||||||
|
this.logger.log('info', 'Cooling down for 1 minute before ACME verification');
|
||||||
|
await plugins.smartdelay.delayFor(60000);
|
||||||
|
}
|
||||||
|
// Official ACME verification (ensures challenge is publicly reachable)
|
||||||
|
await this.retry(
|
||||||
|
() => this.client.verifyChallenge(authz, selectedChallengeArg),
|
||||||
|
`${type}.verifyChallenge`,
|
||||||
|
);
|
||||||
|
// Notify ACME server to complete the challenge
|
||||||
|
await this.retry(
|
||||||
|
() => this.client.completeChallenge(selectedChallengeArg),
|
||||||
|
`${type}.completeChallenge`,
|
||||||
|
);
|
||||||
|
// Wait for valid status (warnings on staging timeouts)
|
||||||
|
try {
|
||||||
|
await this.retry(
|
||||||
|
() => this.client.waitForValidStatus(selectedChallengeArg),
|
||||||
|
`${type}.waitForValidStatus`,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
await this.logger.log(
|
||||||
|
'warn',
|
||||||
|
`Challenge ${type} did not reach valid status in time, proceeding to finalize`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await this.retry(() => this.client.completeChallenge(selectedChallengeArg), `${type}.completeChallenge`);
|
|
||||||
await this.retry(() => this.client.waitForValidStatus(selectedChallengeArg), `${type}.waitForValidStatus`);
|
|
||||||
} finally {
|
} finally {
|
||||||
|
// Always cleanup resource
|
||||||
try {
|
try {
|
||||||
await this.retry(() => handler.cleanup(input), `${type}.cleanup`);
|
await this.retry(() => handler.cleanup(input), `${type}.cleanup`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -307,19 +369,17 @@ export class SmartAcme {
|
|||||||
|
|
||||||
/* Done */
|
/* Done */
|
||||||
|
|
||||||
await this.certmanager.storeCertificate({
|
// Store the new certificate record
|
||||||
|
const certRecord = new SmartacmeCert({
|
||||||
id: plugins.smartunique.shortId(),
|
id: plugins.smartunique.shortId(),
|
||||||
domainName: certDomainName,
|
domainName: certDomainName,
|
||||||
privateKey: key.toString(),
|
privateKey: key.toString(),
|
||||||
publicKey: cert.toString(),
|
publicKey: cert.toString(),
|
||||||
csr: csr.toString(),
|
csr: csr.toString(),
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
validUntil:
|
validUntil: Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ days: 90 }),
|
||||||
Date.now() +
|
|
||||||
plugins.smarttime.getMilliSecondsFromUnits({
|
|
||||||
days: 90,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
await this.certmanager.storeCertificate(certRecord);
|
||||||
|
|
||||||
const newCertificate = await this.certmanager.retrieveCertificate(certDomainName);
|
const newCertificate = await this.certmanager.retrieveCertificate(certDomainName);
|
||||||
currentDomainInterst.fullfillInterest(newCertificate);
|
currentDomainInterst.fullfillInterest(newCertificate);
|
||||||
@ -327,7 +387,4 @@ export class SmartAcme {
|
|||||||
return newCertificate;
|
return newCertificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllCertificates(): Promise<SmartacmeCert[]> {
|
|
||||||
return SmartacmeCert.getInstances({});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user