vpsnet-mcp 1.0.9 → 2.1.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.
- package/README.md +487 -20
- package/build/api-configuration.js +59 -0
- package/build/api.js +179 -10
- package/build/application-contract.js +810 -0
- package/build/auth-failure.js +141 -0
- package/build/certificate-contract.js +1614 -0
- package/build/custom-project-contract.js +474 -0
- package/build/file-restore-contract.js +246 -0
- package/build/index.js +3617 -125
- package/build/service-rescue-contract.js +97 -0
- package/build/temp-vm-contract.js +284 -0
- package/package.json +21 -6
|
@@ -0,0 +1,1614 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const certificateOrderIdSchema = z
|
|
3
|
+
.string()
|
|
4
|
+
.regex(/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/)
|
|
5
|
+
.describe("Customer-owned certificate order UUID returned by list_certificates");
|
|
6
|
+
export const certificateProductIdSchema = z
|
|
7
|
+
.number()
|
|
8
|
+
.int()
|
|
9
|
+
.positive()
|
|
10
|
+
.describe("Published product ID returned by list_certificate_catalog");
|
|
11
|
+
export const certificateOfferIdSchema = z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.positive()
|
|
15
|
+
.describe("Published EUR offer ID returned by the selected catalog product");
|
|
16
|
+
export const certificateOfferGenerationSchema = z
|
|
17
|
+
.number()
|
|
18
|
+
.int()
|
|
19
|
+
.positive()
|
|
20
|
+
.describe("Exact offer generation returned by the catalog; stale generations are rejected");
|
|
21
|
+
export const certificateSubscriptionIdSchema = z
|
|
22
|
+
.string()
|
|
23
|
+
.regex(/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/)
|
|
24
|
+
.describe("Customer-owned Automatic SSL subscription UUID returned by list_automatic_ssl_subscriptions");
|
|
25
|
+
export const freeCertificateRequestIdSchema = z
|
|
26
|
+
.string()
|
|
27
|
+
.regex(/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/)
|
|
28
|
+
.describe("Customer-owned free-certificate request UUID returned by list_free_certificates");
|
|
29
|
+
export const freeCertificateStateSchema = z.enum([
|
|
30
|
+
"draft",
|
|
31
|
+
"pending_validation",
|
|
32
|
+
"validating",
|
|
33
|
+
"issuing",
|
|
34
|
+
"issued",
|
|
35
|
+
"renewing",
|
|
36
|
+
"failed",
|
|
37
|
+
"cancelled",
|
|
38
|
+
"revoked",
|
|
39
|
+
"expired",
|
|
40
|
+
]);
|
|
41
|
+
export const freeCertificateAuthoritySchema = z.enum(["letsencrypt", "zerossl", "google"]);
|
|
42
|
+
export const certificateDnsIdentifierSchema = z
|
|
43
|
+
.string()
|
|
44
|
+
.min(3)
|
|
45
|
+
.max(253)
|
|
46
|
+
.refine((identifier) => {
|
|
47
|
+
if (identifier !== identifier.toLowerCase() || identifier !== identifier.trim()) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const hostname = identifier.startsWith("*.") ? identifier.slice(2) : identifier;
|
|
51
|
+
const labels = hostname.split(".");
|
|
52
|
+
return labels.length >= 2 && labels.every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label));
|
|
53
|
+
}, "Use a canonical lowercase DNS name or a single leading wildcard");
|
|
54
|
+
const certificateAcmeDomainsSchema = z
|
|
55
|
+
.array(certificateDnsIdentifierSchema)
|
|
56
|
+
.min(1)
|
|
57
|
+
.max(255)
|
|
58
|
+
.superRefine((domains, context) => {
|
|
59
|
+
const names = new Set();
|
|
60
|
+
for (const [index, domain] of domains.entries()) {
|
|
61
|
+
if (names.has(domain)) {
|
|
62
|
+
context.addIssue({
|
|
63
|
+
code: z.ZodIssueCode.custom,
|
|
64
|
+
path: [index],
|
|
65
|
+
message: "Automatic SSL DNS names must be unique",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
names.add(domain);
|
|
69
|
+
}
|
|
70
|
+
for (const [index, domain] of domains.entries()) {
|
|
71
|
+
if (domain.startsWith("www.") && names.has(domain.slice(4))) {
|
|
72
|
+
context.addIssue({
|
|
73
|
+
code: z.ZodIssueCode.custom,
|
|
74
|
+
path: [index],
|
|
75
|
+
message: "Do not add www separately when its base domain is subscribed",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.describe("Canonical subscribed DNS names. A base name includes its www alias; an apex and wildcard are separate names and may be ordered together.");
|
|
81
|
+
export const certificateAcmeSubscriptionInputShape = {
|
|
82
|
+
product_id: certificateProductIdSchema,
|
|
83
|
+
offer_id: certificateOfferIdSchema,
|
|
84
|
+
offer_generation: certificateOfferGenerationSchema,
|
|
85
|
+
auto_renew: z
|
|
86
|
+
.boolean()
|
|
87
|
+
.describe("Explicitly choose whether the subscription remains eligible for separately prepaid future terms"),
|
|
88
|
+
domains: certificateAcmeDomainsSchema,
|
|
89
|
+
};
|
|
90
|
+
export const certificateAcmeSubscriptionDomainInputShape = {
|
|
91
|
+
domain: certificateDnsIdentifierSchema.describe("One canonical DNS name to add. A base name includes www; a wildcard includes its base name."),
|
|
92
|
+
};
|
|
93
|
+
export const certificateAcmeSubscriptionActionRequestSchema = z.discriminatedUnion("action", [
|
|
94
|
+
z.object({
|
|
95
|
+
action: z.literal("cancel"),
|
|
96
|
+
reason: z.string().trim().min(3).max(500),
|
|
97
|
+
}).strict(),
|
|
98
|
+
z.object({
|
|
99
|
+
action: z.literal("remove_domain"),
|
|
100
|
+
domain_id: z.number().int().positive(),
|
|
101
|
+
}).strict(),
|
|
102
|
+
z.object({
|
|
103
|
+
action: z.literal("replace_domain"),
|
|
104
|
+
domain_id: z.number().int().positive(),
|
|
105
|
+
new_domain: certificateDnsIdentifierSchema,
|
|
106
|
+
}).strict(),
|
|
107
|
+
]);
|
|
108
|
+
export function certificateAcmeSubscriptionActionRequestBody(request) {
|
|
109
|
+
const { action, ...body } = request;
|
|
110
|
+
return { action, body };
|
|
111
|
+
}
|
|
112
|
+
const validationMethodSchema = z
|
|
113
|
+
.string()
|
|
114
|
+
.min(3)
|
|
115
|
+
.max(190)
|
|
116
|
+
.refine((method) => ["dns-txt", "dns-cname", "http", "https"].includes(method)
|
|
117
|
+
|| /^(?:admin|administrator|hostmaster|postmaster|webmaster)@[a-z0-9](?:[a-z0-9.-]{1,251}[a-z0-9])$/.test(method), "Use DNS, HTTP/HTTPS, or an allowed administrative mailbox validation method");
|
|
118
|
+
export const certificateIdentifierSchema = z
|
|
119
|
+
.object({
|
|
120
|
+
name: certificateDnsIdentifierSchema,
|
|
121
|
+
validation_method: validationMethodSchema,
|
|
122
|
+
})
|
|
123
|
+
.strict()
|
|
124
|
+
.superRefine((identifier, context) => {
|
|
125
|
+
const hostname = identifier.name.startsWith("*.")
|
|
126
|
+
? identifier.name.slice(2)
|
|
127
|
+
: identifier.name;
|
|
128
|
+
if (identifier.name.startsWith("*.")
|
|
129
|
+
&& !["dns-txt", "dns-cname"].includes(identifier.validation_method)) {
|
|
130
|
+
context.addIssue({
|
|
131
|
+
code: z.ZodIssueCode.custom,
|
|
132
|
+
path: ["validation_method"],
|
|
133
|
+
message: "Wildcard certificates require DNS validation",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (identifier.validation_method.includes("@")) {
|
|
137
|
+
const emailDomain = identifier.validation_method.split("@")[1];
|
|
138
|
+
if (emailDomain !== hostname) {
|
|
139
|
+
context.addIssue({
|
|
140
|
+
code: z.ZodIssueCode.custom,
|
|
141
|
+
path: ["validation_method"],
|
|
142
|
+
message: "Administrative validation email must use the exact certificate hostname",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
export const certificateCsrSchema = z
|
|
148
|
+
.string()
|
|
149
|
+
.min(128)
|
|
150
|
+
.max(65536)
|
|
151
|
+
.refine((value) => /^-----BEGIN CERTIFICATE REQUEST-----\n(?:[A-Za-z0-9+/=]+\n)+-----END CERTIFICATE REQUEST-----\n?$/.test(value.replace(/\r\n/g, "\n").trimEnd() + "\n") && !value.includes("PRIVATE KEY"), "Provide only a PEM PKCS#10 certificate signing request; never provide a private key")
|
|
152
|
+
.describe("Customer-generated PEM PKCS#10 CSR. The private key must remain with the customer and must never be sent to VPSnet or an AI assistant.");
|
|
153
|
+
const freeCertificateAlternativeNamesSchema = z
|
|
154
|
+
.array(certificateDnsIdentifierSchema)
|
|
155
|
+
.max(99)
|
|
156
|
+
.superRefine((identifiers, context) => {
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
for (const [index, identifier] of identifiers.entries()) {
|
|
159
|
+
if (seen.has(identifier)) {
|
|
160
|
+
context.addIssue({
|
|
161
|
+
code: z.ZodIssueCode.custom,
|
|
162
|
+
path: [index],
|
|
163
|
+
message: "Free-certificate DNS names must be unique",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
seen.add(identifier);
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
.default([])
|
|
170
|
+
.describe("Optional unique lowercase DNS subject alternative names");
|
|
171
|
+
const freeCertificateTargetOrderIdSchema = z
|
|
172
|
+
.number()
|
|
173
|
+
.int()
|
|
174
|
+
.positive()
|
|
175
|
+
.nullable()
|
|
176
|
+
.optional()
|
|
177
|
+
.describe("Optional owned VPS or Cloud VPS order ID returned by service tools");
|
|
178
|
+
export const freeCertificatePreflightInputShape = {
|
|
179
|
+
hostname: certificateDnsIdentifierSchema.describe("Primary lowercase DNS name; wildcards begin with *."),
|
|
180
|
+
alternative_names: freeCertificateAlternativeNamesSchema,
|
|
181
|
+
target_order_id: freeCertificateTargetOrderIdSchema,
|
|
182
|
+
certificate_authority: freeCertificateAuthoritySchema
|
|
183
|
+
.nullable()
|
|
184
|
+
.optional()
|
|
185
|
+
.describe("Optional CA choice; omit to let preflight recommend a currently ready authority"),
|
|
186
|
+
};
|
|
187
|
+
export const freeCertificatePreflightRequestSchema = z
|
|
188
|
+
.object(freeCertificatePreflightInputShape)
|
|
189
|
+
.strict()
|
|
190
|
+
.superRefine((request, context) => {
|
|
191
|
+
if (request.alternative_names.includes(request.hostname)) {
|
|
192
|
+
context.addIssue({
|
|
193
|
+
code: z.ZodIssueCode.custom,
|
|
194
|
+
path: ["alternative_names"],
|
|
195
|
+
message: "Do not repeat the primary hostname as an alternative name",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
export const freeCertificateCreateInputShape = {
|
|
200
|
+
...freeCertificatePreflightInputShape,
|
|
201
|
+
key_mode: z.enum(["managed", "customer_csr"]).describe("managed enables unattended renewal but its private key stays portal/2FA-only; customer_csr keeps the private key with the caller"),
|
|
202
|
+
csr: certificateCsrSchema
|
|
203
|
+
.nullable()
|
|
204
|
+
.optional()
|
|
205
|
+
.describe("Required only for customer_csr. Supply a public PKCS#10 CSR, never a private key"),
|
|
206
|
+
validation_method: z.enum(["dns_zone", "dns_cname"]).describe("Choose only a validation method that preflight reports available"),
|
|
207
|
+
delivery_method: z.enum(["download", "auto_install_guest", "assistant_install"]).describe("Choose only a delivery method that preflight reports available"),
|
|
208
|
+
};
|
|
209
|
+
export const freeCertificateCreateRequestSchema = z
|
|
210
|
+
.object(freeCertificateCreateInputShape)
|
|
211
|
+
.strict()
|
|
212
|
+
.superRefine((request, context) => {
|
|
213
|
+
if (request.alternative_names.includes(request.hostname)) {
|
|
214
|
+
context.addIssue({
|
|
215
|
+
code: z.ZodIssueCode.custom,
|
|
216
|
+
path: ["alternative_names"],
|
|
217
|
+
message: "Do not repeat the primary hostname as an alternative name",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (request.key_mode === "customer_csr" && !request.csr) {
|
|
221
|
+
context.addIssue({
|
|
222
|
+
code: z.ZodIssueCode.custom,
|
|
223
|
+
path: ["csr"],
|
|
224
|
+
message: "customer_csr mode requires a public PKCS#10 CSR",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (request.key_mode === "managed" && request.csr != null) {
|
|
228
|
+
context.addIssue({
|
|
229
|
+
code: z.ZodIssueCode.custom,
|
|
230
|
+
path: ["csr"],
|
|
231
|
+
message: "Managed mode does not accept a CSR",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
export const freeCertificateActionRequestSchema = z.discriminatedUnion("action", [
|
|
236
|
+
z.object({
|
|
237
|
+
action: z.literal("renew"),
|
|
238
|
+
csr: certificateCsrSchema.nullable().optional(),
|
|
239
|
+
}).strict(),
|
|
240
|
+
z.object({
|
|
241
|
+
action: z.literal("revoke"),
|
|
242
|
+
reason: z.enum(["unspecified", "key_compromise", "superseded", "cessation_of_operation"]),
|
|
243
|
+
}).strict(),
|
|
244
|
+
z.object({ action: z.literal("recheck") }).strict(),
|
|
245
|
+
z.object({ action: z.literal("cancel") }).strict(),
|
|
246
|
+
]);
|
|
247
|
+
export function freeCertificateActionRequestBody(request) {
|
|
248
|
+
const { action, ...body } = request;
|
|
249
|
+
return { action, body };
|
|
250
|
+
}
|
|
251
|
+
export const certificateOrderInputShape = {
|
|
252
|
+
product_id: certificateProductIdSchema,
|
|
253
|
+
offer_id: certificateOfferIdSchema,
|
|
254
|
+
offer_generation: certificateOfferGenerationSchema,
|
|
255
|
+
common_name: certificateIdentifierSchema.describe("Primary certificate name and its validation method"),
|
|
256
|
+
alternative_names: z
|
|
257
|
+
.array(certificateIdentifierSchema)
|
|
258
|
+
.max(100)
|
|
259
|
+
.default([])
|
|
260
|
+
.describe("Optional unique subject alternative names; pricing is returned by quote_certificate"),
|
|
261
|
+
csr: certificateCsrSchema,
|
|
262
|
+
administrator_contact_id: z
|
|
263
|
+
.number()
|
|
264
|
+
.int()
|
|
265
|
+
.positive()
|
|
266
|
+
.describe("Owned administrator contact ID selected in the VPSnet certificate order form"),
|
|
267
|
+
technical_contact_id: z
|
|
268
|
+
.number()
|
|
269
|
+
.int()
|
|
270
|
+
.positive()
|
|
271
|
+
.describe("Owned technical contact ID selected in the VPSnet certificate order form"),
|
|
272
|
+
organization_contact_id: z
|
|
273
|
+
.number()
|
|
274
|
+
.int()
|
|
275
|
+
.positive()
|
|
276
|
+
.nullable()
|
|
277
|
+
.optional()
|
|
278
|
+
.describe("Owned organization contact ID when required for OV or EV validation"),
|
|
279
|
+
renewal_of: certificateOrderIdSchema
|
|
280
|
+
.nullable()
|
|
281
|
+
.optional()
|
|
282
|
+
.describe("Existing eligible certificate order UUID when quoting a renewal"),
|
|
283
|
+
};
|
|
284
|
+
export const certificateActionRequestSchema = z.discriminatedUnion("action", [
|
|
285
|
+
z.object({
|
|
286
|
+
action: z.literal("cancel"),
|
|
287
|
+
reason: z.string().min(3).max(500),
|
|
288
|
+
}).strict(),
|
|
289
|
+
z.object({
|
|
290
|
+
action: z.literal("recheck_validation"),
|
|
291
|
+
domains: z.array(certificateDnsIdentifierSchema).min(1).max(100),
|
|
292
|
+
}).strict(),
|
|
293
|
+
z.object({ action: z.literal("resend_validation") }).strict(),
|
|
294
|
+
z.object({
|
|
295
|
+
action: z.literal("change_validation"),
|
|
296
|
+
domain: certificateDnsIdentifierSchema,
|
|
297
|
+
validation_method: validationMethodSchema,
|
|
298
|
+
}).strict(),
|
|
299
|
+
z.object({
|
|
300
|
+
action: z.literal("reissue"),
|
|
301
|
+
csr: certificateCsrSchema,
|
|
302
|
+
common_name: certificateIdentifierSchema,
|
|
303
|
+
alternative_names: z.array(certificateIdentifierSchema).max(99).default([]),
|
|
304
|
+
}).strict(),
|
|
305
|
+
]);
|
|
306
|
+
export function certificateActionRequestBody(request) {
|
|
307
|
+
const { action, ...body } = request;
|
|
308
|
+
return { action, body };
|
|
309
|
+
}
|
|
310
|
+
function record(value) {
|
|
311
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
312
|
+
? value
|
|
313
|
+
: {};
|
|
314
|
+
}
|
|
315
|
+
function text(value, maximum = 1024) {
|
|
316
|
+
if (typeof value !== "string" || value.length > maximum || value.includes("\0")) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
function integer(value) {
|
|
322
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0
|
|
323
|
+
? value
|
|
324
|
+
: null;
|
|
325
|
+
}
|
|
326
|
+
function nonNegativeInteger(value) {
|
|
327
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
328
|
+
? value
|
|
329
|
+
: null;
|
|
330
|
+
}
|
|
331
|
+
function nullableInteger(value) {
|
|
332
|
+
return value === null ? null : integer(value);
|
|
333
|
+
}
|
|
334
|
+
function boolean(value) {
|
|
335
|
+
return value === true;
|
|
336
|
+
}
|
|
337
|
+
function timestamp(value) {
|
|
338
|
+
return value === null ? null : text(value, 64);
|
|
339
|
+
}
|
|
340
|
+
function money(value) {
|
|
341
|
+
return typeof value === "string" && /^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,4})?$/.test(value)
|
|
342
|
+
? value
|
|
343
|
+
: null;
|
|
344
|
+
}
|
|
345
|
+
function safeIdentifier(value) {
|
|
346
|
+
const source = record(value);
|
|
347
|
+
const parsed = certificateIdentifierSchema.safeParse(source);
|
|
348
|
+
return parsed.success ? parsed.data : null;
|
|
349
|
+
}
|
|
350
|
+
function safeOffer(value) {
|
|
351
|
+
const source = record(value);
|
|
352
|
+
const id = integer(source.id);
|
|
353
|
+
const generation = integer(source.generation);
|
|
354
|
+
const termMonths = integer(source.term_months);
|
|
355
|
+
const nameKind = source.name_kind === "single" || source.name_kind === "wildcard"
|
|
356
|
+
? source.name_kind
|
|
357
|
+
: null;
|
|
358
|
+
const basePrice = money(source.base_price);
|
|
359
|
+
const singlePrice = source.san_single_price === null ? null : money(source.san_single_price);
|
|
360
|
+
const wildcardPrice = source.san_wildcard_price === null ? null : money(source.san_wildcard_price);
|
|
361
|
+
if (id === null
|
|
362
|
+
|| generation === null
|
|
363
|
+
|| termMonths === null
|
|
364
|
+
|| nameKind === null
|
|
365
|
+
|| source.currency !== "EUR"
|
|
366
|
+
|| basePrice === null
|
|
367
|
+
|| (source.san_single_price !== null && singlePrice === null)
|
|
368
|
+
|| (source.san_wildcard_price !== null && wildcardPrice === null)) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
id,
|
|
373
|
+
generation,
|
|
374
|
+
term_months: termMonths,
|
|
375
|
+
name_kind: nameKind,
|
|
376
|
+
currency: "EUR",
|
|
377
|
+
base_price: basePrice,
|
|
378
|
+
san_single_price: singlePrice,
|
|
379
|
+
san_wildcard_price: wildcardPrice,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function safeProduct(value) {
|
|
383
|
+
const source = record(value);
|
|
384
|
+
const id = integer(source.id);
|
|
385
|
+
const label = text(source.label, 255);
|
|
386
|
+
const category = source.category === "caas" || source.category === "tls"
|
|
387
|
+
? source.category
|
|
388
|
+
: null;
|
|
389
|
+
const fulfillment = source.fulfillment === "acme_subscription"
|
|
390
|
+
|| source.fulfillment === "portable_certificate"
|
|
391
|
+
? source.fulfillment
|
|
392
|
+
: null;
|
|
393
|
+
const validationType = ["DV", "OV", "EV"].includes(String(source.validation_type))
|
|
394
|
+
? String(source.validation_type)
|
|
395
|
+
: null;
|
|
396
|
+
if (id === null
|
|
397
|
+
|| label === null
|
|
398
|
+
|| category === null
|
|
399
|
+
|| fulfillment === null
|
|
400
|
+
|| validationType === null
|
|
401
|
+
|| (category === "caas") !== (fulfillment === "acme_subscription"))
|
|
402
|
+
return null;
|
|
403
|
+
const capabilities = record(source.capabilities);
|
|
404
|
+
const management = record(capabilities.management);
|
|
405
|
+
const commonName = record(capabilities.common_name);
|
|
406
|
+
const san = record(capabilities.san);
|
|
407
|
+
const offers = Array.isArray(source.offers)
|
|
408
|
+
? source.offers.map(safeOffer).filter((offer) => offer !== null)
|
|
409
|
+
: [];
|
|
410
|
+
const includedSingle = nonNegativeInteger(san.included_single);
|
|
411
|
+
const includedWildcard = nonNegativeInteger(san.included_wildcard);
|
|
412
|
+
const minimum = nonNegativeInteger(san.min);
|
|
413
|
+
const maximum = nonNegativeInteger(san.max);
|
|
414
|
+
if (includedSingle === null
|
|
415
|
+
|| includedWildcard === null
|
|
416
|
+
|| minimum === null
|
|
417
|
+
|| maximum === null
|
|
418
|
+
|| maximum < minimum)
|
|
419
|
+
return null;
|
|
420
|
+
return {
|
|
421
|
+
id,
|
|
422
|
+
label,
|
|
423
|
+
category,
|
|
424
|
+
fulfillment,
|
|
425
|
+
validation_type: validationType,
|
|
426
|
+
brand: source.brand === null ? null : text(source.brand, 128),
|
|
427
|
+
capabilities: {
|
|
428
|
+
management: {
|
|
429
|
+
cancel: boolean(management.cancel),
|
|
430
|
+
reissue: boolean(management.reissue),
|
|
431
|
+
renew: boolean(management.renew),
|
|
432
|
+
revoke: boolean(management.revoke),
|
|
433
|
+
acme_credentials: boolean(management.acme_credentials),
|
|
434
|
+
domains: boolean(management.domains),
|
|
435
|
+
subscription_renewal: boolean(management.subscription_renewal),
|
|
436
|
+
},
|
|
437
|
+
common_name: {
|
|
438
|
+
single: boolean(commonName.single),
|
|
439
|
+
wildcard: boolean(commonName.wildcard),
|
|
440
|
+
ip: boolean(commonName.ip),
|
|
441
|
+
},
|
|
442
|
+
san: {
|
|
443
|
+
included_single: includedSingle,
|
|
444
|
+
included_wildcard: includedWildcard,
|
|
445
|
+
min: minimum,
|
|
446
|
+
max: maximum,
|
|
447
|
+
single: boolean(san.single),
|
|
448
|
+
wildcard: boolean(san.wildcard),
|
|
449
|
+
ip: boolean(san.ip),
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
offers,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function safeOrderDomain(value) {
|
|
456
|
+
const source = record(value);
|
|
457
|
+
const identifier = text(source.identifier, 253);
|
|
458
|
+
if (identifier === null)
|
|
459
|
+
return null;
|
|
460
|
+
return {
|
|
461
|
+
identifier,
|
|
462
|
+
name_kind: source.name_kind === "wildcard" ? "wildcard" : "single",
|
|
463
|
+
validation_method: text(source.validation_method, 253),
|
|
464
|
+
approved: boolean(source.approved),
|
|
465
|
+
validated_at: timestamp(source.validated_at),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function safeOrder(value) {
|
|
469
|
+
const source = record(value);
|
|
470
|
+
const id = certificateOrderIdSchema.safeParse(source.id);
|
|
471
|
+
const product = record(source.product);
|
|
472
|
+
const productId = integer(product.id);
|
|
473
|
+
const productLabel = text(product.label, 255);
|
|
474
|
+
const validationType = ["DV", "OV", "EV"].includes(String(product.validation_type))
|
|
475
|
+
? String(product.validation_type)
|
|
476
|
+
: null;
|
|
477
|
+
const termMonths = integer(product.term_months);
|
|
478
|
+
const amount = record(source.amount);
|
|
479
|
+
const commonName = text(source.common_name, 253);
|
|
480
|
+
const net = money(amount.net);
|
|
481
|
+
const billingState = text(source.billing_state, 64);
|
|
482
|
+
const state = text(source.state, 64);
|
|
483
|
+
const artifactState = text(source.artifact_state, 64);
|
|
484
|
+
const createdAt = timestamp(source.created_at);
|
|
485
|
+
const updatedAt = timestamp(source.updated_at);
|
|
486
|
+
if (!id.success
|
|
487
|
+
|| productId === null
|
|
488
|
+
|| productLabel === null
|
|
489
|
+
|| validationType === null
|
|
490
|
+
|| termMonths === null
|
|
491
|
+
|| commonName === null
|
|
492
|
+
|| source.deployment_mode !== "customer_csr"
|
|
493
|
+
|| !["order", "renew"].includes(String(source.operation))
|
|
494
|
+
|| amount.currency !== "EUR"
|
|
495
|
+
|| net === null
|
|
496
|
+
|| billingState === null
|
|
497
|
+
|| state === null
|
|
498
|
+
|| artifactState === null
|
|
499
|
+
|| createdAt === null
|
|
500
|
+
|| updatedAt === null)
|
|
501
|
+
return null;
|
|
502
|
+
return {
|
|
503
|
+
id: id.data,
|
|
504
|
+
product: {
|
|
505
|
+
id: productId,
|
|
506
|
+
label: productLabel,
|
|
507
|
+
validation_type: validationType,
|
|
508
|
+
term_months: termMonths,
|
|
509
|
+
},
|
|
510
|
+
common_name: commonName,
|
|
511
|
+
domains: Array.isArray(source.domains)
|
|
512
|
+
? source.domains.map(safeOrderDomain).filter((domain) => domain !== null)
|
|
513
|
+
: [],
|
|
514
|
+
deployment_mode: "customer_csr",
|
|
515
|
+
operation: source.operation,
|
|
516
|
+
renewal_of: source.renewal_of === null ? null : text(source.renewal_of, 36),
|
|
517
|
+
amount: {
|
|
518
|
+
currency: "EUR",
|
|
519
|
+
net,
|
|
520
|
+
},
|
|
521
|
+
billing_state: billingState,
|
|
522
|
+
payment_id: nullableInteger(source.payment_id),
|
|
523
|
+
refund_payment_id: nullableInteger(source.refund_payment_id),
|
|
524
|
+
refunded_at: timestamp(source.refunded_at),
|
|
525
|
+
state,
|
|
526
|
+
attention_required: boolean(source.attention_required),
|
|
527
|
+
subscription_begin_at: timestamp(source.subscription_begin_at),
|
|
528
|
+
subscription_end_at: timestamp(source.subscription_end_at),
|
|
529
|
+
artifact_state: artifactState,
|
|
530
|
+
download_available: boolean(source.download_available),
|
|
531
|
+
created_at: createdAt,
|
|
532
|
+
updated_at: updatedAt,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function safeQuote(value) {
|
|
536
|
+
const source = record(value);
|
|
537
|
+
const product = record(source.product);
|
|
538
|
+
const commonName = safeIdentifier(source.common_name);
|
|
539
|
+
const amount = record(source.amount);
|
|
540
|
+
const productId = integer(product.id);
|
|
541
|
+
const productLabel = text(product.label, 255);
|
|
542
|
+
const validationType = ["DV", "OV", "EV"].includes(String(product.validation_type))
|
|
543
|
+
? String(product.validation_type)
|
|
544
|
+
: null;
|
|
545
|
+
const termMonths = integer(product.term_months);
|
|
546
|
+
const csrSha256 = typeof source.csr_sha256 === "string" && /^[a-f0-9]{64}$/.test(source.csr_sha256)
|
|
547
|
+
? source.csr_sha256
|
|
548
|
+
: null;
|
|
549
|
+
const keyType = ["rsa", "ec-prime256v1", "ec-secp384r1", "ec-secp521r1"].includes(String(source.key_type))
|
|
550
|
+
? String(source.key_type)
|
|
551
|
+
: null;
|
|
552
|
+
const base = money(amount.base);
|
|
553
|
+
const san = money(amount.san);
|
|
554
|
+
const net = money(amount.net);
|
|
555
|
+
const vat = money(amount.vat);
|
|
556
|
+
const vatRate = money(amount.vat_rate);
|
|
557
|
+
const total = money(amount.total);
|
|
558
|
+
if (commonName === null
|
|
559
|
+
|| productId === null
|
|
560
|
+
|| productLabel === null
|
|
561
|
+
|| validationType === null
|
|
562
|
+
|| termMonths === null
|
|
563
|
+
|| csrSha256 === null
|
|
564
|
+
|| keyType === null
|
|
565
|
+
|| amount.currency !== "EUR"
|
|
566
|
+
|| base === null
|
|
567
|
+
|| san === null
|
|
568
|
+
|| net === null
|
|
569
|
+
|| vat === null
|
|
570
|
+
|| vatRate === null
|
|
571
|
+
|| total === null)
|
|
572
|
+
return null;
|
|
573
|
+
return {
|
|
574
|
+
operation: source.operation === "renew" ? "renew" : "order",
|
|
575
|
+
renewal: source.renewal === null ? null : {
|
|
576
|
+
certificate_order_id: text(record(source.renewal).certificate_order_id, 36),
|
|
577
|
+
remaining_days: Number.isInteger(record(source.renewal).remaining_days)
|
|
578
|
+
? record(source.renewal).remaining_days
|
|
579
|
+
: null,
|
|
580
|
+
},
|
|
581
|
+
product: {
|
|
582
|
+
id: productId,
|
|
583
|
+
label: productLabel,
|
|
584
|
+
validation_type: validationType,
|
|
585
|
+
term_months: termMonths,
|
|
586
|
+
},
|
|
587
|
+
common_name: commonName,
|
|
588
|
+
alternative_names: Array.isArray(source.alternative_names)
|
|
589
|
+
? source.alternative_names.map(safeIdentifier).filter((item) => item !== null)
|
|
590
|
+
: [],
|
|
591
|
+
csr_sha256: csrSha256,
|
|
592
|
+
key_type: keyType,
|
|
593
|
+
amount: {
|
|
594
|
+
currency: "EUR",
|
|
595
|
+
base,
|
|
596
|
+
san,
|
|
597
|
+
net,
|
|
598
|
+
vat,
|
|
599
|
+
vat_rate: vatRate,
|
|
600
|
+
total,
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function safeValidation(value) {
|
|
605
|
+
const source = record(value);
|
|
606
|
+
const orderId = certificateOrderIdSchema.safeParse(source.order_id);
|
|
607
|
+
if (!orderId.success)
|
|
608
|
+
return null;
|
|
609
|
+
const domains = Array.isArray(source.domains) ? source.domains.map((item) => {
|
|
610
|
+
const domain = record(item);
|
|
611
|
+
const challenge = domain.challenge === null ? null : record(domain.challenge);
|
|
612
|
+
return {
|
|
613
|
+
identifier: text(domain.identifier, 253),
|
|
614
|
+
validation_method: text(domain.validation_method, 253),
|
|
615
|
+
approved: boolean(domain.approved),
|
|
616
|
+
challenge: challenge === null ? null : {
|
|
617
|
+
file_name: text(challenge.file_name, 1024),
|
|
618
|
+
value: text(challenge.value, 16384),
|
|
619
|
+
},
|
|
620
|
+
validated_at: timestamp(domain.validated_at),
|
|
621
|
+
};
|
|
622
|
+
}) : [];
|
|
623
|
+
return { order_id: orderId.data, state: text(source.state, 64), domains };
|
|
624
|
+
}
|
|
625
|
+
function safeArtifact(value) {
|
|
626
|
+
const source = record(value);
|
|
627
|
+
const orderId = certificateOrderIdSchema.safeParse(source.order_id);
|
|
628
|
+
if (!orderId.success || source.private_key_included !== false)
|
|
629
|
+
return null;
|
|
630
|
+
const pem = (candidate) => {
|
|
631
|
+
const value = text(candidate, 2_097_152);
|
|
632
|
+
return value !== null && value.includes("-----BEGIN CERTIFICATE-----") && !value.includes("PRIVATE KEY")
|
|
633
|
+
? value
|
|
634
|
+
: null;
|
|
635
|
+
};
|
|
636
|
+
const generation = integer(source.generation);
|
|
637
|
+
const certificatePem = pem(source.certificate_pem);
|
|
638
|
+
const chainPem = pem(source.chain_pem);
|
|
639
|
+
const fullchainPem = pem(source.fullchain_pem);
|
|
640
|
+
const certificateSha256 = typeof source.certificate_sha256 === "string" && /^[a-f0-9]{64}$/.test(source.certificate_sha256)
|
|
641
|
+
? source.certificate_sha256
|
|
642
|
+
: null;
|
|
643
|
+
if (generation === null
|
|
644
|
+
|| certificatePem === null
|
|
645
|
+
|| chainPem === null
|
|
646
|
+
|| fullchainPem === null
|
|
647
|
+
|| certificateSha256 === null)
|
|
648
|
+
return null;
|
|
649
|
+
return {
|
|
650
|
+
order_id: orderId.data,
|
|
651
|
+
generation,
|
|
652
|
+
certificate_pem: certificatePem,
|
|
653
|
+
chain_pem: chainPem,
|
|
654
|
+
fullchain_pem: fullchainPem,
|
|
655
|
+
certificate_sha256: certificateSha256,
|
|
656
|
+
identifiers: Array.isArray(source.identifiers)
|
|
657
|
+
? source.identifiers.map((identifier) => text(identifier, 253)).filter((identifier) => identifier !== null)
|
|
658
|
+
: [],
|
|
659
|
+
not_before: timestamp(source.not_before),
|
|
660
|
+
not_after: timestamp(source.not_after),
|
|
661
|
+
fetched_at: timestamp(source.fetched_at),
|
|
662
|
+
private_key_included: false,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function safeAction(value) {
|
|
666
|
+
const source = record(value);
|
|
667
|
+
const id = certificateOrderIdSchema.safeParse(source.id);
|
|
668
|
+
const action = ["cancel", "recheck_validation", "resend_validation", "change_validation", "reissue"]
|
|
669
|
+
.includes(String(source.action)) ? source.action : null;
|
|
670
|
+
const state = ["queued", "submitting", "succeeded", "failed", "stale", "reconciliation_required"]
|
|
671
|
+
.includes(String(source.state)) ? source.state : null;
|
|
672
|
+
if (!id.success || action === null || state === null)
|
|
673
|
+
return null;
|
|
674
|
+
return {
|
|
675
|
+
id: id.data,
|
|
676
|
+
action,
|
|
677
|
+
state,
|
|
678
|
+
outcome_ambiguous: boolean(source.outcome_ambiguous),
|
|
679
|
+
attention_required: boolean(source.attention_required),
|
|
680
|
+
submitted_at: timestamp(source.submitted_at),
|
|
681
|
+
completed_at: timestamp(source.completed_at),
|
|
682
|
+
created_at: timestamp(source.created_at),
|
|
683
|
+
updated_at: timestamp(source.updated_at),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function safeAcmeSubscriptionDomain(value) {
|
|
687
|
+
const source = record(value);
|
|
688
|
+
const id = integer(source.id);
|
|
689
|
+
const name = certificateDnsIdentifierSchema.safeParse(source.name);
|
|
690
|
+
const nameKind = source.name_kind === "single" || source.name_kind === "wildcard"
|
|
691
|
+
? source.name_kind
|
|
692
|
+
: null;
|
|
693
|
+
const state = ["requested", "active", "removing", "removed"].includes(String(source.state))
|
|
694
|
+
? source.state
|
|
695
|
+
: null;
|
|
696
|
+
const replaceUntil = timestamp(source.replace_until);
|
|
697
|
+
if (id === null
|
|
698
|
+
|| !name.success
|
|
699
|
+
|| nameKind === null
|
|
700
|
+
|| state === null
|
|
701
|
+
|| typeof source.included !== "boolean"
|
|
702
|
+
|| typeof source.can_remove !== "boolean"
|
|
703
|
+
|| typeof source.can_replace !== "boolean"
|
|
704
|
+
|| (source.replace_until !== null && replaceUntil === null))
|
|
705
|
+
return null;
|
|
706
|
+
if ((nameKind === "wildcard") !== name.data.startsWith("*."))
|
|
707
|
+
return null;
|
|
708
|
+
return {
|
|
709
|
+
id,
|
|
710
|
+
name: name.data,
|
|
711
|
+
name_kind: nameKind,
|
|
712
|
+
included: boolean(source.included),
|
|
713
|
+
state,
|
|
714
|
+
can_remove: source.can_remove,
|
|
715
|
+
can_replace: source.can_replace,
|
|
716
|
+
replace_until: replaceUntil,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function safeAcmeSubscriptionRenewal(value) {
|
|
720
|
+
const source = record(value);
|
|
721
|
+
const id = certificateSubscriptionIdSchema.safeParse(source.id);
|
|
722
|
+
const renewalAt = text(source.renewal_at, 64);
|
|
723
|
+
const state = [
|
|
724
|
+
"awaiting_payment",
|
|
725
|
+
"prepaid",
|
|
726
|
+
"settled",
|
|
727
|
+
"refund_pending",
|
|
728
|
+
"refunded",
|
|
729
|
+
"failed",
|
|
730
|
+
"manual_review",
|
|
731
|
+
].includes(String(source.state)) ? source.state : null;
|
|
732
|
+
const billingState = [
|
|
733
|
+
"awaiting_payment",
|
|
734
|
+
"captured",
|
|
735
|
+
"settled",
|
|
736
|
+
"refund_pending",
|
|
737
|
+
"refunded",
|
|
738
|
+
"failed",
|
|
739
|
+
"billing_review_required",
|
|
740
|
+
].includes(String(source.billing_state)) ? source.billing_state : null;
|
|
741
|
+
const domainCount = integer(source.domain_count);
|
|
742
|
+
const amount = record(source.amount);
|
|
743
|
+
const net = money(amount.net);
|
|
744
|
+
const createdAt = text(source.created_at, 64);
|
|
745
|
+
const updatedAt = text(source.updated_at, 64);
|
|
746
|
+
if (!id.success
|
|
747
|
+
|| renewalAt === null
|
|
748
|
+
|| state === null
|
|
749
|
+
|| billingState === null
|
|
750
|
+
|| domainCount === null
|
|
751
|
+
|| domainCount > 255
|
|
752
|
+
|| amount.currency !== "EUR"
|
|
753
|
+
|| net === null
|
|
754
|
+
|| createdAt === null
|
|
755
|
+
|| updatedAt === null)
|
|
756
|
+
return null;
|
|
757
|
+
return {
|
|
758
|
+
id: id.data,
|
|
759
|
+
renewal_at: renewalAt,
|
|
760
|
+
state,
|
|
761
|
+
billing_state: billingState,
|
|
762
|
+
domain_count: domainCount,
|
|
763
|
+
amount: { currency: "EUR", net },
|
|
764
|
+
created_at: createdAt,
|
|
765
|
+
updated_at: updatedAt,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function safeAcmeSubscriptionAction(value) {
|
|
769
|
+
const source = record(value);
|
|
770
|
+
const id = certificateSubscriptionIdSchema.safeParse(source.id);
|
|
771
|
+
const action = ["cancel", "add_domain", "remove_domain", "replace_domain"]
|
|
772
|
+
.includes(String(source.action)) ? source.action : null;
|
|
773
|
+
const state = [
|
|
774
|
+
"awaiting_payment",
|
|
775
|
+
"queued",
|
|
776
|
+
"submitting",
|
|
777
|
+
"reconciling",
|
|
778
|
+
"reconciliation_required",
|
|
779
|
+
"succeeded",
|
|
780
|
+
"failed",
|
|
781
|
+
"manual_review",
|
|
782
|
+
].includes(String(source.state)) ? source.state : null;
|
|
783
|
+
const billingState = [
|
|
784
|
+
"awaiting_payment",
|
|
785
|
+
"captured",
|
|
786
|
+
"paid",
|
|
787
|
+
"no_charge",
|
|
788
|
+
"billing_review_required",
|
|
789
|
+
"refund_required",
|
|
790
|
+
"refunded",
|
|
791
|
+
"failed",
|
|
792
|
+
].includes(String(source.billing_state)) ? source.billing_state : null;
|
|
793
|
+
const amount = record(source.amount);
|
|
794
|
+
const net = money(amount.net);
|
|
795
|
+
const targetDomainId = nullableInteger(source.target_domain_id);
|
|
796
|
+
const createdAt = text(source.created_at, 64);
|
|
797
|
+
const updatedAt = text(source.updated_at, 64);
|
|
798
|
+
if (!id.success
|
|
799
|
+
|| action === null
|
|
800
|
+
|| state === null
|
|
801
|
+
|| billingState === null
|
|
802
|
+
|| typeof source.outcome_ambiguous !== "boolean"
|
|
803
|
+
|| typeof source.attention_required !== "boolean"
|
|
804
|
+
|| amount.currency !== "EUR"
|
|
805
|
+
|| net === null
|
|
806
|
+
|| (source.target_domain_id !== null && targetDomainId === null)
|
|
807
|
+
|| createdAt === null
|
|
808
|
+
|| updatedAt === null)
|
|
809
|
+
return null;
|
|
810
|
+
return {
|
|
811
|
+
id: id.data,
|
|
812
|
+
action,
|
|
813
|
+
state,
|
|
814
|
+
outcome_ambiguous: boolean(source.outcome_ambiguous),
|
|
815
|
+
attention_required: boolean(source.attention_required),
|
|
816
|
+
billing_state: billingState,
|
|
817
|
+
amount: { currency: "EUR", net },
|
|
818
|
+
target_domain_id: targetDomainId,
|
|
819
|
+
submitted_at: timestamp(source.submitted_at),
|
|
820
|
+
completed_at: timestamp(source.completed_at),
|
|
821
|
+
created_at: createdAt,
|
|
822
|
+
updated_at: updatedAt,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function safeAcmeSubscription(value) {
|
|
826
|
+
const source = record(value);
|
|
827
|
+
const id = certificateSubscriptionIdSchema.safeParse(source.id);
|
|
828
|
+
const product = record(source.product);
|
|
829
|
+
const productLabel = text(product.label, 255);
|
|
830
|
+
const termMonths = integer(product.term_months);
|
|
831
|
+
const state = [
|
|
832
|
+
"awaiting_payment",
|
|
833
|
+
"queued",
|
|
834
|
+
"provisioning",
|
|
835
|
+
"active",
|
|
836
|
+
"cancelled",
|
|
837
|
+
"failed",
|
|
838
|
+
"needs_attention",
|
|
839
|
+
].includes(String(source.state)) ? source.state : null;
|
|
840
|
+
const billingState = [
|
|
841
|
+
"awaiting_payment",
|
|
842
|
+
"captured",
|
|
843
|
+
"paid",
|
|
844
|
+
"billing_review",
|
|
845
|
+
"refund_required",
|
|
846
|
+
"refund_pending",
|
|
847
|
+
"refunded",
|
|
848
|
+
"failed",
|
|
849
|
+
].includes(String(source.billing_state)) ? source.billing_state : null;
|
|
850
|
+
const autoRenew = record(source.auto_renew);
|
|
851
|
+
const subscription = record(source.subscription);
|
|
852
|
+
const renewal = record(source.renewal);
|
|
853
|
+
const amount = record(source.amount);
|
|
854
|
+
const net = money(amount.net);
|
|
855
|
+
const createdAt = timestamp(source.created_at);
|
|
856
|
+
const updatedAt = timestamp(source.updated_at);
|
|
857
|
+
const providerEnabled = autoRenew.provider_enabled === null
|
|
858
|
+
? null
|
|
859
|
+
: typeof autoRenew.provider_enabled === "boolean"
|
|
860
|
+
? autoRenew.provider_enabled
|
|
861
|
+
: undefined;
|
|
862
|
+
const currentRenewal = renewal.current === null
|
|
863
|
+
? null
|
|
864
|
+
: safeAcmeSubscriptionRenewal(renewal.current);
|
|
865
|
+
if (!id.success
|
|
866
|
+
|| source.fulfillment !== "acme_subscription"
|
|
867
|
+
|| productLabel === null
|
|
868
|
+
|| termMonths === null
|
|
869
|
+
|| state === null
|
|
870
|
+
|| billingState === null
|
|
871
|
+
|| typeof source.credentials_available !== "boolean"
|
|
872
|
+
|| typeof autoRenew.enabled !== "boolean"
|
|
873
|
+
|| providerEnabled === undefined
|
|
874
|
+
|| amount.currency !== "EUR"
|
|
875
|
+
|| net === null
|
|
876
|
+
|| createdAt === null
|
|
877
|
+
|| updatedAt === null
|
|
878
|
+
|| typeof renewal.available !== "boolean"
|
|
879
|
+
|| renewal.payment_source !== "balance"
|
|
880
|
+
|| (renewal.current !== null && currentRenewal === null))
|
|
881
|
+
return null;
|
|
882
|
+
const rawDomains = Array.isArray(source.domains) ? source.domains : [];
|
|
883
|
+
const domains = rawDomains.map(safeAcmeSubscriptionDomain).filter((domain) => domain !== null);
|
|
884
|
+
if (domains.length === 0 || domains.length !== rawDomains.length)
|
|
885
|
+
return null;
|
|
886
|
+
return {
|
|
887
|
+
id: id.data,
|
|
888
|
+
fulfillment: "acme_subscription",
|
|
889
|
+
product: { label: productLabel, term_months: termMonths },
|
|
890
|
+
domains,
|
|
891
|
+
state,
|
|
892
|
+
billing_state: billingState,
|
|
893
|
+
credentials_available: boolean(source.credentials_available),
|
|
894
|
+
auto_renew: {
|
|
895
|
+
enabled: boolean(autoRenew.enabled),
|
|
896
|
+
provider_enabled: providerEnabled,
|
|
897
|
+
},
|
|
898
|
+
subscription: {
|
|
899
|
+
begins_at: timestamp(subscription.begins_at),
|
|
900
|
+
ends_at: timestamp(subscription.ends_at),
|
|
901
|
+
renews_at: timestamp(subscription.renews_at),
|
|
902
|
+
},
|
|
903
|
+
renewal: {
|
|
904
|
+
available: renewal.available,
|
|
905
|
+
payment_source: "balance",
|
|
906
|
+
current: currentRenewal,
|
|
907
|
+
},
|
|
908
|
+
amount: { currency: "EUR", net },
|
|
909
|
+
created_at: createdAt,
|
|
910
|
+
updated_at: updatedAt,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
function safeAcmeSubscriptionQuote(value) {
|
|
914
|
+
const source = record(value);
|
|
915
|
+
const product = record(source.product);
|
|
916
|
+
const productId = integer(product.id);
|
|
917
|
+
const productLabel = text(product.label, 255);
|
|
918
|
+
const termMonths = integer(product.term_months);
|
|
919
|
+
const amount = record(source.amount);
|
|
920
|
+
const base = money(amount.base);
|
|
921
|
+
const domainsPrice = money(amount.domains);
|
|
922
|
+
const net = money(amount.net);
|
|
923
|
+
const vat = money(amount.vat);
|
|
924
|
+
const vatRate = money(amount.vat_rate);
|
|
925
|
+
const total = money(amount.total);
|
|
926
|
+
if (source.fulfillment !== "acme_subscription"
|
|
927
|
+
|| productId === null
|
|
928
|
+
|| productLabel === null
|
|
929
|
+
|| termMonths === null
|
|
930
|
+
|| typeof source.auto_renew !== "boolean"
|
|
931
|
+
|| amount.currency !== "EUR"
|
|
932
|
+
|| base === null
|
|
933
|
+
|| domainsPrice === null
|
|
934
|
+
|| net === null
|
|
935
|
+
|| vat === null
|
|
936
|
+
|| vatRate === null
|
|
937
|
+
|| total === null)
|
|
938
|
+
return null;
|
|
939
|
+
const rawDomains = Array.isArray(source.domains) ? source.domains : [];
|
|
940
|
+
const domains = rawDomains.map((value) => {
|
|
941
|
+
const domain = record(value);
|
|
942
|
+
const name = certificateDnsIdentifierSchema.safeParse(domain.name);
|
|
943
|
+
const nameKind = domain.name_kind === "single" || domain.name_kind === "wildcard"
|
|
944
|
+
? domain.name_kind
|
|
945
|
+
: null;
|
|
946
|
+
if (!name.success || nameKind === null || (nameKind === "wildcard") !== name.data.startsWith("*.")) {
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
return { name: name.data, name_kind: nameKind };
|
|
950
|
+
}).filter((domain) => domain !== null);
|
|
951
|
+
if (domains.length === 0 || domains.length !== rawDomains.length)
|
|
952
|
+
return null;
|
|
953
|
+
return {
|
|
954
|
+
fulfillment: "acme_subscription",
|
|
955
|
+
product: { id: productId, label: productLabel, term_months: termMonths },
|
|
956
|
+
domains,
|
|
957
|
+
auto_renew: boolean(source.auto_renew),
|
|
958
|
+
amount: {
|
|
959
|
+
currency: "EUR",
|
|
960
|
+
base,
|
|
961
|
+
domains: domainsPrice,
|
|
962
|
+
net,
|
|
963
|
+
vat,
|
|
964
|
+
vat_rate: vatRate,
|
|
965
|
+
total,
|
|
966
|
+
},
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function safeAcmeSubscriptionDomainQuote(value) {
|
|
970
|
+
const source = record(value);
|
|
971
|
+
const subscriptionId = certificateSubscriptionIdSchema.safeParse(source.certificate_subscription_id);
|
|
972
|
+
const domain = record(source.domain);
|
|
973
|
+
const name = certificateDnsIdentifierSchema.safeParse(domain.name);
|
|
974
|
+
const nameKind = domain.name_kind === "single" || domain.name_kind === "wildcard"
|
|
975
|
+
? domain.name_kind
|
|
976
|
+
: null;
|
|
977
|
+
const amount = record(source.amount);
|
|
978
|
+
const net = money(amount.net);
|
|
979
|
+
const vat = money(amount.vat);
|
|
980
|
+
const vatRate = money(amount.vat_rate);
|
|
981
|
+
const total = money(amount.total);
|
|
982
|
+
if (source.operation !== "add_domain"
|
|
983
|
+
|| !subscriptionId.success
|
|
984
|
+
|| !name.success
|
|
985
|
+
|| nameKind === null
|
|
986
|
+
|| (nameKind === "wildcard") !== name.data.startsWith("*.")
|
|
987
|
+
|| amount.currency !== "EUR"
|
|
988
|
+
|| net === null
|
|
989
|
+
|| vat === null
|
|
990
|
+
|| vatRate === null
|
|
991
|
+
|| total === null)
|
|
992
|
+
return null;
|
|
993
|
+
return {
|
|
994
|
+
operation: "add_domain",
|
|
995
|
+
certificate_subscription_id: subscriptionId.data,
|
|
996
|
+
domain: { name: name.data, name_kind: nameKind },
|
|
997
|
+
amount: { currency: "EUR", net, vat, vat_rate: vatRate, total },
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function safeAcmeSubscriptionRenewalQuote(value) {
|
|
1001
|
+
const source = record(value);
|
|
1002
|
+
const subscriptionId = certificateSubscriptionIdSchema.safeParse(source.certificate_subscription_id);
|
|
1003
|
+
const renewsAt = text(source.renews_at, 64);
|
|
1004
|
+
const domainCount = integer(source.domain_count);
|
|
1005
|
+
const amount = record(source.amount);
|
|
1006
|
+
const net = money(amount.net);
|
|
1007
|
+
const vat = money(amount.vat);
|
|
1008
|
+
const vatRate = money(amount.vat_rate);
|
|
1009
|
+
const total = money(amount.total);
|
|
1010
|
+
if (source.operation !== "renew_term"
|
|
1011
|
+
|| !subscriptionId.success
|
|
1012
|
+
|| renewsAt === null
|
|
1013
|
+
|| domainCount === null
|
|
1014
|
+
|| domainCount > 255
|
|
1015
|
+
|| source.payment_source !== "balance"
|
|
1016
|
+
|| amount.currency !== "EUR"
|
|
1017
|
+
|| net === null
|
|
1018
|
+
|| vat === null
|
|
1019
|
+
|| vatRate === null
|
|
1020
|
+
|| total === null)
|
|
1021
|
+
return null;
|
|
1022
|
+
return {
|
|
1023
|
+
operation: "renew_term",
|
|
1024
|
+
certificate_subscription_id: subscriptionId.data,
|
|
1025
|
+
renews_at: renewsAt,
|
|
1026
|
+
domain_count: domainCount,
|
|
1027
|
+
payment_source: "balance",
|
|
1028
|
+
amount: { currency: "EUR", net, vat, vat_rate: vatRate, total },
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
const freeCertificateAuthorityNames = {
|
|
1032
|
+
letsencrypt: "Let's Encrypt",
|
|
1033
|
+
zerossl: "ZeroSSL",
|
|
1034
|
+
google: "Google Trust Services",
|
|
1035
|
+
};
|
|
1036
|
+
function safeFreeProviderOption(value) {
|
|
1037
|
+
const source = record(value);
|
|
1038
|
+
const id = freeCertificateAuthoritySchema.safeParse(source.id);
|
|
1039
|
+
if (!id.success
|
|
1040
|
+
|| source.name !== freeCertificateAuthorityNames[id.data]
|
|
1041
|
+
|| typeof source.default !== "boolean")
|
|
1042
|
+
return null;
|
|
1043
|
+
return { id: id.data, name: source.name, default: source.default };
|
|
1044
|
+
}
|
|
1045
|
+
function safeFreeKeyModeOption(value) {
|
|
1046
|
+
const source = record(value);
|
|
1047
|
+
if (!["managed", "customer_csr"].includes(String(source.mode))
|
|
1048
|
+
|| typeof source.available !== "boolean"
|
|
1049
|
+
|| typeof source.default !== "boolean"
|
|
1050
|
+
|| (source.reason !== null && text(source.reason, 190) === null))
|
|
1051
|
+
return null;
|
|
1052
|
+
return {
|
|
1053
|
+
mode: source.mode,
|
|
1054
|
+
available: source.available,
|
|
1055
|
+
reason: source.reason === null ? null : text(source.reason, 190),
|
|
1056
|
+
default: source.default,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function safeFreeMethodOption(value) {
|
|
1060
|
+
const source = record(value);
|
|
1061
|
+
if (![
|
|
1062
|
+
"dns_zone",
|
|
1063
|
+
"dns_cname",
|
|
1064
|
+
"http",
|
|
1065
|
+
"download",
|
|
1066
|
+
"auto_install_guest",
|
|
1067
|
+
"assistant_install",
|
|
1068
|
+
].includes(String(source.method))
|
|
1069
|
+
|| typeof source.available !== "boolean"
|
|
1070
|
+
|| (source.reason !== null && text(source.reason, 190) === null)
|
|
1071
|
+
|| (source.automatic !== undefined && typeof source.automatic !== "boolean"))
|
|
1072
|
+
return null;
|
|
1073
|
+
const option = {
|
|
1074
|
+
method: source.method,
|
|
1075
|
+
available: source.available,
|
|
1076
|
+
reason: source.reason === null ? null : text(source.reason, 190),
|
|
1077
|
+
};
|
|
1078
|
+
if (typeof source.automatic === "boolean")
|
|
1079
|
+
option.automatic = source.automatic;
|
|
1080
|
+
return option;
|
|
1081
|
+
}
|
|
1082
|
+
function safeFreeQuota(value) {
|
|
1083
|
+
const source = record(value);
|
|
1084
|
+
const pending = nonNegativeInteger(source.pending_requests);
|
|
1085
|
+
const maxPending = integer(source.max_pending_requests);
|
|
1086
|
+
const used = nonNegativeInteger(source.registered_domains_used_7d);
|
|
1087
|
+
const maximum = integer(source.max_registered_domains_7d);
|
|
1088
|
+
const domains = Array.isArray(source.registered_domains)
|
|
1089
|
+
? source.registered_domains.map((domain) => certificateDnsIdentifierSchema.safeParse(domain))
|
|
1090
|
+
: [];
|
|
1091
|
+
if (pending === null
|
|
1092
|
+
|| maxPending === null
|
|
1093
|
+
|| used === null
|
|
1094
|
+
|| maximum === null
|
|
1095
|
+
|| !Array.isArray(source.registered_domains)
|
|
1096
|
+
|| domains.some((domain) => !domain.success)
|
|
1097
|
+
|| new Set(domains.map((domain) => domain.success ? domain.data : "")).size !== domains.length)
|
|
1098
|
+
return null;
|
|
1099
|
+
return {
|
|
1100
|
+
pending_requests: pending,
|
|
1101
|
+
max_pending_requests: maxPending,
|
|
1102
|
+
registered_domains_used_7d: used,
|
|
1103
|
+
max_registered_domains_7d: maximum,
|
|
1104
|
+
registered_domains: domains.map((domain) => domain.success ? domain.data : ""),
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
function safeArray(value, mapper, maximum = 200) {
|
|
1108
|
+
if (!Array.isArray(value) || value.length > maximum)
|
|
1109
|
+
return null;
|
|
1110
|
+
const mapped = value.map(mapper);
|
|
1111
|
+
return mapped.some((item) => item === null) ? null : mapped;
|
|
1112
|
+
}
|
|
1113
|
+
function safeFreeEligibility(value) {
|
|
1114
|
+
const source = record(value);
|
|
1115
|
+
const providers = safeArray(source.certificate_authorities, safeFreeProviderOption, 3);
|
|
1116
|
+
const modes = safeArray(source.key_modes, safeFreeKeyModeOption, 2);
|
|
1117
|
+
const qualifiesVia = safeArray(source.qualifies_via, (item) => typeof item === "string" && /^[A-Za-z][A-Za-z0-9._-]{0,95}$/.test(item) ? item : null, 20);
|
|
1118
|
+
const quota = source.quota === undefined ? null : safeFreeQuota(source.quota);
|
|
1119
|
+
if (typeof source.eligible !== "boolean"
|
|
1120
|
+
|| (source.reason !== null && text(source.reason, 190) === null)
|
|
1121
|
+
|| providers === null
|
|
1122
|
+
|| modes === null
|
|
1123
|
+
|| qualifiesVia === null
|
|
1124
|
+
|| (source.quota !== undefined && quota === null))
|
|
1125
|
+
return null;
|
|
1126
|
+
const result = {
|
|
1127
|
+
eligible: source.eligible,
|
|
1128
|
+
reason: source.reason === null ? null : text(source.reason, 190),
|
|
1129
|
+
qualifies_via: qualifiesVia,
|
|
1130
|
+
key_modes: modes,
|
|
1131
|
+
certificate_authorities: providers,
|
|
1132
|
+
};
|
|
1133
|
+
if (quota !== null)
|
|
1134
|
+
result.quota = quota;
|
|
1135
|
+
return result;
|
|
1136
|
+
}
|
|
1137
|
+
function safeFreePreflight(value) {
|
|
1138
|
+
const source = record(value);
|
|
1139
|
+
const hostname = certificateDnsIdentifierSchema.safeParse(source.hostname);
|
|
1140
|
+
const registeredDomain = certificateDnsIdentifierSchema.safeParse(source.registered_domain);
|
|
1141
|
+
const identifiers = safeArray(source.identifiers, (item) => {
|
|
1142
|
+
const parsed = certificateDnsIdentifierSchema.safeParse(item);
|
|
1143
|
+
return parsed.success ? parsed.data : null;
|
|
1144
|
+
}, 100);
|
|
1145
|
+
const validations = safeArray(source.validation_methods, safeFreeMethodOption, 3);
|
|
1146
|
+
const deliveries = safeArray(source.delivery_methods, safeFreeMethodOption, 3);
|
|
1147
|
+
const modes = safeArray(source.key_modes, safeFreeKeyModeOption, 2);
|
|
1148
|
+
const providers = safeArray(source.certificate_authorities, safeFreeProviderOption, 3);
|
|
1149
|
+
const recommended = record(source.recommended);
|
|
1150
|
+
const recommendedProvider = recommended.certificate_authority === null
|
|
1151
|
+
? null
|
|
1152
|
+
: freeCertificateAuthoritySchema.safeParse(recommended.certificate_authority);
|
|
1153
|
+
if (!hostname.success
|
|
1154
|
+
|| !registeredDomain.success
|
|
1155
|
+
|| identifiers === null
|
|
1156
|
+
|| identifiers.length === 0
|
|
1157
|
+
|| new Set(identifiers).size !== identifiers.length
|
|
1158
|
+
|| !identifiers.includes(hostname.success ? hostname.data : "")
|
|
1159
|
+
|| validations === null
|
|
1160
|
+
|| deliveries === null
|
|
1161
|
+
|| modes === null
|
|
1162
|
+
|| providers === null
|
|
1163
|
+
|| validations.some((option) => !["dns_zone", "dns_cname", "http"].includes(String(option.method)))
|
|
1164
|
+
|| deliveries.some((option) => !["download", "auto_install_guest", "assistant_install"].includes(String(option.method)))
|
|
1165
|
+
|| typeof source.issuable !== "boolean"
|
|
1166
|
+
|| (source.reason !== null && text(source.reason, 190) === null)
|
|
1167
|
+
|| ![null, "dns_zone", "dns_cname", "http"].includes(recommended.validation_method)
|
|
1168
|
+
|| ![null, "download", "auto_install_guest", "assistant_install"].includes(recommended.delivery_method)
|
|
1169
|
+
|| !["managed", "customer_csr"].includes(String(recommended.key_mode))
|
|
1170
|
+
|| (recommended.certificate_authority !== null && !recommendedProvider?.success))
|
|
1171
|
+
return null;
|
|
1172
|
+
return {
|
|
1173
|
+
hostname: hostname.data,
|
|
1174
|
+
identifiers,
|
|
1175
|
+
registered_domain: registeredDomain.data,
|
|
1176
|
+
issuable: source.issuable,
|
|
1177
|
+
reason: source.reason === null ? null : text(source.reason, 190),
|
|
1178
|
+
validation_methods: validations,
|
|
1179
|
+
delivery_methods: deliveries,
|
|
1180
|
+
key_modes: modes,
|
|
1181
|
+
certificate_authorities: providers,
|
|
1182
|
+
recommended: {
|
|
1183
|
+
validation_method: recommended.validation_method,
|
|
1184
|
+
delivery_method: recommended.delivery_method,
|
|
1185
|
+
key_mode: recommended.key_mode,
|
|
1186
|
+
certificate_authority: recommended.certificate_authority,
|
|
1187
|
+
},
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
function safeFreeTimelineEvent(value) {
|
|
1191
|
+
const source = record(value);
|
|
1192
|
+
const event = text(source.event, 96);
|
|
1193
|
+
const createdAt = timestamp(source.created_at);
|
|
1194
|
+
if (event === null || !["customer", "system", "admin"].includes(String(source.actor)) || createdAt === null) {
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
return { event, actor: source.actor, created_at: createdAt };
|
|
1198
|
+
}
|
|
1199
|
+
function safeFreeRequest(value) {
|
|
1200
|
+
const source = record(value);
|
|
1201
|
+
const id = freeCertificateRequestIdSchema.safeParse(source.id);
|
|
1202
|
+
const hostname = certificateDnsIdentifierSchema.safeParse(source.hostname);
|
|
1203
|
+
const registeredDomain = certificateDnsIdentifierSchema.safeParse(source.registered_domain);
|
|
1204
|
+
const state = freeCertificateStateSchema.safeParse(source.state);
|
|
1205
|
+
const identifiers = safeArray(source.identifiers, (item) => {
|
|
1206
|
+
const parsed = certificateDnsIdentifierSchema.safeParse(item);
|
|
1207
|
+
return parsed.success ? parsed.data : null;
|
|
1208
|
+
}, 100);
|
|
1209
|
+
const authorityId = source.certificate_authority_id === null
|
|
1210
|
+
? null
|
|
1211
|
+
: freeCertificateAuthoritySchema.safeParse(source.certificate_authority_id);
|
|
1212
|
+
const authorityName = source.certificate_authority === null
|
|
1213
|
+
? null
|
|
1214
|
+
: text(source.certificate_authority, 64);
|
|
1215
|
+
const deliveryOrderId = nullableInteger(source.delivery_order_id);
|
|
1216
|
+
const timeline = source.timeline === undefined
|
|
1217
|
+
? undefined
|
|
1218
|
+
: safeArray(source.timeline, safeFreeTimelineEvent, 200);
|
|
1219
|
+
const createdAt = timestamp(source.created_at);
|
|
1220
|
+
if (!id.success
|
|
1221
|
+
|| !hostname.success
|
|
1222
|
+
|| !registeredDomain.success
|
|
1223
|
+
|| !state.success
|
|
1224
|
+
|| identifiers === null
|
|
1225
|
+
|| identifiers.length === 0
|
|
1226
|
+
|| new Set(identifiers).size !== identifiers.length
|
|
1227
|
+
|| !identifiers.includes(hostname.success ? hostname.data : "")
|
|
1228
|
+
|| !["managed", "customer_csr"].includes(String(source.key_mode))
|
|
1229
|
+
|| !["dns_zone", "dns_cname"].includes(String(source.validation_method))
|
|
1230
|
+
|| !["download", "auto_install_guest", "assistant_install"].includes(String(source.delivery_method))
|
|
1231
|
+
|| (source.delivery_order_id !== null && deliveryOrderId === null)
|
|
1232
|
+
|| (source.certificate_authority_id !== null && !authorityId?.success)
|
|
1233
|
+
|| (source.certificate_authority !== null && authorityName === null)
|
|
1234
|
+
|| (authorityId === null) !== (authorityName === null)
|
|
1235
|
+
|| (authorityId !== null && authorityId.success && authorityName !== freeCertificateAuthorityNames[authorityId.data])
|
|
1236
|
+
|| [
|
|
1237
|
+
"vpsnet_holds_private_key",
|
|
1238
|
+
"auto_renew",
|
|
1239
|
+
"renewal_requires_new_csr",
|
|
1240
|
+
"download_available",
|
|
1241
|
+
"revocable",
|
|
1242
|
+
"revocation_pending",
|
|
1243
|
+
"attention_required",
|
|
1244
|
+
].some((field) => typeof source[field] !== "boolean")
|
|
1245
|
+
|| ["issued_at", "not_before", "not_after", "renewal_due_at"]
|
|
1246
|
+
.some((field) => source[field] !== null && timestamp(source[field]) === null)
|
|
1247
|
+
|| createdAt === null
|
|
1248
|
+
|| timeline === null)
|
|
1249
|
+
return null;
|
|
1250
|
+
const result = {
|
|
1251
|
+
id: id.data,
|
|
1252
|
+
hostname: hostname.data,
|
|
1253
|
+
identifiers,
|
|
1254
|
+
registered_domain: registeredDomain.data,
|
|
1255
|
+
state: state.data,
|
|
1256
|
+
key_mode: source.key_mode,
|
|
1257
|
+
vpsnet_holds_private_key: source.vpsnet_holds_private_key,
|
|
1258
|
+
validation_method: source.validation_method,
|
|
1259
|
+
delivery_method: source.delivery_method,
|
|
1260
|
+
delivery_order_id: deliveryOrderId,
|
|
1261
|
+
auto_renew: source.auto_renew,
|
|
1262
|
+
renewal_requires_new_csr: source.renewal_requires_new_csr,
|
|
1263
|
+
issued_at: timestamp(source.issued_at),
|
|
1264
|
+
not_before: timestamp(source.not_before),
|
|
1265
|
+
not_after: timestamp(source.not_after),
|
|
1266
|
+
renewal_due_at: timestamp(source.renewal_due_at),
|
|
1267
|
+
certificate_authority_id: authorityId !== null && authorityId.success ? authorityId.data : null,
|
|
1268
|
+
certificate_authority: authorityName,
|
|
1269
|
+
download_available: source.download_available,
|
|
1270
|
+
revocable: source.revocable,
|
|
1271
|
+
revocation_pending: source.revocation_pending,
|
|
1272
|
+
attention_required: source.attention_required,
|
|
1273
|
+
created_at: createdAt,
|
|
1274
|
+
};
|
|
1275
|
+
if (timeline !== undefined)
|
|
1276
|
+
result.timeline = timeline;
|
|
1277
|
+
return result;
|
|
1278
|
+
}
|
|
1279
|
+
function safeFreeDnsRecord(value) {
|
|
1280
|
+
const source = record(value);
|
|
1281
|
+
const name = text(source.name, 253);
|
|
1282
|
+
const recordValue = text(source.value, 253);
|
|
1283
|
+
const ttl = integer(source.ttl);
|
|
1284
|
+
const dnsText = (candidate) => candidate !== null
|
|
1285
|
+
&& candidate === candidate.toLowerCase()
|
|
1286
|
+
&& !/[\s\x00-\x1f\x7f]/.test(candidate)
|
|
1287
|
+
&& /^[a-z0-9_](?:[a-z0-9._-]{0,251}[a-z0-9.])?$/.test(candidate);
|
|
1288
|
+
if (source.type !== "CNAME"
|
|
1289
|
+
|| !dnsText(name)
|
|
1290
|
+
|| !dnsText(recordValue)
|
|
1291
|
+
|| ttl === null
|
|
1292
|
+
|| ttl < 60)
|
|
1293
|
+
return null;
|
|
1294
|
+
return { type: "CNAME", name, value: recordValue, ttl };
|
|
1295
|
+
}
|
|
1296
|
+
function safeFreeInstruction(value) {
|
|
1297
|
+
const source = record(value);
|
|
1298
|
+
const item = source.record === null ? null : safeFreeDnsRecord(source.record);
|
|
1299
|
+
const records = safeArray(source.records, safeFreeDnsRecord, 100);
|
|
1300
|
+
const noteKey = text(source.note_key, 190);
|
|
1301
|
+
const lastCheck = source.last_check === undefined ? undefined : record(source.last_check);
|
|
1302
|
+
if (!["dns_zone", "dns_cname", "http"].includes(String(source.validation_method))
|
|
1303
|
+
|| typeof source.automatic !== "boolean"
|
|
1304
|
+
|| (source.record !== null && item === null)
|
|
1305
|
+
|| records === null
|
|
1306
|
+
|| typeof source.verified !== "boolean"
|
|
1307
|
+
|| noteKey === null
|
|
1308
|
+
|| (lastCheck !== undefined
|
|
1309
|
+
&& ((lastCheck.at !== null && timestamp(lastCheck.at) === null)
|
|
1310
|
+
|| (lastCheck.detail !== null && text(lastCheck.detail, 500) === null))))
|
|
1311
|
+
return null;
|
|
1312
|
+
const result = {
|
|
1313
|
+
validation_method: source.validation_method,
|
|
1314
|
+
automatic: source.automatic,
|
|
1315
|
+
record: item,
|
|
1316
|
+
records,
|
|
1317
|
+
verified: source.verified,
|
|
1318
|
+
note_key: noteKey,
|
|
1319
|
+
};
|
|
1320
|
+
if (lastCheck !== undefined) {
|
|
1321
|
+
result.last_check = {
|
|
1322
|
+
at: timestamp(lastCheck.at),
|
|
1323
|
+
detail: lastCheck.detail === null ? null : text(lastCheck.detail, 500),
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
return result;
|
|
1327
|
+
}
|
|
1328
|
+
function safeFreeArtifact(value) {
|
|
1329
|
+
const source = record(value);
|
|
1330
|
+
const pem = (candidate, allowEmpty = false) => {
|
|
1331
|
+
const value = text(candidate, 2_097_152);
|
|
1332
|
+
if (value === null || value.includes("PRIVATE KEY"))
|
|
1333
|
+
return null;
|
|
1334
|
+
if (allowEmpty && value === "")
|
|
1335
|
+
return value;
|
|
1336
|
+
return value.includes("-----BEGIN CERTIFICATE-----") ? value : null;
|
|
1337
|
+
};
|
|
1338
|
+
const certificate = pem(source.certificate);
|
|
1339
|
+
const chain = pem(source.chain, true);
|
|
1340
|
+
const fullchain = pem(source.fullchain);
|
|
1341
|
+
const notAfter = timestamp(source.not_after);
|
|
1342
|
+
const fingerprint = typeof source.fingerprint_sha256 === "string"
|
|
1343
|
+
&& /^[a-f0-9]{64}$/.test(source.fingerprint_sha256)
|
|
1344
|
+
? source.fingerprint_sha256
|
|
1345
|
+
: null;
|
|
1346
|
+
if (certificate === null || chain === null || fullchain === null || notAfter === null || fingerprint === null) {
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
return { certificate, chain, fullchain, not_after: notAfter, fingerprint_sha256: fingerprint };
|
|
1350
|
+
}
|
|
1351
|
+
function safeError(status, value) {
|
|
1352
|
+
const source = record(value);
|
|
1353
|
+
const errors = Object.entries(source)
|
|
1354
|
+
.filter(([key, enabled]) => enabled === true && /^[A-Za-z][A-Za-z0-9._-]{0,95}$/.test(key))
|
|
1355
|
+
.map(([key]) => key)
|
|
1356
|
+
.slice(0, 20);
|
|
1357
|
+
return { success: false, status, errors };
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Defense-in-depth projection for the MCP boundary. Even if a future backend
|
|
1361
|
+
* response accidentally contains wholesale cost, provider IDs, credentials,
|
|
1362
|
+
* request bodies, or raw errors, none of those fields can enter model context.
|
|
1363
|
+
*/
|
|
1364
|
+
export function safeCertificatePayload(kind, status, value) {
|
|
1365
|
+
const source = record(value);
|
|
1366
|
+
if (source.success !== true)
|
|
1367
|
+
return safeError(status, source);
|
|
1368
|
+
switch (kind) {
|
|
1369
|
+
case "catalog-list":
|
|
1370
|
+
return {
|
|
1371
|
+
success: true,
|
|
1372
|
+
records: Array.isArray(source.records)
|
|
1373
|
+
? source.records.map(safeProduct).filter((product) => product !== null)
|
|
1374
|
+
: [],
|
|
1375
|
+
};
|
|
1376
|
+
case "catalog-product": {
|
|
1377
|
+
const product = safeProduct(source.product);
|
|
1378
|
+
return product === null ? safeError(502, { invalidCertificateResponse: true }) : { success: true, product };
|
|
1379
|
+
}
|
|
1380
|
+
case "order-list":
|
|
1381
|
+
return {
|
|
1382
|
+
success: true,
|
|
1383
|
+
records: Array.isArray(source.records)
|
|
1384
|
+
? source.records.map(safeOrder).filter((order) => order !== null)
|
|
1385
|
+
: [],
|
|
1386
|
+
};
|
|
1387
|
+
case "order": {
|
|
1388
|
+
const order = safeOrder(source.order);
|
|
1389
|
+
return order === null ? safeError(502, { invalidCertificateResponse: true }) : { success: true, order };
|
|
1390
|
+
}
|
|
1391
|
+
case "quote": {
|
|
1392
|
+
const quote = safeQuote(source.quote);
|
|
1393
|
+
const quoteToken = text(source.quote_token, 190);
|
|
1394
|
+
return quote === null || quoteToken === null
|
|
1395
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1396
|
+
: {
|
|
1397
|
+
success: true,
|
|
1398
|
+
quote_token: quoteToken,
|
|
1399
|
+
quote_expires_at: timestamp(source.quote_expires_at),
|
|
1400
|
+
quote,
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
case "confirmation": {
|
|
1404
|
+
const order = safeOrder(source.order);
|
|
1405
|
+
return order === null
|
|
1406
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1407
|
+
: {
|
|
1408
|
+
success: true,
|
|
1409
|
+
replayed: boolean(source.replayed),
|
|
1410
|
+
redirect: source.redirect === null ? null : text(source.redirect, 2048),
|
|
1411
|
+
payment_id: nullableInteger(source.payment_id),
|
|
1412
|
+
order,
|
|
1413
|
+
refund_policy: text(source.refund_policy, 1000),
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
case "validation": {
|
|
1417
|
+
const validation = safeValidation(source.validation);
|
|
1418
|
+
return validation === null ? safeError(502, { invalidCertificateResponse: true }) : { success: true, validation };
|
|
1419
|
+
}
|
|
1420
|
+
case "artifact": {
|
|
1421
|
+
const artifact = safeArtifact(source.artifact);
|
|
1422
|
+
return artifact === null ? safeError(502, { invalidCertificateResponse: true }) : { success: true, artifact };
|
|
1423
|
+
}
|
|
1424
|
+
case "action-list":
|
|
1425
|
+
return {
|
|
1426
|
+
success: true,
|
|
1427
|
+
records: Array.isArray(source.records)
|
|
1428
|
+
? source.records.map(safeAction).filter((action) => action !== null)
|
|
1429
|
+
: [],
|
|
1430
|
+
};
|
|
1431
|
+
case "action": {
|
|
1432
|
+
const action = safeAction(source.action);
|
|
1433
|
+
return action === null ? safeError(502, { invalidCertificateResponse: true }) : { success: true, action };
|
|
1434
|
+
}
|
|
1435
|
+
case "refresh":
|
|
1436
|
+
return source.reconciliation_queued === true
|
|
1437
|
+
? { success: true, reconciliation_queued: true }
|
|
1438
|
+
: safeError(502, { invalidCertificateResponse: true });
|
|
1439
|
+
case "acme-subscription-list":
|
|
1440
|
+
return {
|
|
1441
|
+
success: true,
|
|
1442
|
+
records: Array.isArray(source.records)
|
|
1443
|
+
? source.records.map(safeAcmeSubscription).filter((subscription) => subscription !== null)
|
|
1444
|
+
: [],
|
|
1445
|
+
};
|
|
1446
|
+
case "acme-subscription": {
|
|
1447
|
+
const subscription = safeAcmeSubscription(source.subscription);
|
|
1448
|
+
return subscription === null
|
|
1449
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1450
|
+
: { success: true, subscription };
|
|
1451
|
+
}
|
|
1452
|
+
case "acme-subscription-quote": {
|
|
1453
|
+
const quote = safeAcmeSubscriptionQuote(source.quote);
|
|
1454
|
+
const quoteToken = text(source.quote_token, 190);
|
|
1455
|
+
return quote === null || quoteToken === null
|
|
1456
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1457
|
+
: {
|
|
1458
|
+
success: true,
|
|
1459
|
+
quote_token: quoteToken,
|
|
1460
|
+
quote_expires_at: timestamp(source.quote_expires_at),
|
|
1461
|
+
quote,
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
case "acme-subscription-confirmation": {
|
|
1465
|
+
const subscription = safeAcmeSubscription(source.subscription);
|
|
1466
|
+
return subscription === null
|
|
1467
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1468
|
+
: {
|
|
1469
|
+
success: true,
|
|
1470
|
+
replayed: boolean(source.replayed),
|
|
1471
|
+
redirect: source.redirect === null ? null : text(source.redirect, 2048),
|
|
1472
|
+
payment_id: nullableInteger(source.payment_id),
|
|
1473
|
+
subscription,
|
|
1474
|
+
refund_policy: text(source.refund_policy, 1000),
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
case "acme-subscription-action-list":
|
|
1478
|
+
return {
|
|
1479
|
+
success: true,
|
|
1480
|
+
records: Array.isArray(source.records)
|
|
1481
|
+
? source.records.map(safeAcmeSubscriptionAction).filter((action) => action !== null)
|
|
1482
|
+
: [],
|
|
1483
|
+
};
|
|
1484
|
+
case "acme-subscription-action": {
|
|
1485
|
+
const action = safeAcmeSubscriptionAction(source.action);
|
|
1486
|
+
return action === null
|
|
1487
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1488
|
+
: { success: true, action };
|
|
1489
|
+
}
|
|
1490
|
+
case "acme-subscription-refresh":
|
|
1491
|
+
return source.reconciliation_queued === true
|
|
1492
|
+
? { success: true, reconciliation_queued: true }
|
|
1493
|
+
: safeError(502, { invalidCertificateResponse: true });
|
|
1494
|
+
case "acme-subscription-domain-quote": {
|
|
1495
|
+
const quote = safeAcmeSubscriptionDomainQuote(source.quote);
|
|
1496
|
+
const quoteToken = text(source.quote_token, 190);
|
|
1497
|
+
return quote === null || quoteToken === null
|
|
1498
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1499
|
+
: {
|
|
1500
|
+
success: true,
|
|
1501
|
+
quote_token: quoteToken,
|
|
1502
|
+
quote_expires_at: timestamp(source.quote_expires_at),
|
|
1503
|
+
quote,
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
case "acme-subscription-domain-confirmation": {
|
|
1507
|
+
const action = safeAcmeSubscriptionAction(source.action);
|
|
1508
|
+
return action === null
|
|
1509
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1510
|
+
: {
|
|
1511
|
+
success: true,
|
|
1512
|
+
replayed: boolean(source.replayed),
|
|
1513
|
+
redirect: source.redirect === null ? null : text(source.redirect, 2048),
|
|
1514
|
+
payment_id: nullableInteger(source.payment_id),
|
|
1515
|
+
action,
|
|
1516
|
+
refund_policy: text(source.refund_policy, 1000),
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
case "acme-subscription-renewal-quote": {
|
|
1520
|
+
const quote = safeAcmeSubscriptionRenewalQuote(source.quote);
|
|
1521
|
+
const quoteToken = text(source.quote_token, 190);
|
|
1522
|
+
return quote === null || quoteToken === null
|
|
1523
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1524
|
+
: {
|
|
1525
|
+
success: true,
|
|
1526
|
+
quote_token: quoteToken,
|
|
1527
|
+
quote_expires_at: timestamp(source.quote_expires_at),
|
|
1528
|
+
quote,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
case "acme-subscription-renewal-confirmation": {
|
|
1532
|
+
const renewal = safeAcmeSubscriptionRenewal(source.renewal);
|
|
1533
|
+
return renewal === null
|
|
1534
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1535
|
+
: {
|
|
1536
|
+
success: true,
|
|
1537
|
+
replayed: boolean(source.replayed),
|
|
1538
|
+
payment_id: nullableInteger(source.payment_id),
|
|
1539
|
+
payment_processing: boolean(source.payment_processing),
|
|
1540
|
+
renewal,
|
|
1541
|
+
refund_policy: text(source.refund_policy, 1000),
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
case "free-eligibility": {
|
|
1545
|
+
const eligibility = safeFreeEligibility(source);
|
|
1546
|
+
return eligibility === null
|
|
1547
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1548
|
+
: { success: true, ...eligibility };
|
|
1549
|
+
}
|
|
1550
|
+
case "free-preflight": {
|
|
1551
|
+
const preflight = safeFreePreflight(source);
|
|
1552
|
+
return preflight === null
|
|
1553
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1554
|
+
: { success: true, ...preflight };
|
|
1555
|
+
}
|
|
1556
|
+
case "free-list": {
|
|
1557
|
+
const requests = safeArray(source.records, safeFreeRequest, 200);
|
|
1558
|
+
return requests === null
|
|
1559
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1560
|
+
: { success: true, records: requests };
|
|
1561
|
+
}
|
|
1562
|
+
case "free-request": {
|
|
1563
|
+
const request = safeFreeRequest(source.request);
|
|
1564
|
+
return request === null
|
|
1565
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1566
|
+
: { success: true, request };
|
|
1567
|
+
}
|
|
1568
|
+
case "free-create": {
|
|
1569
|
+
const request = safeFreeRequest(source.request);
|
|
1570
|
+
const instruction = safeFreeInstruction(source.instruction);
|
|
1571
|
+
const csrSha256 = source.csr_sha256 === null
|
|
1572
|
+
? null
|
|
1573
|
+
: typeof source.csr_sha256 === "string" && /^[a-f0-9]{64}$/.test(source.csr_sha256)
|
|
1574
|
+
? source.csr_sha256
|
|
1575
|
+
: undefined;
|
|
1576
|
+
return request === null
|
|
1577
|
+
|| instruction === null
|
|
1578
|
+
|| csrSha256 === undefined
|
|
1579
|
+
|| typeof source.replayed !== "boolean"
|
|
1580
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1581
|
+
: {
|
|
1582
|
+
success: true,
|
|
1583
|
+
request,
|
|
1584
|
+
instruction,
|
|
1585
|
+
csr_sha256: csrSha256,
|
|
1586
|
+
replayed: source.replayed,
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
case "free-instruction": {
|
|
1590
|
+
const instruction = safeFreeInstruction(source);
|
|
1591
|
+
return instruction === null
|
|
1592
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1593
|
+
: { success: true, ...instruction };
|
|
1594
|
+
}
|
|
1595
|
+
case "free-artifact": {
|
|
1596
|
+
const artifact = safeFreeArtifact(source.artifact);
|
|
1597
|
+
return artifact === null
|
|
1598
|
+
|| !["managed", "customer_csr"].includes(String(source.key_mode))
|
|
1599
|
+
|| source.private_key_included !== false
|
|
1600
|
+
? safeError(502, { invalidCertificateResponse: true })
|
|
1601
|
+
: {
|
|
1602
|
+
success: true,
|
|
1603
|
+
key_mode: source.key_mode,
|
|
1604
|
+
artifact,
|
|
1605
|
+
private_key_included: false,
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
case "free-action":
|
|
1609
|
+
return ["renew", "revoke", "recheck", "cancel"].includes(String(source.action))
|
|
1610
|
+
&& source.queued === true
|
|
1611
|
+
? { success: true, action: source.action, queued: true }
|
|
1612
|
+
: safeError(502, { invalidCertificateResponse: true });
|
|
1613
|
+
}
|
|
1614
|
+
}
|