@push.rocks/smartacme 8.0.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/acme/acme.classes.account.d.ts +20 -0
  3. package/dist_ts/acme/acme.classes.account.js +37 -0
  4. package/dist_ts/acme/acme.classes.challenge.d.ts +22 -0
  5. package/dist_ts/acme/acme.classes.challenge.js +36 -0
  6. package/dist_ts/acme/acme.classes.client.d.ts +72 -0
  7. package/dist_ts/acme/acme.classes.client.js +77 -0
  8. package/dist_ts/acme/acme.classes.crypto.d.ts +50 -0
  9. package/dist_ts/acme/acme.classes.crypto.js +180 -0
  10. package/dist_ts/acme/acme.classes.directory.d.ts +13 -0
  11. package/dist_ts/acme/acme.classes.directory.js +14 -0
  12. package/dist_ts/acme/acme.classes.error.d.ts +44 -0
  13. package/dist_ts/acme/acme.classes.error.js +41 -0
  14. package/dist_ts/acme/acme.classes.http-client.d.ts +42 -0
  15. package/dist_ts/acme/acme.classes.http-client.js +211 -0
  16. package/dist_ts/acme/acme.classes.order.d.ts +40 -0
  17. package/dist_ts/acme/acme.classes.order.js +100 -0
  18. package/dist_ts/acme/acme.interfaces.d.ts +64 -0
  19. package/dist_ts/acme/acme.interfaces.js +5 -0
  20. package/dist_ts/acme/index.d.ts +5 -0
  21. package/dist_ts/acme/index.js +5 -0
  22. package/dist_ts/certmanagers/mongo.js +1 -2
  23. package/dist_ts/plugins.d.ts +2 -7
  24. package/dist_ts/plugins.js +5 -11
  25. package/dist_ts/smartacme.classes.smartacme.d.ts +3 -1
  26. package/dist_ts/smartacme.classes.smartacme.js +48 -13
  27. package/npmextra.json +12 -6
  28. package/package.json +23 -24
  29. package/readme.hints.md +27 -3
  30. package/readme.md +245 -267
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/acme/acme.classes.account.ts +45 -0
  33. package/ts/acme/acme.classes.challenge.ts +45 -0
  34. package/ts/acme/acme.classes.client.ts +106 -0
  35. package/ts/acme/acme.classes.crypto.ts +220 -0
  36. package/ts/acme/acme.classes.directory.ts +13 -0
  37. package/ts/acme/acme.classes.error.ts +55 -0
  38. package/ts/acme/acme.classes.http-client.ts +249 -0
  39. package/ts/acme/acme.classes.order.ts +125 -0
  40. package/ts/acme/acme.interfaces.ts +74 -0
  41. package/ts/acme/index.ts +16 -0
  42. package/ts/certmanagers/mongo.ts +0 -1
  43. package/ts/plugins.ts +3 -14
  44. package/ts/smartacme.classes.smartacme.ts +49 -16
  45. package/dist_ts/certmanagers.d.ts +0 -42
  46. package/dist_ts/certmanagers.js +0 -86
  47. package/dist_ts/smartacme.classes.certmanager.d.ts +0 -43
  48. package/dist_ts/smartacme.classes.certmanager.js +0 -92
  49. package/dist_ts/smartacme.plugins.d.ts +0 -21
  50. package/dist_ts/smartacme.plugins.js +0 -28
package/readme.md CHANGED
@@ -1,361 +1,339 @@
1
1
  # @push.rocks/smartacme
2
2
 
3
- A TypeScript-based ACME client with an easy yet powerful interface for LetsEncrypt certificate management.
3
+ A TypeScript-based ACME client for Let's Encrypt certificate management with a focus on simplicity and power. 🔒
4
4
 
5
- ## Install
5
+ ## Issue Reporting and Security
6
+
7
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
6
8
 
7
- Using pnpm as the package manager:
9
+ ## Install
8
10
 
9
11
  ```bash
10
12
  pnpm add @push.rocks/smartacme
11
13
  ```
12
14
 
13
- Ensure your project is set up to use TypeScript and ECMAScript Modules (ESM).
14
- ## Running Tests
15
+ Ensure your project uses TypeScript and ECMAScript Modules (ESM).
15
16
 
