@push.rocks/smartacme 7.3.4 → 9.0.0

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 +68 -0
  7. package/dist_ts/acme/acme.classes.client.js +71 -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 +36 -0
  15. package/dist_ts/acme/acme.classes.http-client.js +201 -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 +7 -2
  26. package/dist_ts/smartacme.classes.smartacme.js +80 -20
  27. package/npmextra.json +12 -6
  28. package/package.json +23 -24
  29. package/readme.hints.md +37 -2
  30. package/readme.md +201 -255
  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 +99 -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 +236 -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 +88 -23
  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
@@ -0,0 +1,125 @@
1
+ import { AcmeCrypto } from './acme.classes.crypto.js';
2
+ import { AcmeError } from './acme.classes.error.js';
3
+ import type { AcmeHttpClient } from './acme.classes.http-client.js';
4
+ import type {
5
+ IAcmeAuthorization,
6
+ IAcmeIdentifier,
7
+ IAcmeOrder,
8
+ } from './acme.interfaces.js';
9
+
10
+ /**
11
+ * ACME order lifecycle management.
12
+ * Handles order creation, authorization retrieval, finalization, and certificate download.
13
+ */
14
+ export class AcmeOrderManager {
15
+ private httpClient: AcmeHttpClient;
16
+
17
+ constructor(httpClient: AcmeHttpClient) {
18
+ this.httpClient = httpClient;
19
+ }
20
+
21
+ /**
22
+ * Create a new ACME order for the given identifiers
23
+ */
24
+ async create(opts: { identifiers: IAcmeIdentifier[] }): Promise<IAcmeOrder> {
25
+ const dir = await this.httpClient.getDirectory();
26
+ const response = await this.httpClient.signedRequest(dir.newOrder, {
27
+ identifiers: opts.identifiers,
28
+ });
29
+
30
+ const order = response.data as IAcmeOrder;
31
+ // Capture order URL from Location header
32
+ order.url = response.headers['location'] || '';
33
+ return order;
34
+ }
35
+
36
+ /**
37
+ * Retrieve all authorizations for an order (POST-as-GET each authorization URL)
38
+ */
39
+ async getAuthorizations(order: IAcmeOrder): Promise<IAcmeAuthorization[]> {
40
+ const authorizations: IAcmeAuthorization[] = [];
41
+ for (const authzUrl of order.authorizations) {
42
+ const response = await this.httpClient.signedRequest(authzUrl, null);
43
+ authorizations.push(response.data as IAcmeAuthorization);
44
+ }
45
+ return authorizations;
46
+ }
47
+
48
+ /**
49
+ * Finalize an order by submitting the CSR.
50
+ * Waits for the order to reach 'valid' status.
51
+ * Mutates the order object with updated status and certificate URL.
52
+ */
53
+ async finalize(order: IAcmeOrder, csrPem: string): Promise<void> {
54
+ // Convert PEM CSR to base64url DER for ACME
55
+ const csrDer = AcmeCrypto.pemToBuffer(csrPem);
56
+ const csrB64url = csrDer.toString('base64url');
57
+
58
+ const response = await this.httpClient.signedRequest(order.finalize, { csr: csrB64url });
59
+
60
+ // Update order with response data
61
+ const updatedOrder = response.data;
62
+ order.status = updatedOrder.status;
63
+ if (updatedOrder.certificate) {
64
+ order.certificate = updatedOrder.certificate;
65
+ }
66
+
67
+ // If not yet valid, poll until it is
68
+ if (order.status !== 'valid' && order.url) {
69
+ const finalOrder = await this.waitForValidStatus({ url: order.url });
70
+ order.status = finalOrder.status;
71
+ order.certificate = finalOrder.certificate;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Download the certificate chain (PEM) from the order's certificate URL
77
+ */
78
+ async getCertificate(order: IAcmeOrder): Promise<string> {
79
+ if (!order.certificate) {
80
+ throw new Error('Order does not have a certificate URL - finalize first');
81
+ }
82
+ const response = await this.httpClient.signedRequest(order.certificate, null);
83
+ // Certificate chain is returned as PEM text
84
+ return typeof response.data === 'string' ? response.data : response.data.toString();
85
+ }
86
+
87
+ /**
88
+ * Poll an ACME resource (order or challenge) until it reaches 'valid' or 'ready' status.
89
+ * Uses exponential backoff with Retry-After header support.
90
+ */
91
+ async waitForValidStatus(
92
+ item: { url: string },
93
+ opts?: { maxAttempts?: number; initialDelayMs?: number },
94
+ ): Promise<any> {
95
+ const maxAttempts = opts?.maxAttempts ?? 30;
96
+ const initialDelay = opts?.initialDelayMs ?? 1000;
97
+
98
+ for (let i = 0; i < maxAttempts; i++) {
99
+ const response = await this.httpClient.signedRequest(item.url, null);
100
+ const body = response.data;
101
+
102
+ if (body.status === 'valid' || body.status === 'ready') {
103
+ return body;
104
+ }
105
+ if (body.status === 'invalid') {
106
+ const challengeError = body.challenges?.find((c: any) => c.error)?.error;
107
+ throw new AcmeError({
108
+ status: 0,
109
+ type: challengeError?.type || 'urn:ietf:params:acme:error:rejectedIdentifier',
110
+ detail: challengeError?.detail || JSON.stringify(body),
111
+ subproblems: challengeError?.subproblems,
112
+ url: item.url,
113
+ });
114
+ }
115
+
116
+ // Respect Retry-After header, otherwise exponential backoff
117
+ const retryAfter = parseInt(response.headers['retry-after'] || '0', 10);
118
+ const delay =
119
+ retryAfter > 0 ? retryAfter * 1000 : Math.min(initialDelay * Math.pow(2, i), 30000);
120
+ await new Promise((resolve) => setTimeout(resolve, delay));
121
+ }
122
+
123
+ throw new Error(`Timeout waiting for valid status after ${maxAttempts} attempts`);
124
+ }
125
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * ACME Protocol interfaces per RFC 8555
3
+ */
4
+
5
+ export interface IAcmeDirectory {
6
+ newNonce: string;
7
+ newAccount: string;
8
+ newOrder: string;
9
+ newAuthz?: string;
10
+ revokeCert?: string;
11
+ keyChange?: string;
12
+ meta?: IAcmeDirectoryMeta;
13
+ }
14
+
15
+ export interface IAcmeDirectoryMeta {
16
+ termsOfService?: string;
17
+ website?: string;
18
+ caaIdentities?: string[];
19
+ externalAccountRequired?: boolean;
20
+ }
21
+
22
+ export interface IAcmeIdentifier {
23
+ type: 'dns';
24
+ value: string;
25
+ }
26
+
27
+ export interface IAcmeAccount {
28
+ status: string;
29
+ contact?: string[];
30
+ termsOfServiceAgreed?: boolean;
31
+ orders?: string;
32
+ }
33
+
34
+ export interface IAcmeAccountCreateRequest {
35
+ termsOfServiceAgreed: boolean;
36
+ contact?: string[];
37
+ }
38
+
39
+ export interface IAcmeOrder {
40
+ url: string;
41
+ status: string;
42
+ expires?: string;
43
+ identifiers: IAcmeIdentifier[];
44
+ authorizations: string[];
45
+ finalize: string;
46
+ certificate?: string;
47
+ }
48
+
49
+ export interface IAcmeAuthorization {
50
+ identifier: IAcmeIdentifier;
51
+ status: string;
52
+ expires?: string;
53
+ challenges: IAcmeChallenge[];
54
+ wildcard?: boolean;
55
+ }
56
+
57
+ export interface IAcmeChallenge {
58
+ type: string;
59
+ url: string;
60
+ status: string;
61
+ token: string;
62
+ validated?: string;
63
+ }
64
+
65
+ export interface IAcmeCsrOptions {
66
+ commonName: string;
67
+ altNames?: string[];
68
+ }
69
+
70
+ export interface IAcmeHttpResponse {
71
+ status: number;
72
+ headers: Record<string, string>;
73
+ data: any;
74
+ }
@@ -0,0 +1,16 @@
1
+ export { AcmeClient, type IAcmeClientOptions } from './acme.classes.client.js';
2
+ export { AcmeCrypto } from './acme.classes.crypto.js';
3
+ export { AcmeError } from './acme.classes.error.js';
4
+ export { ACME_DIRECTORY_URLS } from './acme.classes.directory.js';
5
+ export type {
6
+ IAcmeDirectory,
7
+ IAcmeDirectoryMeta,
8
+ IAcmeIdentifier,
9
+ IAcmeAccount,
10
+ IAcmeAccountCreateRequest,
11
+ IAcmeOrder,
12
+ IAcmeAuthorization,
13
+ IAcmeChallenge,
14
+ IAcmeCsrOptions,
15
+ IAcmeHttpResponse,
16
+ } from './acme.interfaces.js';
@@ -14,7 +14,6 @@ export class MongoCertManager implements ICertManager {
14
14
  */
15
15
  constructor(mongoDescriptor: plugins.smartdata.IMongoDescriptor) {
16
16
  this.db = new plugins.smartdata.SmartdataDb(mongoDescriptor);
17
- // Use a single EasyStore document to hold all certs keyed by domainName
18
17
  this.store = new plugins.smartdata.EasyStore<Record<string, any>>(
19
18
  'smartacme-certs',
20
19
  this.db,
package/ts/plugins.ts CHANGED
@@ -9,21 +9,13 @@ import * as cloudflare from '@apiclient.xyz/cloudflare';
9
9
 
10
10
  export { cloudflare };
11
11
 
12
- // @apiglobal scope
13
- import * as typedserver from '@api.global/typedserver';
14
-
15
- export { typedserver };
16
-
17
- // @pushrocks scope
12
+ // @push.rocks scope
18
13
  import * as lik from '@push.rocks/lik';
19
14
  import * as smartdata from '@push.rocks/smartdata';
20
15
  import * as smartdelay from '@push.rocks/smartdelay';
21
16
  import * as smartdnsClient from '@push.rocks/smartdns/client';
22
- import * as smartfile from '@push.rocks/smartfile';
23
17
  import * as smartlog from '@push.rocks/smartlog';
24
18
  import * as smartnetwork from '@push.rocks/smartnetwork';
25
- import * as smartpromise from '@push.rocks/smartpromise';
26
- import * as smartrequest from '@push.rocks/smartrequest';
27
19
  import * as smartunique from '@push.rocks/smartunique';
28
20
  import * as smartstring from '@push.rocks/smartstring';
29
21
  import * as smarttime from '@push.rocks/smarttime';
@@ -33,11 +25,8 @@ export {
33
25
  smartdata,
34
26
  smartdelay,
35
27
  smartdnsClient,
36
- smartfile,
37
28
  smartlog,
38
29
  smartnetwork,
39
- smartpromise,
40
- smartrequest,
41
30
  smartunique,
42
31
  smartstring,
43
32
  smarttime,
@@ -48,8 +37,8 @@ import * as tsclass from '@tsclass/tsclass';
48
37
 
49
38
  export { tsclass };
50
39
 
51
- // third party scope
52
- import * as acme from 'acme-client';
40
+ // acme protocol (custom implementation)
41
+ import * as acme from './acme/index.js';
53
42
 
54
43
  export { acme };
55
44
  // local handlers for challenge types
@@ -54,7 +54,7 @@ export class SmartAcme {
54
54
  private options: ISmartAcmeOptions;
55
55
 
56
56
  // the acme client
57
- private client: plugins.acme.Client;
57
+ private client: plugins.acme.AcmeClient;
58
58
  private smartdns = new plugins.smartdnsClient.Smartdns({});
59
59
  public logger: plugins.smartlog.Smartlog;
60
60
 
@@ -77,6 +77,9 @@ export class SmartAcme {
77
77
  private challengePriority: string[];
78
78
  // Map for coordinating concurrent certificate requests
79
79
  private interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
80
+ // bound signal handlers so they can be removed on stop()
81
+ private boundSigintHandler: (() => void) | null = null;
82
+ private boundSigtermHandler: (() => void) | null = null;
80
83
 
81
84
  constructor(optionsArg: ISmartAcmeOptions) {
82
85
  this.options = optionsArg;
@@ -114,7 +117,7 @@ export class SmartAcme {
114
117
  */
115
118
  public async start() {
116
119
  this.privateKey =
117
- this.options.accountPrivateKey || (await plugins.acme.forge.createPrivateKey()).toString();
120
+ this.options.accountPrivateKey || plugins.acme.AcmeCrypto.createRsaPrivateKey();
118
121
 
119
122
  // Initialize certificate manager
120
123
  if (!this.options.certManager) {
@@ -127,15 +130,18 @@ export class SmartAcme {
127
130
  this.certmatcher = new SmartacmeCertMatcher();
128
131
 
129
132
  // ACME Client
130
- this.client = new plugins.acme.Client({
133
+ this.client = new plugins.acme.AcmeClient({
131
134
  directoryUrl: (() => {
132
135
  if (this.options.environment === 'production') {
133
- return plugins.acme.directory.letsencrypt.production;
136
+ return plugins.acme.ACME_DIRECTORY_URLS.letsencrypt.production;
134
137
  } else {
135
- return plugins.acme.directory.letsencrypt.staging;
138
+ return plugins.acme.ACME_DIRECTORY_URLS.letsencrypt.staging;
136
139
  }
137
140
  })(),
138
- accountKey: this.privateKey,
141
+ accountKeyPem: this.privateKey,
142
+ logger: (level, message, data) => {
143
+ this.logger.log(level as any, message, data);
144
+ },
139
145
  });
140
146
 
141
147
  /* Register account */
@@ -143,20 +149,31 @@ export class SmartAcme {
143
149
  termsOfServiceAgreed: true,
144
150
  contact: [`mailto:${this.options.accountEmail}`],
145
151
  });
146
- // Setup graceful shutdown handlers
147
- process.on('SIGINT', () => this.handleSignal('SIGINT'));
148
- process.on('SIGTERM', () => this.handleSignal('SIGTERM'));
152
+ // Setup graceful shutdown handlers (store references for removal in stop())
153
+ this.boundSigintHandler = () => this.handleSignal('SIGINT');
154
+ this.boundSigtermHandler = () => this.handleSignal('SIGTERM');
155
+ process.on('SIGINT', this.boundSigintHandler);
156
+ process.on('SIGTERM', this.boundSigtermHandler);
149
157
  }
150
158
 
151
159
  /**
152
160
  * Stops the SmartAcme instance and closes certificate store connections.
153
161
  */
154
162
  public async stop() {
163
+ // Remove signal handlers so the process can exit cleanly
164
+ if (this.boundSigintHandler) {
165
+ process.removeListener('SIGINT', this.boundSigintHandler);
166
+ this.boundSigintHandler = null;
167
+ }
168
+ if (this.boundSigtermHandler) {
169
+ process.removeListener('SIGTERM', this.boundSigtermHandler);
170
+ this.boundSigtermHandler = null;
171
+ }
155
172
  if (this.certmanager && typeof (this.certmanager as any).close === 'function') {
156
173
  await (this.certmanager as any).close();
157
174
  }
158
175
  }
159
- /** Retry helper with exponential backoff */
176
+ /** Retry helper with exponential backoff and AcmeError awareness */
160
177
  private async retry<T>(operation: () => Promise<T>, operationName: string = 'operation'): Promise<T> {
161
178
  let attempt = 0;
162
179
  let delay = this.retryOptions.minTimeoutMs;
@@ -164,6 +181,19 @@ export class SmartAcme {
164
181
  try {
165
182
  return await operation();
166
183
  } catch (err) {
184
+ // Check if it's a non-retryable ACME error — throw immediately
185
+ if (err instanceof plugins.acme.AcmeError) {
186
+ if (!err.isRetryable) {
187
+ await this.logger.log('error', `Operation ${operationName} failed with non-retryable error (${err.type}, HTTP ${err.status}) at ${err.url}`, err);
188
+ throw err;
189
+ }
190
+ // For rate-limited errors, use server-specified Retry-After delay
191
+ if (err.isRateLimited && err.retryAfter > 0) {
192
+ delay = err.retryAfter * 1000;
193
+ await this.logger.log('warn', `Operation ${operationName} rate-limited, Retry-After: ${err.retryAfter}s`, err);
194
+ }
195
+ }
196
+
167
197
  attempt++;
168
198
  if (attempt > this.retryOptions.retries) {
169
199
  await this.logger.log('error', `Operation ${operationName} failed after ${attempt} attempts`, err);
@@ -221,8 +251,12 @@ export class SmartAcme {
221
251
  * * retrieve it from the databse and return it
222
252
  *
223
253
  * @param domainArg
254
+ * @param options Optional configuration for certificate generation
224
255
  */
225
- public async getCertificateForDomain(domainArg: string): Promise<SmartacmeCert> {
256
+ public async getCertificateForDomain(
257
+ domainArg: string,
258
+ options?: { includeWildcard?: boolean }
259
+ ): Promise<SmartacmeCert> {
226
260
  // Determine if this is a wildcard request (e.g., '*.example.com').
227
261
  const isWildcardRequest = domainArg.startsWith('*.');
228
262
  // Determine the base domain for certificate retrieval/issuance.
@@ -259,12 +293,32 @@ export class SmartAcme {
259
293
  // lets make sure others get the same interest
260
294
  const currentDomainInterst = await this.interestMap.addInterest(certDomainName);
261
295
 
296
+ // Build identifiers array based on request
297
+ const identifiers = [];
298
+
299
+ if (isWildcardRequest) {
300
+ // If requesting a wildcard directly, only add the wildcard
301
+ identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
302
+ } else {
303
+ // Add the regular domain
304
+ identifiers.push({ type: 'dns', value: certDomainName });
305
+
306
+ // Only add wildcard if explicitly requested
307
+ if (options?.includeWildcard) {
308
+ const hasDnsHandler = this.challengeHandlers.some((h) =>
309
+ h.getSupportedTypes().includes('dns-01'),
310
+ );
311
+ if (!hasDnsHandler) {
312
+ this.logger.log('warn', 'Wildcard certificate requested but no DNS-01 handler available. Skipping wildcard.');
313
+ } else {
314
+ identifiers.push({ type: 'dns', value: `*.${certDomainName}` });
315
+ }
316
+ }
317
+ }
318
+
262
319
  /* Place new order with retry */
263
320
  const order = await this.retry(() => this.client.createOrder({
264
- identifiers: [
265
- { type: 'dns', value: certDomainName },
266
- { type: 'dns', value: `*.${certDomainName}` },
267
- ],
321
+ identifiers,
268
322
  }), 'createOrder');
269
323
 
270
324
  /* Get authorizations and select challenges */
@@ -323,11 +377,6 @@ export class SmartAcme {
323
377
  this.logger.log('info', 'Cooling down for 1 minute before ACME verification');
324
378
  await plugins.smartdelay.delayFor(60000);
325
379
  }
326
- // Official ACME verification (ensures challenge is publicly reachable)
327
- await this.retry(
328
- () => this.client.verifyChallenge(authz, selectedChallengeArg),
329
- `${type}.verifyChallenge`,
330
- );
331
380
  // Notify ACME server to complete the challenge
332
381
  await this.retry(
333
382
  () => this.client.completeChallenge(selectedChallengeArg),
@@ -359,9 +408,25 @@ export class SmartAcme {
359
408
  }
360
409
 
361
410
  /* Finalize order */
362
- const [key, csr] = await plugins.acme.forge.createCsr({
363
- commonName: `*.${certDomainName}`,
364
- altNames: [certDomainName],
411
+ const csrDomains = [];
412
+ let commonName: string;
413
+
414
+ if (isWildcardRequest) {
415
+ // For wildcard requests, use wildcard as common name
416
+ commonName = `*.${certDomainName}`;
417
+ csrDomains.push(certDomainName); // Add base domain as alt name
418
+ } else {
419
+ // For regular requests, use base domain as common name
420
+ commonName = certDomainName;
421
+ if (options?.includeWildcard && identifiers.some(id => id.value === `*.${certDomainName}`)) {
422
+ // If wildcard was successfully added, include it as alt name
423
+ csrDomains.push(`*.${certDomainName}`);
424
+ }
425
+ }
426
+
427
+ const [key, csr] = await plugins.acme.AcmeCrypto.createCsr({
428
+ commonName,
429
+ altNames: csrDomains,
365
430
  });
366
431
 
367
432
  await this.retry(() => this.client.finalizeOrder(order, csr), 'finalizeOrder');
@@ -1,42 +0,0 @@
1
- import * as plugins from './smartacme.plugins.js';
2
- import type { ICertManager } from './interfaces/certmanager.js';
3
- import { SmartacmeCert } from './smartacme.classes.cert.js';
4
- /**
5
- * In-memory certificate manager for mongoless mode.
6
- * Stores certificates in memory only and does not connect to MongoDB.
7
- */
8
- export declare class MemoryCertManager implements ICertManager {
9
- interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
10
- private certs;
11
- constructor();
12
- init(): Promise<void>;
13
- retrieveCertificate(domainName: string): Promise<SmartacmeCert | null>;
14
- storeCertificate(cert: SmartacmeCert): Promise<void>;
15
- deleteCertificate(domainName: string): Promise<void>;
16
- close(): Promise<void>;
17
- /**
18
- * Wipe all certificates from the in-memory store (for testing)
19
- */
20
- wipe(): Promise<void>;
21
- }
22
- /**
23
- * MongoDB-backed certificate manager using EasyStore from smartdata.
24
- */
25
- export declare class MongoCertManager implements ICertManager {
26
- interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
27
- private db;
28
- private store;
29
- /**
30
- * @param mongoDescriptor MongoDB connection settings
31
- */
32
- constructor(mongoDescriptor: plugins.smartdata.IMongoDescriptor);
33
- init(): Promise<void>;
34
- retrieveCertificate(domainName: string): Promise<SmartacmeCert | null>;
35
- storeCertificate(cert: SmartacmeCert): Promise<void>;
36
- deleteCertificate(domainName: string): Promise<void>;
37
- close(): Promise<void>;
38
- /**
39
- * Wipe all certificates from the persistent store (for integration testing)
40
- */
41
- wipe(): Promise<void>;
42
- }
@@ -1,86 +0,0 @@
1
- import * as plugins from './smartacme.plugins.js';
2
- import { SmartacmeCert } from './smartacme.classes.cert.js';
3
- /**
4
- * In-memory certificate manager for mongoless mode.
5
- * Stores certificates in memory only and does not connect to MongoDB.
6
- */
7
- export class MemoryCertManager {
8
- constructor() {
9
- this.certs = new Map();
10
- this.interestMap = new plugins.lik.InterestMap((domain) => domain);
11
- }
12
- async init() {
13
- // no-op for in-memory store
14
- }
15
- async retrieveCertificate(domainName) {
16
- return this.certs.get(domainName) ?? null;
17
- }
18
- async storeCertificate(cert) {
19
- this.certs.set(cert.domainName, cert);
20
- const interest = this.interestMap.findInterest(cert.domainName);
21
- if (interest) {
22
- interest.fullfillInterest(cert);
23
- interest.markLost();
24
- }
25
- }
26
- async deleteCertificate(domainName) {
27
- this.certs.delete(domainName);
28
- }
29
- async close() {
30
- // no-op
31
- }
32
- /**
33
- * Wipe all certificates from the in-memory store (for testing)
34
- */
35
- async wipe() {
36
- this.certs.clear();
37
- // reset interest map
38
- this.interestMap = new plugins.lik.InterestMap((domain) => domain);
39
- }
40
- }
41
- /**
42
- * MongoDB-backed certificate manager using EasyStore from smartdata.
43
- */
44
- export class MongoCertManager {
45
- /**
46
- * @param mongoDescriptor MongoDB connection settings
47
- */
48
- constructor(mongoDescriptor) {
49
- this.db = new plugins.smartdata.SmartdataDb(mongoDescriptor);
50
- // Use a single EasyStore document to hold all certs keyed by domainName
51
- this.store = new plugins.smartdata.EasyStore('smartacme-certs', this.db);
52
- this.interestMap = new plugins.lik.InterestMap((domain) => domain);
53
- }
54
- async init() {
55
- await this.db.init();
56
- }
57
- async retrieveCertificate(domainName) {
58
- const data = await this.store.readKey(domainName);
59
- return data ? new SmartacmeCert(data) : null;
60
- }
61
- async storeCertificate(cert) {
62
- // write plain object for persistence
63
- await this.store.writeKey(cert.domainName, { ...cert });
64
- const interest = this.interestMap.findInterest(cert.domainName);
65
- if (interest) {
66
- interest.fullfillInterest(cert);
67
- interest.markLost();
68
- }
69
- }
70
- async deleteCertificate(domainName) {
71
- await this.store.deleteKey(domainName);
72
- }
73
- async close() {
74
- await this.db.close();
75
- }
76
- /**
77
- * Wipe all certificates from the persistent store (for integration testing)
78
- */
79
- async wipe() {
80
- // clear all keys in the easy store
81
- await this.store.wipe();
82
- // reset interest map
83
- this.interestMap = new plugins.lik.InterestMap((domain) => domain);
84
- }
85
- }
86
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2VydG1hbmFnZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2VydG1hbmFnZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sd0JBQXdCLENBQUM7QUFFbEQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBRTVEOzs7R0FHRztBQUNILE1BQU0sT0FBTyxpQkFBaUI7SUFJNUI7UUFGUSxVQUFLLEdBQStCLElBQUksR0FBRyxFQUFFLENBQUM7UUFHcEQsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNyRSxDQUFDO0lBRU0sS0FBSyxDQUFDLElBQUk7UUFDZiw0QkFBNEI7SUFDOUIsQ0FBQztJQUVNLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxVQUFrQjtRQUNqRCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxJQUFJLElBQUksQ0FBQztJQUM1QyxDQUFDO0lBRU0sS0FBSyxDQUFDLGdCQUFnQixDQUFDLElBQW1CO1FBQy9DLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDdEMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ2hFLElBQUksUUFBUSxFQUFFLENBQUM7WUFDYixRQUFRLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDaEMsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3RCLENBQUM7SUFDSCxDQUFDO0lBRU0sS0FBSyxDQUFDLGlCQUFpQixDQUFDLFVBQWtCO1FBQy9DLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxD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
@@ -1,43 +0,0 @@
1
- import * as plugins from './smartacme.plugins.js';
2
- import { SmartacmeCert } from './smartacme.classes.cert.js';
3
- import { SmartAcme } from './smartacme.classes.smartacme.js';
4
- export declare class SmartacmeCertManager {
5
- static activeDB: plugins.smartdata.SmartdataDb;
6
- private mongoDescriptor;
7
- smartdataDb: plugins.smartdata.SmartdataDb;
8
- interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
9
- constructor(smartAcmeArg: SmartAcme, optionsArg: {
10
- mongoDescriptor: plugins.smartdata.IMongoDescriptor;
11
- });
12
- init(): Promise<void>;
13
- /**
14
- * retrieves a certificate
15
- * @returns the Cert class or null
16
- * @param certDomainNameArg the domain Name to retrieve the vcertificate for
17
- */
18
- retrieveCertificate(certDomainNameArg: string): Promise<SmartacmeCert>;
19
- /**
20
- * stores the certificate
21
- * @param optionsArg
22
- */
23
- storeCertificate(optionsArg: plugins.tsclass.network.ICert): Promise<void>;
24
- deleteCertificate(certDomainNameArg: string): Promise<void>;
25
- /**
26
- * Close underlying MongoDB connection
27
- */
28
- close(): Promise<void>;
29
- }
30
- /**
31
- * In-memory certificate manager for mongoless mode.
32
- * Stores certificates in memory only and does not connect to MongoDB.
33
- */
34
- export declare class MemoryCertManager {
35
- interestMap: plugins.lik.InterestMap<string, SmartacmeCert>;
36
- private certs;
37
- constructor();
38
- init(): Promise<void>;
39
- retrieveCertificate(certDomainName: string): Promise<SmartacmeCert>;
40
- storeCertificate(optionsArg: plugins.tsclass.network.ICert): Promise<void>;
41
- deleteCertificate(certDomainName: string): Promise<void>;
42
- close(): Promise<void>;
43
- }