@push.rocks/smartacme 9.1.3 → 9.3.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 (89) hide show
  1. package/dist_ts/00_commitinfo_data.js +3 -3
  2. package/dist_ts/acme/acme.classes.account.js +1 -1
  3. package/dist_ts/acme/acme.classes.challenge.js +1 -1
  4. package/dist_ts/acme/acme.classes.client.js +1 -1
  5. package/dist_ts/acme/acme.classes.crypto.d.ts +1 -1
  6. package/dist_ts/acme/acme.classes.crypto.js +1 -1
  7. package/dist_ts/acme/acme.classes.directory.js +1 -1
  8. package/dist_ts/acme/acme.classes.error.js +1 -1
  9. package/dist_ts/acme/acme.classes.http-client.js +1 -1
  10. package/dist_ts/acme/acme.classes.order.js +1 -1
  11. package/dist_ts/acme/acme.interfaces.js +1 -1
  12. package/dist_ts/acme/index.js +1 -1
  13. package/dist_ts/certmanagers/index.js +1 -1
  14. package/dist_ts/certmanagers/memory.js +1 -1
  15. package/dist_ts/certmanagers/mongo.js +1 -1
  16. package/dist_ts/handlers/Dns01Handler.js +1 -1
  17. package/dist_ts/handlers/Http01Handler.js +1 -1
  18. package/dist_ts/handlers/Http01MemoryHandler.js +1 -1
  19. package/dist_ts/handlers/IChallengeHandler.js +1 -1
  20. package/dist_ts/handlers/index.js +1 -1
  21. package/dist_ts/index.d.ts +2 -0
  22. package/dist_ts/index.js +4 -1
  23. package/dist_ts/interfaces/accountdata.js +1 -1
  24. package/dist_ts/interfaces/certmanager.js +1 -1
  25. package/dist_ts/interfaces/index.js +1 -1
  26. package/dist_ts/plugins.js +1 -1
  27. package/dist_ts/smartacme.classes.cert.js +1 -1
  28. package/dist_ts/smartacme.classes.certmatcher.js +1 -1
  29. package/dist_ts/smartacme.classes.smartacme.js +1 -1
  30. package/dist_ts_server/index.d.ts +4 -0
  31. package/dist_ts_server/index.js +4 -0
  32. package/dist_ts_server/server.classes.account.store.d.ts +12 -0
  33. package/dist_ts_server/server.classes.account.store.js +25 -0
  34. package/dist_ts_server/server.classes.acmeserver.d.ts +22 -0
  35. package/dist_ts_server/server.classes.acmeserver.js +100 -0
  36. package/dist_ts_server/server.classes.ca.d.ts +25 -0
  37. package/dist_ts_server/server.classes.ca.js +109 -0
  38. package/dist_ts_server/server.classes.challenge.verifier.d.ts +16 -0
  39. package/dist_ts_server/server.classes.challenge.verifier.js +56 -0
  40. package/dist_ts_server/server.classes.jws.verifier.d.ts +31 -0
  41. package/dist_ts_server/server.classes.jws.verifier.js +116 -0
  42. package/dist_ts_server/server.classes.nonce.d.ts +12 -0
  43. package/dist_ts_server/server.classes.nonce.js +32 -0
  44. package/dist_ts_server/server.classes.order.store.d.ts +21 -0
  45. package/dist_ts_server/server.classes.order.store.js +57 -0
  46. package/dist_ts_server/server.classes.router.d.ts +17 -0
  47. package/dist_ts_server/server.classes.router.js +99 -0
  48. package/dist_ts_server/server.handlers.account.d.ts +8 -0
  49. package/dist_ts_server/server.handlers.account.js +59 -0
  50. package/dist_ts_server/server.handlers.authz.d.ts +7 -0
  51. package/dist_ts_server/server.handlers.authz.js +41 -0
  52. package/dist_ts_server/server.handlers.cert.d.ts +7 -0
  53. package/dist_ts_server/server.handlers.cert.js +18 -0
  54. package/dist_ts_server/server.handlers.challenge.d.ts +10 -0
  55. package/dist_ts_server/server.handlers.challenge.js +110 -0
  56. package/dist_ts_server/server.handlers.directory.d.ts +5 -0
  57. package/dist_ts_server/server.handlers.directory.js +23 -0
  58. package/dist_ts_server/server.handlers.finalize.d.ts +8 -0
  59. package/dist_ts_server/server.handlers.finalize.js +65 -0
  60. package/dist_ts_server/server.handlers.nonce.d.ts +7 -0
  61. package/dist_ts_server/server.handlers.nonce.js +17 -0
  62. package/dist_ts_server/server.handlers.order.d.ts +11 -0
  63. package/dist_ts_server/server.handlers.order.js +131 -0
  64. package/dist_ts_server/server.interfaces.d.ts +72 -0
  65. package/dist_ts_server/server.interfaces.js +2 -0
  66. package/package.json +18 -13
  67. package/readme.hints.md +25 -1
  68. package/readme.md +122 -2
  69. package/ts/00_commitinfo_data.ts +2 -2
  70. package/ts/acme/acme.classes.crypto.ts +1 -1
  71. package/ts/index.ts +4 -0
  72. package/ts_server/index.ts +12 -0
  73. package/ts_server/server.classes.account.store.ts +27 -0
  74. package/ts_server/server.classes.acmeserver.ts +128 -0
  75. package/ts_server/server.classes.ca.ts +142 -0
  76. package/ts_server/server.classes.challenge.verifier.ts +61 -0
  77. package/ts_server/server.classes.jws.verifier.ts +153 -0
  78. package/ts_server/server.classes.nonce.ts +36 -0
  79. package/ts_server/server.classes.order.store.ts +72 -0
  80. package/ts_server/server.classes.router.ts +116 -0
  81. package/ts_server/server.handlers.account.ts +85 -0
  82. package/ts_server/server.handlers.authz.ts +58 -0
  83. package/ts_server/server.handlers.cert.ts +32 -0
  84. package/ts_server/server.handlers.challenge.ts +142 -0
  85. package/ts_server/server.handlers.directory.ts +30 -0
  86. package/ts_server/server.handlers.finalize.ts +93 -0
  87. package/ts_server/server.handlers.nonce.ts +29 -0
  88. package/ts_server/server.handlers.order.ts +177 -0
  89. package/ts_server/server.interfaces.ts +98 -0