16
- Tests are written using `@push.rocks/tapbundle` and can be run with:
17
+ ## Usage
17
18
 
18
- ```bash
19
- pnpm test
19
+ `@push.rocks/smartacme` automates the full ACME certificate lifecycle — obtaining, renewing, and storing SSL/TLS certificates from Let's Encrypt. It features a built-in RFC 8555-compliant ACME protocol implementation, pluggable challenge handlers (DNS-01, HTTP-01), pluggable certificate storage backends (MongoDB, in-memory, or your own), and structured error handling with smart retry logic.
20
+
21
+ ### 🚀 Quick Start
22
+
23
+ ```typescript
24
+ import { SmartAcme, certmanagers, handlers } from '@push.rocks/smartacme';
25
+ import * as cloudflare from '@apiclient.xyz/cloudflare';
26
+
27
+ // 1. Set up a certificate manager (MongoDB or in-memory)
28
+ const certManager = new certmanagers.MongoCertManager({
29
+ mongoDbUrl: 'mongodb://localhost:27017',
30
+ mongoDbName: 'myapp',
31
+ mongoDbPass: 'secret',
32
+ });
33
+
34
+ // 2. Set up challenge handlers
35
+ const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_API_TOKEN');
36
+ const dnsHandler = new handlers.Dns01Handler(cfAccount);
37
+
38
+ // 3. Create and start SmartAcme
39
+ const smartAcme = new SmartAcme({
40
+ accountEmail: 'admin@example.com',
41
+ certManager,
42
+ environment: 'production', // or 'integration' for staging
43
+ challengeHandlers: [dnsHandler],
44
+ });
45
+
46
+ await smartAcme.start();
47
+
48
+ // 4. Get a certificate
49
+ const cert = await smartAcme.getCertificateForDomain('example.com');
50
+ console.log(cert.publicKey); // PEM certificate chain
51
+ console.log(cert.privateKey); // PEM private key
52
+
53
+ // 5. Clean up
54
+ await smartAcme.stop();
20
55
  ```
21
56
 
22
- To run a specific test file:
57
+ ### ⚙️ SmartAcme Options
23
58
 
24
- ```bash
25
- tsx test/<test-file>.ts
59
+ ```typescript
60
+ interface ISmartAcmeOptions {
61
+ accountEmail: string; // ACME account email
62
+ accountPrivateKey?: string; // Optional account key (auto-generated if omitted)
63
+ certManager: ICertManager; // Certificate storage backend
64
+ environment: 'production' | 'integration'; // Let's Encrypt environment
65
+ challengeHandlers: IChallengeHandler[]; // At least one handler required
66
+ challengePriority?: string[]; // e.g. ['dns-01', 'http-01']
67
+ retryOptions?: { // Optional retry/backoff config
68
+ retries?: number; // Default: 10
69
+ factor?: number; // Default: 4
70
+ minTimeoutMs?: number; // Default: 1000
71
+ maxTimeoutMs?: number; // Default: 60000
72
+ };
73
+ }
26
74
  ```
27
75
 
28
- ## Usage
76
+ ### 📜 Getting Certificates
77
+
78
+ ```typescript
79
+ // Standard certificate for a single domain
80
+ const cert = await smartAcme.getCertificateForDomain('example.com');
29
81
 
30
- This guide will walk you through using `@push.rocks/smartacme` to set up and manage ACME (Automated Certificate Management Environment) certificates with a focus on the Let's Encrypt service, which provides free SSL certificates. The library provides an easy yet powerful TypeScript interface to automate the process of obtaining, renewing, and installing your SSL certificates.
82
+ // Include wildcard coverage (requires DNS-01 handler)
83
+ // Issues a single cert covering example.com AND *.example.com
84
+ const certWithWildcard = await smartAcme.getCertificateForDomain('example.com', {
85
+ includeWildcard: true,
86
+ });
31
87
 
32
- ### Table of Contents
88
+ // Request wildcard only
89
+ const wildcardCert = await smartAcme.getCertificateForDomain('*.example.com');
90
+ ```
33
91
 
