@push.rocks/smartacme 9.1.2 → 9.2.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 (88) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  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 +2 -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 +11 -11
  67. package/ts/00_commitinfo_data.ts +1 -1
  68. package/ts/acme/acme.classes.crypto.ts +1 -1
  69. package/ts/index.ts +4 -0
  70. package/ts/smartacme.classes.smartacme.ts +1 -0
  71. package/ts_server/index.ts +12 -0
  72. package/ts_server/server.classes.account.store.ts +27 -0
  73. package/ts_server/server.classes.acmeserver.ts +128 -0
  74. package/ts_server/server.classes.ca.ts +142 -0
  75. package/ts_server/server.classes.challenge.verifier.ts +61 -0
  76. package/ts_server/server.classes.jws.verifier.ts +153 -0
  77. package/ts_server/server.classes.nonce.ts +36 -0
  78. package/ts_server/server.classes.order.store.ts +72 -0
  79. package/ts_server/server.classes.router.ts +116 -0
  80. package/ts_server/server.handlers.account.ts +85 -0
  81. package/ts_server/server.handlers.authz.ts +58 -0
  82. package/ts_server/server.handlers.cert.ts +32 -0
  83. package/ts_server/server.handlers.challenge.ts +142 -0
  84. package/ts_server/server.handlers.directory.ts +30 -0
  85. package/ts_server/server.handlers.finalize.ts +93 -0
  86. package/ts_server/server.handlers.nonce.ts +29 -0
  87. package/ts_server/server.handlers.order.ts +177 -0
  88. package/ts_server/server.interfaces.ts +98 -0