@@ -0,0 +1,177 @@
1
+ import type * as http from 'node:http';
2
+ import * as crypto from 'node:crypto';
3
+ import type { JwsVerifier } from './server.classes.jws.verifier.js';
4
+ import { AcmeServerError } from './server.classes.jws.verifier.js';
5
+ import type { IServerOrderStore, IServerAccountStore } from './server.interfaces.js';
6
+ import type { IAcmeIdentifier } from '../ts/acme/acme.interfaces.js';
7
+
8
+ /**
9
+ * POST /new-order — Create a new ACME order.
10
+ */
11
+ export function createNewOrderHandler(
12
+ baseUrl: string,
13
+ jwsVerifier: JwsVerifier,
14
+ orderStore: IServerOrderStore,
15
+ ) {
16
+ return async (
17
+ req: http.IncomingMessage,
18
+ res: http.ServerResponse,
19
+ _params: Record<string, string>,
20
+ body: any,
21
+ ): Promise<void> => {
22
+ const requestUrl = `${baseUrl}/new-order`;
23
+ const verified = await jwsVerifier.verify(body, requestUrl);
24
+
25
+ if (!verified.kid) {
26
+ throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'newOrder requires kid');
27
+ }
28
+
29
+ const { payload } = verified;
30
+ const identifiers: IAcmeIdentifier[] = payload?.identifiers;
31
+
32
+ if (!identifiers || !Array.isArray(identifiers) || identifiers.length === 0) {
33
+ throw new AcmeServerError(
34
+ 400,
35
+ 'urn:ietf:params:acme:error:malformed',
36
+ 'Order must include at least one identifier',
37
+ );
38
+ }
39
+
40
+ const orderId = crypto.randomBytes(16).toString('hex');
41
+ const expires = new Date();
42
+ expires.setDate(expires.getDate() + 7);
43
+
44
+ // Create authorizations and challenges for each identifier
45
+ const authorizationIds: string[] = [];
46
+
47
+ for (const identifier of identifiers) {
48
+ const authzId = crypto.randomBytes(16).toString('hex');
49
+ const isWildcard = identifier.value.startsWith('*.');
50
+ const domain = isWildcard ? identifier.value.slice(2) : identifier.value;
51
+
52
+ // Create challenges for this authorization
53
+ const challengeIds: string[] = [];
54
+
55
+ // HTTP-01 challenge (not for wildcards)
56
+ if (!isWildcard) {
57
+ const http01Id = crypto.randomBytes(16).toString('hex');
58
+ const http01Token = crypto.randomBytes(32).toString('base64url');
59
+ await orderStore.createChallenge({
60
+ id: http01Id,
61
+ authorizationId: authzId,
62
+ type: 'http-01',
63
+ token: http01Token,
64
+ status: 'pending',
65
+ });
66
+ challengeIds.push(http01Id);
67
+ }
68
+
69
+ // DNS-01 challenge (always)
70
+ const dns01Id = crypto.randomBytes(16).toString('hex');
71
+ const dns01Token = crypto.randomBytes(32).toString('base64url');
72
+ await orderStore.createChallenge({
73
+ id: dns01Id,
74
+ authorizationId: authzId,
75
+ type: 'dns-01',
76
+ token: dns01Token,
77
+ status: 'pending',
78
+ });
79
+ challengeIds.push(dns01Id);
80
+
81
+ await orderStore.createAuthorization({
82
+ id: authzId,
83
+ orderId,
84
+ identifier: { type: 'dns', value: domain },
85
+ status: 'pending',
86
+ expires: expires.toISOString(),
87
+ challengeIds,
88
+ wildcard: isWildcard || undefined,
89
+ });
90
+
91
+ authorizationIds.push(authzId);
92
+ }
93
+
94
+ const order = await orderStore.createOrder({
95
+ id: orderId,
96
+ accountUrl: verified.kid,
97
+ status: 'pending',
98
+ identifiers,
99
+ authorizationIds,
100
+ expires: expires.toISOString(),
101
+ finalize: `${baseUrl}/finalize/${orderId}`,
102
+ });
103
+
104
+ const responseBody = {
105
+ status: order.status,
106
+ expires: order.expires,
107
+ identifiers: order.identifiers,
108
+ authorizations: order.authorizationIds.map((id) => `${baseUrl}/authz/${id}`),
109
+ finalize: order.finalize,
110
+ };
111
+
112
+ res.writeHead(201, {
113
+ 'Content-Type': 'application/json',
114
+ 'Location': `${baseUrl}/order/${orderId}`,
115
+ });
116
+ res.end(JSON.stringify(responseBody));
117
+ };
118
+ }
119
+
120
+ /**
121
+ * POST /order/:id — Poll order status (POST-as-GET).
122
+ */
123
+ export function createOrderPollHandler(
124
+ baseUrl: string,
125
+ jwsVerifier: JwsVerifier,
126
+ orderStore: IServerOrderStore,
127
+ ) {
128
+ return async (
129
+ req: http.IncomingMessage,
130
+ res: http.ServerResponse,
131
+ params: Record<string, string>,
132
+ body: any,
133
+ ): Promise<void> => {
134
+ const orderId = params.id;
135
+ const requestUrl = `${baseUrl}/order/${orderId}`;
136
+ await jwsVerifier.verify(body, requestUrl);
137
+
138
+ const order = await orderStore.getOrder(orderId);
139
+ if (!order) {
140
+ throw new AcmeServerError(404, 'urn:ietf:params:acme:error:malformed', 'Order not found');
141
+ }
142
+
143
+ // Check if all authorizations are valid → transition to ready
144
+ if (order.status === 'pending') {
145
+ let allValid = true;
146
+ for (const authzId of order.authorizationIds) {
147
+ const authz = await orderStore.getAuthorization(authzId);
148
+ if (!authz || authz.status !== 'valid') {
149
+ allValid = false;
150
+ break;
151
+ }
152
+ }
153
+ if (allValid) {
154
+ await orderStore.updateOrder(orderId, { status: 'ready' });
155
+ order.status = 'ready';
156
+ }
157
+ }
158
+
159
+ const responseBody: Record<string, any> = {
160
+ status: order.status,
161
+ expires: order.expires,
162
+ identifiers: order.identifiers,
163
+ authorizations: order.authorizationIds.map((id) => `${baseUrl}/authz/${id}`),
164
+ finalize: order.finalize,
165
+ };
166
+
167
+ if (order.certificate) {
168
+ responseBody.certificate = order.certificate;
169
+ }
170
+
171
+ res.writeHead(200, {
172
+ 'Content-Type': 'application/json',
173
+ 'Location': requestUrl,
174
+ });
175
+ res.end(JSON.stringify(responseBody));
176
+ };
177
+ }
@@ -0,0 +1,98 @@
1
+ import type { IAcmeIdentifier } from '../ts/acme/acme.interfaces.js';
2
+
3
+ // ============================================================================
4
+ // Server configuration
5
+ // ============================================================================
6
+
7
+ export interface IAcmeServerOptions {
8
+ port?: number;
9
+ hostname?: string;
10
+ baseUrl?: string;
11
+ /** When false, challenges auto-approve on trigger (useful for testing) */
12
+ challengeVerification?: boolean;
13
+ caOptions?: {
14
+ commonName?: string;
15
+ validityDays?: number;
16
+ certValidityDays?: number;
17
+ };
18
+ }
19
+
20
+ // ============================================================================
21
+ // Pluggable storage interfaces
22
+ // ============================================================================
23
+
24
+ export interface IServerAccountStore {
25
+ create(account: IServerAccount): Promise<IServerAccount>;
26
+ getByThumbprint(thumbprint: string): Promise<IServerAccount | null>;
27
+ getByUrl(url: string): Promise<IServerAccount | null>;
28
+ }
29
+
30
+ export interface IServerOrderStore {
31
+ createOrder(order: IServerOrder): Promise<IServerOrder>;
32
+ getOrder(id: string): Promise<IServerOrder | null>;
33
+ updateOrder(id: string, updates: Partial<IServerOrder>): Promise<void>;
34
+ createAuthorization(authz: IServerAuthorization): Promise<IServerAuthorization>;
35
+ getAuthorization(id: string): Promise<IServerAuthorization | null>;
36
+ updateAuthorization(id: string, updates: Partial<IServerAuthorization>): Promise<void>;
37
+ createChallenge(challenge: IServerChallenge): Promise<IServerChallenge>;
38
+ getChallenge(id: string): Promise<IServerChallenge | null>;
39
+ updateChallenge(id: string, updates: Partial<IServerChallenge>): Promise<void>;
40
+ storeCertPem(orderId: string, pem: string): Promise<void>;
41
+ getCertPem(orderId: string): Promise<string | null>;
42
+ }
43
+
44
+ // ============================================================================
45
+ // Internal server models
46
+ // ============================================================================
47
+
48
+ export interface IServerAccount {
49
+ id: string;
50
+ thumbprint: string;
51
+ url: string;
52
+ jwk: Record<string, string>;
53
+ status: string;
54
+ contact: string[];
55
+ createdAt: string;
56
+ }
57
+
58
+ export interface IServerOrder {
59
+ id: string;
60
+ accountUrl: string;
61
+ status: string;
62
+ identifiers: IAcmeIdentifier[];
63
+ authorizationIds: string[];
64
+ expires: string;
65
+ finalize: string;
66
+ certificate?: string;
67
+ }
68
+
69
+ export interface IServerAuthorization {
70
+ id: string;
71
+ orderId: string;
72
+ identifier: IAcmeIdentifier;
73
+ status: string;
74
+ expires: string;
75
+ challengeIds: string[];
76
+ wildcard?: boolean;
77
+ }
78
+
79
+ export interface IServerChallenge {
80
+ id: string;
81
+ authorizationId: string;
82
+ type: string;
83
+ token: string;
84
+ status: string;
85
+ validated?: string;
86
+ error?: { type: string; detail: string };
87
+ }
88
+
89
+ // ============================================================================
90
+ // Route handler type
91
+ // ============================================================================
92
+
93
+ export type TRouteHandler = (
94
+ req: import('node:http').IncomingMessage,
95
+ res: import('node:http').ServerResponse,
96
+ params: Record<string, string>,
97
+ body: any,
98
+ ) => Promise<void>;