34
- 1. [Setting Up Your Project](#setting-up-your-project)
35
- 2. [Creating a SmartAcme Instance](#creating-a-smartacme-instance)
36
- 3. [Initializing SmartAcme](#initializing-smartacme)
37
- 4. [Obtaining a Certificate for a Domain](#obtaining-a-certificate-for-a-domain)
38
- 5. [Automating DNS Challenges](#automating-dns-challenges)
39
- 6. [Managing Certificates](#managing-certificates)
40
- 7. [Environmental Considerations](#environmental-considerations)
41
- 8. [Complete Example](#complete-example)
92
+ Certificates are automatically cached and reused when still valid. Renewal happens automatically when a certificate is within 10 days of expiration.
42
93
 
43
- ### Setting Up Your Project
94
+ ### 📦 Certificate Object
44
95
 
45
- Ensure your project includes the necessary TypeScript configuration and dependencies. You'll need to have TypeScript installed and configured for ECMAScript Modules. If you are new to TypeScript, review its [documentation](https://www.typescriptlang.org/docs/) to get started.
96
+ The returned `SmartacmeCert` (also exported as `Cert`) object has these properties:
46
97
 
47
- ### Creating a SmartAcme Instance
98
+ | Property | Type | Description |
99
+ |-------------|----------|--------------------------------------|
100
+ | `id` | `string` | Unique certificate identifier |
101
+ | `domainName`| `string` | Domain the cert is issued for |
102
+ | `publicKey` | `string` | PEM-encoded certificate chain |
103
+ | `privateKey`| `string` | PEM-encoded private key |
104
+ | `csr` | `string` | Certificate Signing Request |
105
+ | `created` | `number` | Timestamp of creation |
106
+ | `validUntil`| `number` | Timestamp of expiration |
48
107
 
49
- Start by importing the `SmartAcme` class and any built-in handlers you plan to use. For example, to use DNS-01 via Cloudflare:
108
+ Useful methods:
50
109
 
51
110
  ```typescript
52
- import { SmartAcme, MongoCertManager } from '@push.rocks/smartacme';
53
- import * as cloudflare from '@apiclient.xyz/cloudflare';
54
- import { Dns01Handler } from '@push.rocks/smartacme/ts/handlers/Dns01Handler.js';
111
+ cert.isStillValid(); // true if not expired
112
+ cert.shouldBeRenewed(); // true if expires within 10 days
113
+ ```
55
114
 
56
- // Create a Cloudflare account client with your API token
57
- const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_TOKEN');
115
+ ## Certificate Managers
58
116
 
59
- // Initialize a certificate manager (e.g., MongoDB)
60
- const certManager = new MongoCertManager({
61
- mongoDbUrl: 'mongodb://yourmongoURL',
62
- mongoDbName: 'yourDbName',
63
- mongoDbPass: 'yourDbPassword',
64
- });
117
+ SmartAcme uses the `ICertManager` interface for pluggable certificate storage.
65
118
 
66
- // Instantiate SmartAcme with the certManager and challenge handlers
67
- const smartAcmeInstance = new SmartAcme({
68
- accountEmail: 'youremail@example.com',
69
- certManager,
70
- environment: 'integration', // 'production' to request real certificates
71
- retryOptions: {}, // optional retry/backoff settings
72
- challengeHandlers: [ // pluggable ACME challenge handlers
73
- new Dns01Handler(cfAccount),
74
- // add more handlers as needed (e.g., Http01Webroot, Http01MemoryHandler)
75
- ],
76
- challengePriority: ['dns-01'], // optional challenge ordering
119
+ ### 🗄️ MongoCertManager
120
+
121
+ Persistent storage backed by MongoDB using `@push.rocks/smartdata`:
122
+
123
+ ```typescript
124
+ import { certmanagers } from '@push.rocks/smartacme';
125
+
126
+ const certManager = new certmanagers.MongoCertManager({
127
+ mongoDbUrl: 'mongodb://localhost:27017',
128
+ mongoDbName: 'myapp',
129
+ mongoDbPass: 'secret',
77
130
  });
78
131
  ```
79
132
 
80
- ### Initializing SmartAcme
133
+ ### 🧪 MemoryCertManager
81
134
 
82
- Before proceeding to request certificates, start your SmartAcme instance:
135
+ In-memory storage, ideal for testing or ephemeral workloads:
83
136
 
84
137
  ```typescript
85
- await smartAcmeInstance.start();
138
+ import { certmanagers } from '@push.rocks/smartacme';
139
+
140
+ const certManager = new certmanagers.MemoryCertManager();
86
141
  ```
87
142
 
88
- ### Obtaining a Certificate for a Domain
143
+ ### 🔧 Custom Certificate Manager
89
144
 
90
- To obtain a certificate for a specific domain, use the `getCertificateForDomain` method. This function ensures that if a valid certificate is already present, it will be reused; otherwise, a new certificate is obtained:
145
+ Implement the `ICertManager` interface for your own storage backend:
91
146
 
92
147
  ```typescript
93
- const myDomain = 'example.com';
94
- const myCert = await smartAcmeInstance.getCertificateForDomain(myDomain);
95
- console.log('Certificate:', myCert);
148
+ import type { ICertManager, Cert } from '@push.rocks/smartacme';
149
+
150
+ class RedisCertManager implements ICertManager {
151
+ async init(): Promise<void> { /* connect */ }
152
+ async retrieveCertificate(domainName: string): Promise<Cert | null> { /* lookup */ }
153
+ async storeCertificate(cert: Cert): Promise<void> { /* save */ }
154
+ async deleteCertificate(domainName: string): Promise<void> { /* remove */ }
155
+ async close(): Promise<void> { /* disconnect */ }
156
+ async wipe(): Promise<void> { /* clear all */ }
157
+ }
96
158
  ```
97
159
 
98
- ### Automating DNS Challenges
160
+ ## Challenge Handlers
161
+
162
+ SmartAcme ships with three built-in ACME challenge handlers. All implement `IChallengeHandler<T>`.
99
163
 
100
- SmartAcme uses pluggable ACME challenge handlers (see built-in handlers below) to automate domain validation. You configure handlers via the `challengeHandlers` array when creating the instance, and SmartAcme will invoke each handler’s `prepare`, optional `verify`, and `cleanup` methods during the ACME order flow.
164
+ ### 🌐 Dns01Handler
101
165
 
102
- ### Managing Certificates
166
+ Uses Cloudflare (or any `IConvenientDnsProvider`) to set and remove DNS TXT records for `dns-01` challenges:
103
167
 
104
- 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.
168
+ ```typescript
169
+ import { handlers } from '@push.rocks/smartacme';
170
+ import * as cloudflare from '@apiclient.xyz/cloudflare';
171
+
172
+ const cfAccount = new cloudflare.CloudflareAccount('YOUR_CF_TOKEN');
173
+ const dnsHandler = new handlers.Dns01Handler(cfAccount);
174
+ ```
175
+
176
+ DNS-01 is **required** for wildcard certificates and works regardless of server accessibility.
177
+
178
+ ### 📁 Http01Webroot
179
+
180
+ Writes challenge response files to a filesystem webroot for `http-01` validation:
105
181
 
106
182
  ```typescript
107
- import { MongoCertManager } from '@push.rocks/smartacme';
183
+ import { handlers } from '@push.rocks/smartacme';
108
184
 
109
- const certManager = new MongoCertManager({
110
- mongoDbUrl: 'mongodb://yourmongoURL',
111
- mongoDbName: 'yourDbName',
112
- mongoDbPass: 'yourDbPassword',
185
+ const httpHandler = new handlers.Http01Webroot({
186
+ webroot: '/var/www/html',
113
187
  });
114
188
  ```
115
189
 
116
- SmartAcme uses the `ICertManager` interface for certificate storage. Two built-in implementations are available:
190
+ The handler writes to `<webroot>/.well-known/acme-challenge/<token>` and cleans up after validation.
191
+
192
+ ### 🧠 Http01MemoryHandler
193
+
194
+ In-memory HTTP-01 handler — stores challenge tokens in memory and serves them via `handleRequest()`:
117
195
 
118
- - **MemoryCertManager**
119
- - In-memory storage, suitable for testing or ephemeral use.
120
- - Import example:
121
- ```typescript
122
- import { MemoryCertManager } from '@push.rocks/smartacme';
123
- const certManager = new MemoryCertManager();
124
- ```
196
+ ```typescript
197
+ import { handlers } from '@push.rocks/smartacme';
198
+
199
+ const memHandler = new handlers.Http01MemoryHandler();
125
200
 
126
- - **MongoCertManager**
127
- - Persistent storage in MongoDB (collection: `SmartacmeCert`).
128
- - Import example:
129
- ```typescript
130
- import { MongoCertManager } from '@push.rocks/smartacme';
131
- const certManager = new MongoCertManager({
132
- mongoDbUrl: 'mongodb://yourmongoURL',
133
- mongoDbName: 'yourDbName',
134
- mongoDbPass: 'yourDbPassword',
135
- });
136
- ```
201
+ // Integrate with any HTTP server (Express, Koa, raw http, etc.)
202
+ app.use((req, res, next) => memHandler.handleRequest(req, res, next));
203
+ ```
137
204
 
138
- #### Custom Certificate Managers
205
+ Perfect for serverless or container environments where filesystem access is limited.
139
206
 
140
- To implement a custom certificate manager, implement the `ICertManager` interface and pass it to `SmartAcme`:
207
+ ### 🔧 Custom Challenge Handler
208
+
209
+ Implement `IChallengeHandler<T>` for custom challenge types:
141
210
 
142
211
  ```typescript
143
- import type { ICertManager, Cert as SmartacmeCert } from '@push.rocks/smartacme';
144
- import { SmartAcme } from '@push.rocks/smartacme';
145
-
146
- class MyCustomCertManager implements ICertManager {
147
- async init(): Promise<void> { /* setup storage */ }
148
- async get(domainName: string): Promise<SmartacmeCert | null> { /* lookup cert */ }
149
- async put(cert: SmartacmeCert): Promise<SmartacmeCert> { /* store cert */ }
150
- async delete(domainName: string): Promise<void> { /* remove cert */ }
151
- async close?(): Promise<void> { /* optional cleanup */ }
212
+ import type { handlers } from '@push.rocks/smartacme';
213
+
214
+ interface MyChallenge {
215
+ type: string;
216
+ token: string;
217
+ keyAuthorization: string;
152
218
  }
153
219
 
154
- // Use your custom manager:
155
- const customManager = new MyCustomCertManager();
156
- const smartAcme = new SmartAcme({
157
- accountEmail: 'youremail@example.com',
158
- certManager: customManager,
159
- environment: 'integration',
160
- challengeHandlers: [], // add your handlers
161
- });
220
+ class MyHandler implements handlers.IChallengeHandler<MyChallenge> {
221
+ getSupportedTypes(): string[] { return ['http-01']; }
222
+ async prepare(ch: MyChallenge): Promise<void> { /* set up challenge response */ }
223
+ async cleanup(ch: MyChallenge): Promise<void> { /* tear down */ }
224
+ async checkWetherDomainIsSupported(domain: string): Promise<boolean> { return true; }
225
+ }
162
226
  ```
163
227
 
164
- ### Environmental Considerations
228
+ ## Error Handling
165
229
 
166
- When creating an instance of `SmartAcme`, you can specify an `environment` option. This is particularly useful for testing, as you can use the `integration` environment to avoid hitting rate limits and for testing your setup without issuing real certificates. Switch to `production` when you are ready to obtain actual certificates.
230
+ SmartAcme provides structured ACME error handling via the `AcmeError` class, which carries full RFC 8555 error information:
167
231
 
168
- ### Complete Example
232
+ ```typescript
233
+ import { AcmeError } from '@push.rocks/smartacme/ts/acme/acme.classes.error.js';
234
+
235
+ try {
236
+ const cert = await smartAcme.getCertificateForDomain('example.com');
237
+ } catch (err) {
238
+ if (err instanceof AcmeError) {
239
+ console.log(err.status); // HTTP status code (e.g. 429)
240
+ console.log(err.type); // ACME error URN (e.g. 'urn:ietf:params:acme:error:rateLimited')
241
+ console.log(err.detail); // Human-readable message
242
+ console.log(err.subproblems); // Per-identifier sub-errors (RFC 8555 §6.7.1)
243
+ console.log(err.retryAfter); // Retry-After value in seconds
244
+ console.log(err.isRateLimited); // true for 429 or rateLimited type
245
+ console.log(err.isRetryable); // true for 429, 503, 5xx, badNonce; false for 403/404/409
246
+ }
247
+ }
248
+ ```
169
249
 
170
- 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:
250
+ The built-in retry logic is **error-aware**: non-retryable errors (403, 404, 409) are thrown immediately without wasting retry attempts, and rate-limited responses respect the server's `Retry-After` header instead of using blind exponential backoff.
171
251
 
172
- ```typescript
173
- import { SmartAcme, MongoCertManager } from '@push.rocks/smartacme';
174
- import * as cloudflare from '@apiclient.xyz/cloudflare';
175
- import { Qenv } from '@push.rocks/qenv';
176
- import { Dns01Handler } from '@push.rocks/smartacme/ts/handlers/Dns01Handler.js';
177
-
178
- const qenv = new Qenv('./', './.nogit/');
179
- const cloudflareAccount = new cloudflare.CloudflareAccount(qenv.getEnvVarOnDemand('CF_TOKEN'));
180
-
181
- async function main() {
182
- // Initialize MongoDB certificate manager
183
- const certManager = new MongoCertManager({
184
- mongoDbUrl: qenv.getEnvVarRequired('MONGODB_URL'),
185
- mongoDbName: qenv.getEnvVarRequired('MONGODB_DATABASE'),
186
- mongoDbPass: qenv.getEnvVarRequired('MONGODB_PASSWORD'),
187
- });
252
+ ## Domain Matching
188
253
 
189
- const smartAcmeInstance = new SmartAcme({
190
- accountEmail: 'youremail@example.com',
191
- certManager,
192
- environment: 'integration',
193
- challengeHandlers: [new Dns01Handler(cloudflareAccount)],
194
- });
254
+ SmartAcme automatically maps subdomains to their base domain for certificate lookups:
195
255
 
196
- await smartAcmeInstance.start();
256
+ ```
257
+ subdomain.example.com → certificate for example.com ✅
258
+ *.example.com → certificate for example.com ✅
259
+ a.b.example.com → not supported (4+ levels) ❌
260
+ ```
197
261
 
198
- const myDomain = 'example.com';
199
- // Get certificate for domain (no wildcard)
200
- const myCert = await smartAcmeInstance.getCertificateForDomain(myDomain);
201
- console.log('Certificate:', myCert);
202
-
203
- // Get certificate with wildcard (requires DNS-01 handler)
204
- const certWithWildcard = await smartAcmeInstance.getCertificateForDomain(myDomain, { includeWildcard: true });
205
- console.log('Certificate with wildcard:', certWithWildcard);
262
+ ## Environment
206
263
 
207
- await smartAcmeInstance.stop();
208
- }
264
+ | Environment | Description |
265
+ |----------------|-------------|
266
+ | `production` | Let's Encrypt production servers. Certificates are browser-trusted. [Rate limits](https://letsencrypt.org/docs/rate-limits/) apply. |
267
+ | `integration` | Let's Encrypt staging servers. No rate limits, but certificates are **not** browser-trusted. Use for testing. |
209
268
 
210
- main().catch(console.error);
211
- ```
212
-
213
- ## Built-in Challenge Handlers
214
-
215
- This module includes three out-of-the-box ACME challenge handlers:
216
-
217
- - **Dns01Handler**
218
- - Uses a Cloudflare account (from `@apiclient.xyz/cloudflare`) and Smartdns client to set and remove DNS TXT records, then wait for propagation.
219
- - Import path:
220
- ```typescript
221
- import { Dns01Handler } from '@push.rocks/smartacme/ts/handlers/Dns01Handler.js';
222
- ```
223
- - Example:
224
- ```typescript
225
- import * as cloudflare from '@apiclient.xyz/cloudflare';
226
- const cfAccount = new cloudflare.CloudflareAccount('CF_TOKEN');
227
- const dnsHandler = new Dns01Handler(cfAccount);
228
- ```
229
-
230
- - **Http01Webroot**
231
- - Writes ACME HTTP-01 challenge files under a file-system webroot (`/.well-known/acme-challenge/`), and removes them on cleanup.
232
- - Import path:
233
- ```typescript
234
- import { Http01Webroot } from '@push.rocks/smartacme/ts/handlers/Http01Handler.js';
235
- ```
236
- - Example:
237
- ```typescript
238
- const httpHandler = new Http01Webroot({ webroot: '/var/www/html' });
239
- ```
240
-
241
- - **Http01MemoryHandler**
242
- - In-memory HTTP-01 challenge handler that stores and serves ACME tokens without disk I/O.
243
- - Import path:
244
- ```typescript
245
- import { Http01MemoryHandler } from '@push.rocks/smartacme/ts/handlers/Http01MemoryHandler.js';
246
- ```
247
- - Example (Express integration):
248
- ```typescript
249
- import { Http01MemoryHandler } from '@push.rocks/smartacme/ts/handlers/Http01MemoryHandler.js';
250
- const memoryHandler = new Http01MemoryHandler();
251
- app.use((req, res, next) => memoryHandler.handleRequest(req, res, next));
252
- ```
253
-
254
- All handlers implement the `IChallengeHandler<T>` interface and can be combined in the `challengeHandlers` array.
255
-
256
- ## Creating Custom Handlers
257
-
258
- To support additional challenge types or custom validation flows, implement the `IChallengeHandler<T>` interface:
259
-
260
- ```typescript
261
- import type { IChallengeHandler } from '@push.rocks/smartacme/ts/handlers/IChallengeHandler.js';
262
-
263
- // Define your custom challenge payload type
264
- interface MyChallenge { type: string; /* ... */ }
265
-
266
- class MyCustomHandler implements IChallengeHandler<MyChallenge> {
267
- getSupportedTypes(): string[] {
268
- return ['my-01'];
269
- }
270
-
271
- // Prepare the challenge (set DNS records, start servers, etc.)
272
- async prepare(ch: MyChallenge): Promise<void> {
273
- // preparation logic
274
- }
275
-
276
- // Optional verify step after prepare
277
- async verify?(ch: MyChallenge): Promise<void> {
278
- // verification logic
279
- }
280
-
281
- // Cleanup after challenge (remove records, stop servers)
282
- async cleanup(ch: MyChallenge): Promise<void> {
283
- // cleanup logic
284
- }
285
- }
286
-
287
- // Then register your handler:
288
- const customInstance = new SmartAcme({
289
- /* other options */,
290
- challengeHandlers: [ new MyCustomHandler() ],
291
- challengePriority: ['my-01'],
292
- });
293
-
294
- 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.
295
-
296
- ## Additional Details
297
-
298
- ### Certificate Object
299
-
300
- The certificate object obtained from the `getCertificateForDomain` method has the following properties:
301
-
302
- - `id`: Unique identifier for the certificate.
303
- - `domainName`: The domain name for which the certificate is issued.
304
- - `created`: Timestamp of when the certificate was created.
305
- - `privateKey`: The private key associated with the certificate.
306
- - `publicKey`: The public key or certificate itself.
307
- - `csr`: Certificate Signing Request (CSR) used to obtain the certificate.
308
- - `validUntil`: Timestamp indicating the expiration date of the certificate.
309
-
310
- ### Methods Summary
311
-
312
- - **start()**: Initializes the SmartAcme instance, sets up the ACME client, and registers the account with Let's Encrypt.
313
- - **stop()**: Closes the MongoDB connection and performs any necessary cleanup.
314
- - **getCertificateForDomain(domainArg: string, options?: { includeWildcard?: boolean })**: Retrieves or obtains a certificate for the specified domain name. If a valid certificate exists in the database, it is returned. Otherwise, a new certificate is requested and stored.
315
- - By default, only a certificate for the exact domain is requested
316
- - Set `includeWildcard: true` to also request a wildcard certificate (requires DNS-01 handler)
317
- - When requesting a wildcard directly (e.g., `*.example.com`), only the wildcard certificate is requested
318
-
319
- ### Handling Domain Matching
320
-
321
- The `SmartacmeCertMatcher` class is responsible for matching certificates with the broadest scope for wildcard certificates. The `getCertificateDomainNameByDomainName` method ensures that domains at various levels are correctly matched.
269
+ ## Complete Example with HTTP-01
322
270
 
323
271
  ```typescript
324
- import { SmartacmeCertMatcher } from '@push.rocks/smartacme';
272
+ import { SmartAcme, certmanagers, handlers } from '@push.rocks/smartacme';
273
+ import * as http from 'http';
325
274
 
326
- const certMatcher = new SmartacmeCertMatcher();
327
- const certDomainName = certMatcher.getCertificateDomainNameByDomainName('subdomain.example.com');
328
- console.log('Certificate Domain Name:', certDomainName); // Output: example.com
329
- ```
275
+ // In-memory handler for HTTP-01 challenges
276
+ const memHandler = new handlers.Http01MemoryHandler();
330
277
 
331
- ### Testing
278
+ // Create HTTP server that serves ACME challenges
279
+ const server = http.createServer((req, res) => {
280
+ memHandler.handleRequest(req, res, () => {
281
+ res.statusCode = 200;
282
+ res.end('OK');
283
+ });
284
+ });
285
+ server.listen(80);
332
286
 
333
- 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:
287
+ // Set up SmartAcme with in-memory storage and HTTP-01
288
+ const smartAcme = new SmartAcme({
289
+ accountEmail: 'admin@example.com',
290
+ certManager: new certmanagers.MemoryCertManager(),
291
+ environment: 'production',
292
+ challengeHandlers: [memHandler],
293
+ challengePriority: ['http-01'],
294
+ });
334
295
 
335
- ```bash
336
- pnpm test
296
+ await smartAcme.start();
297
+
298
+ const cert = await smartAcme.getCertificateForDomain('example.com');
299
+ // Use cert.publicKey and cert.privateKey with your HTTPS server
300
+
301
+ await smartAcme.stop();
302
+ server.close();
337
303
  ```
338
304
 
305
+ ## Architecture
339
306
 
340
- This comprehensive guide ensures you can set up, manage, and test ACME certificates efficiently and effectively using `@push.rocks/smartacme`.
307
+ Under the hood, SmartAcme uses a fully custom RFC 8555-compliant ACME protocol implementation (no external ACME libraries). Key internal modules:
341
308
 
342
- ---
309
+ | Module | Purpose |
310
+ |--------|---------|
311
+ | `AcmeClient` | Top-level ACME facade — orders, authorizations, finalization |
312
+ | `AcmeCrypto` | RSA key generation, JWK/JWS (RFC 7515/7638), CSR via `@peculiar/x509` |
313
+ | `AcmeHttpClient` | JWS-signed HTTP transport with nonce management and structured logging |
314
+ | `AcmeError` | Structured error class with type URN, subproblems, Retry-After, retryability |
315
+ | `AcmeOrderManager` | Order lifecycle — create, poll, finalize, download certificate |
316
+ | `AcmeChallengeManager` | Key authorization computation and challenge completion |
317
+
318
+ All cryptographic operations use `node:crypto`. The only external crypto dependency is `@peculiar/x509` for CSR generation.
343
319
 
344
320
  ## License and Legal Information
345
321
 
346
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license.md](license.md) file within this repository.
322
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
347
323
 
348
324
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
349
325
 
350
326
  ### Trademarks
351
327
 
352
- This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
328
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
329
+
330
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
353
331
 
354
332
  ### Company Information
355
333
 
356
334
  Task Venture Capital GmbH
357
- Registered at District court Bremen HRB 35230 HB, Germany
335
+ Registered at District Court Bremen HRB 35230 HB, Germany
358
336
 
359
- For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
337
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
360
338
 
361
339
  By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartacme',
6
- version: '8.0.0',
6
+ version: '9.0.1',
7
7
  description: 'A TypeScript-based ACME client for LetsEncrypt certificate management with a focus on simplicity and power.'
8
8
  }