@@ -0,0 +1,142 @@
1
+ import type * as http from 'node:http';
2
+ import * as crypto from 'node:crypto';
3
+ import { AcmeCrypto } from '../ts/acme/acme.classes.crypto.js';
4
+ import type { JwsVerifier } from './server.classes.jws.verifier.js';
5
+ import { AcmeServerError } from './server.classes.jws.verifier.js';
6
+ import type { IServerOrderStore, IServerAccountStore } from './server.interfaces.js';
7
+ import type { ChallengeVerifier } from './server.classes.challenge.verifier.js';
8
+
9
+ /**
10
+ * POST /challenge/:id — Trigger or poll an ACME challenge.
11
+ * - POST with `{}` payload: trigger challenge validation
12
+ * - POST-as-GET (null payload): return current challenge state
13
+ */
14
+ export function createChallengeHandler(
15
+ baseUrl: string,
16
+ jwsVerifier: JwsVerifier,
17
+ orderStore: IServerOrderStore,
18
+ accountStore: IServerAccountStore,
19
+ challengeVerifier: ChallengeVerifier,
20
+ ) {
21
+ return async (
22
+ req: http.IncomingMessage,
23
+ res: http.ServerResponse,
24
+ params: Record<string, string>,
25
+ body: any,
26
+ ): Promise<void> => {
27
+ const challengeId = params.id;
28
+ const requestUrl = `${baseUrl}/challenge/${challengeId}`;
29
+ const verified = await jwsVerifier.verify(body, requestUrl);
30
+
31
+ const challenge = await orderStore.getChallenge(challengeId);
32
+ if (!challenge) {
33
+ throw new AcmeServerError(404, 'urn:ietf:params:acme:error:malformed', 'Challenge not found');
34
+ }
35
+
36
+ // POST-as-GET: just return current state
37
+ if (verified.payload === null) {
38
+ sendChallengeResponse(res, challenge, baseUrl);
39
+ return;
40
+ }
41
+
42
+ // Trigger validation (payload should be `{}`)
43
+ if (challenge.status !== 'pending') {
44
+ // Already processing or completed
45
+ sendChallengeResponse(res, challenge, baseUrl);
46
+ return;
47
+ }
48
+
49
+ // Set to processing
50
+ await orderStore.updateChallenge(challengeId, { status: 'processing' });
51
+
52
+ // Get the authorization to find the domain
53
+ const authz = await orderStore.getAuthorization(challenge.authorizationId);
54
+ if (!authz) {
55
+ throw new AcmeServerError(500, 'urn:ietf:params:acme:error:serverInternal', 'Authorization not found');
56
+ }
57
+
58
+ // Resolve the account's JWK for key authorization computation
59
+ const account = await accountStore.getByUrl(verified.kid!);
60
+ if (!account) {
61
+ throw new AcmeServerError(400, 'urn:ietf:params:acme:error:accountDoesNotExist', 'Account not found');
62
+ }
63
+
64
+ const thumbprint = AcmeCrypto.getJwkThumbprint(account.jwk);
65
+ const keyAuth = `${challenge.token}.${thumbprint}`;
66
+
67
+ // Verify the challenge
68
+ let valid = false;
69
+ const domain = authz.identifier.value;
70
+
71
+ if (challenge.type === 'http-01') {
72
+ valid = await challengeVerifier.verifyHttp01(domain, challenge.token, keyAuth);
73
+ } else if (challenge.type === 'dns-01') {
74
+ const hash = crypto.createHash('sha256').update(keyAuth).digest('base64url');
75
+ valid = await challengeVerifier.verifyDns01(domain, hash);
76
+ }
77
+
78
+ if (valid) {
79
+ await orderStore.updateChallenge(challengeId, {
80
+ status: 'valid',
81
+ validated: new Date().toISOString(),
82
+ });
83
+ challenge.status = 'valid';
84
+ challenge.validated = new Date().toISOString();
85
+
86
+ // Check if all challenges for this authorization's required type are valid
87
+ // One valid challenge is enough to validate the authorization
88
+ await orderStore.updateAuthorization(authz.id, { status: 'valid' });
89
+
90
+ // Check if all authorizations for the order are now valid
91
+ const order = await orderStore.getOrder(authz.orderId);
92
+ if (order && order.status === 'pending') {
93
+ let allValid = true;
94
+ for (const authzId of order.authorizationIds) {
95
+ const a = await orderStore.getAuthorization(authzId);
96
+ if (!a || a.status !== 'valid') {
97
+ allValid = false;
98
+ break;
99
+ }
100
+ }
101
+ if (allValid) {
102
+ await orderStore.updateOrder(order.id, { status: 'ready' });
103
+ }
104
+ }
105
+ } else {
106
+ await orderStore.updateChallenge(challengeId, {
107
+ status: 'invalid',
108
+ error: {
109
+ type: 'urn:ietf:params:acme:error:incorrectResponse',
110
+ detail: `Challenge verification failed for ${domain}`,
111
+ },
112
+ });
113
+ challenge.status = 'invalid';
114
+
115
+ // Mark authorization as invalid too
116
+ await orderStore.updateAuthorization(authz.id, { status: 'invalid' });
117
+ }
118
+
119
+ sendChallengeResponse(res, challenge, baseUrl);
120
+ };
121
+ }
122
+
123
+ function sendChallengeResponse(
124
+ res: http.ServerResponse,
125
+ challenge: { id: string; type: string; status: string; token: string; validated?: string },
126
+ baseUrl: string,
127
+ ): void {
128
+ const responseBody: Record<string, any> = {
129
+ type: challenge.type,
130
+ url: `${baseUrl}/challenge/${challenge.id}`,
131
+ status: challenge.status,
132
+ token: challenge.token,
133
+ };
134
+ if (challenge.validated) {
135
+ responseBody.validated = challenge.validated;
136
+ }
137
+ res.writeHead(200, {
138
+ 'Content-Type': 'application/json',
139
+ 'Link': `<${baseUrl}/directory>;rel="index"`,
140
+ });
141
+ res.end(JSON.stringify(responseBody));
142
+ }
@@ -0,0 +1,30 @@
1
+ import type * as http from 'node:http';
2
+ import type { IAcmeDirectory } from '../ts/acme/acme.interfaces.js';
3
+
4
+ /**
5
+ * GET /directory — Returns the ACME directory object with all endpoint URLs.
6
+ */
7
+ export function createDirectoryHandler(baseUrl: string) {
8
+ return async (
9
+ _req: http.IncomingMessage,
10
+ res: http.ServerResponse,
11
+ _params: Record<string, string>,
12
+ _body: any,
13
+ ): Promise<void> => {
14
+ const directory: IAcmeDirectory = {
15
+ newNonce: `${baseUrl}/new-nonce`,
16
+ newAccount: `${baseUrl}/new-account`,
17
+ newOrder: `${baseUrl}/new-order`,
18
+ revokeCert: `${baseUrl}/revoke-cert`,
19
+ keyChange: `${baseUrl}/key-change`,
20
+ meta: {
21
+ termsOfService: `${baseUrl}/terms`,
22
+ website: `${baseUrl}`,
23
+ caaIdentities: [],
24
+ externalAccountRequired: false,
25
+ },
26
+ };
27
+ res.writeHead(200, { 'Content-Type': 'application/json' });
28
+ res.end(JSON.stringify(directory));
29
+ };
30
+ }
@@ -0,0 +1,93 @@
1
+ import type * as http from 'node:http';
2
+ import type { JwsVerifier } from './server.classes.jws.verifier.js';
3
+ import { AcmeServerError } from './server.classes.jws.verifier.js';
4
+ import type { IServerOrderStore } from './server.interfaces.js';
5
+ import type { AcmeServerCA } from './server.classes.ca.js';
6
+
7
+ /**
8
+ * POST /finalize/:id — Submit CSR and issue certificate.
9
+ */
10
+ export function createFinalizeHandler(
11
+ baseUrl: string,
12
+ jwsVerifier: JwsVerifier,
13
+ orderStore: IServerOrderStore,
14
+ ca: AcmeServerCA,
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 orderId = params.id;
23
+ const requestUrl = `${baseUrl}/finalize/${orderId}`;
24
+ const verified = await jwsVerifier.verify(body, requestUrl);
25
+
26
+ if (!verified.kid) {
27
+ throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Finalize requires kid');
28
+ }
29
+
30
+ const order = await orderStore.getOrder(orderId);
31
+ if (!order) {
32
+ throw new AcmeServerError(404, 'urn:ietf:params:acme:error:malformed', 'Order not found');
33
+ }
34
+
35
+ // Check all authorizations are valid and update order status if needed
36
+ if (order.status === 'pending') {
37
+ let allValid = true;
38
+ for (const authzId of order.authorizationIds) {
39
+ const authz = await orderStore.getAuthorization(authzId);
40
+ if (!authz || authz.status !== 'valid') {
41
+ allValid = false;
42
+ break;
43
+ }
44
+ }
45
+ if (allValid) {
46
+ await orderStore.updateOrder(orderId, { status: 'ready' });
47
+ order.status = 'ready';
48
+ }
49
+ }
50
+
51
+ if (order.status !== 'ready') {
52
+ throw new AcmeServerError(
53
+ 403,
54
+ 'urn:ietf:params:acme:error:orderNotReady',
55
+ `Order is in "${order.status}" state, expected "ready"`,
56
+ );
57
+ }
58
+
59
+ const { payload } = verified;
60
+ if (!payload?.csr) {
61
+ throw new AcmeServerError(400, 'urn:ietf:params:acme:error:malformed', 'Missing CSR in finalize request');
62
+ }
63
+
64
+ // Transition to processing
65
+ await orderStore.updateOrder(orderId, { status: 'processing' });
66
+
67
+ // Sign the certificate
68
+ const certPem = await ca.signCsr(payload.csr);
69
+
70
+ // Store certificate and update order
71
+ const certUrl = `${baseUrl}/cert/${orderId}`;
72
+ await orderStore.storeCertPem(orderId, certPem);
73
+ await orderStore.updateOrder(orderId, {
74
+ status: 'valid',
75
+ certificate: certUrl,
76
+ });
77
+
78
+ const responseBody = {
79
+ status: 'valid',
80
+ expires: order.expires,
81
+ identifiers: order.identifiers,
82
+ authorizations: order.authorizationIds.map((id) => `${baseUrl}/authz/${id}`),
83
+ finalize: order.finalize,
84
+ certificate: certUrl,
85
+ };
86
+
87
+ res.writeHead(200, {
88
+ 'Content-Type': 'application/json',
89
+ 'Location': `${baseUrl}/order/${orderId}`,
90
+ });
91
+ res.end(JSON.stringify(responseBody));
92
+ };
93
+ }
@@ -0,0 +1,29 @@
1
+ import type * as http from 'node:http';
2
+
3
+ /**
4
+ * HEAD /new-nonce — Returns 200 with Replay-Nonce header (added by router).
5
+ * GET /new-nonce — Returns 204 with Replay-Nonce header (added by router).
6
+ */
7
+ export function createNonceHeadHandler() {
8
+ return async (
9
+ _req: http.IncomingMessage,
10
+ res: http.ServerResponse,
11
+ _params: Record<string, string>,
12
+ _body: any,
13
+ ): Promise<void> => {
14
+ res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
15
+ res.end();
16
+ };
17
+ }
18
+
19
+ export function createNonceGetHandler() {
20
+ return async (
21
+ _req: http.IncomingMessage,
22
+ res: http.ServerResponse,
23
+ _params: Record<string, string>,
24
+ _body: any,
25
+ ): Promise<void> => {
26
+ res.writeHead(204);
27
+ res.end();
28
+ };
29
+ }
@@ -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>;