Build, Test, and Push CFDM Docker Image / test (push) Failing after 51s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
460 lines
15 KiB
JavaScript
460 lines
15 KiB
JavaScript
// src/constants.ts
|
|
var SYNC_SYNCED = "synced";
|
|
var SYNC_PENDING_PUSH = "pending_push";
|
|
var SYNC_PENDING_DELETE = "pending_delete";
|
|
var SYNC_CONFLICT = "conflict";
|
|
var SYNC_ERROR = "error";
|
|
var CERT_OK = "ok";
|
|
var CERT_WARNING = "warning";
|
|
var CERT_EXPIRED = "expired";
|
|
var CERT_ERROR = "error";
|
|
var CERT_UNKNOWN = "unknown";
|
|
var CERT_MONITOR_AUTO = "auto";
|
|
var CERT_MONITOR_REQUIRED = "required";
|
|
var CERT_MONITOR_SKIPPED = "skipped";
|
|
var CERT_MONITORING_VALUES = [
|
|
CERT_MONITOR_AUTO,
|
|
CERT_MONITOR_REQUIRED,
|
|
CERT_MONITOR_SKIPPED
|
|
];
|
|
|
|
// src/validators.ts
|
|
var NAME_RE = /^(@|\*|[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?(\.[a-zA-Z0-9_]([a-zA-Z0-9_-]*[a-zA-Z0-9_])?)*)$/;
|
|
var IPV4_RE = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
|
|
var IPV6_RE = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
var ALLOWED_TYPES = ["A", "AAAA", "CNAME", "TXT", "MX", "NS", "SRV", "CAA"];
|
|
var ValidationError = class extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = "ValidationError";
|
|
}
|
|
};
|
|
function validateDnsRecord(recordType, name, content, ttl, proxied) {
|
|
const rt = recordType.toUpperCase();
|
|
if (!ALLOWED_TYPES.includes(rt)) {
|
|
throw new ValidationError(`unsupported record type: ${recordType}`);
|
|
}
|
|
if (!NAME_RE.test(name)) {
|
|
throw new ValidationError(`invalid record name: ${name}`);
|
|
}
|
|
if (ttl !== 1 && (ttl < 60 || ttl > 86400)) {
|
|
throw new ValidationError("ttl must be 1 (auto) or 60-86400");
|
|
}
|
|
if (proxied && !["A", "AAAA", "CNAME"].includes(rt)) {
|
|
throw new ValidationError("proxied only allowed for A, AAAA, CNAME");
|
|
}
|
|
switch (rt) {
|
|
case "A":
|
|
if (!IPV4_RE.test(content)) {
|
|
throw new ValidationError("A record requires valid IPv4");
|
|
}
|
|
break;
|
|
case "AAAA":
|
|
if (!IPV6_RE.test(content)) {
|
|
throw new ValidationError("AAAA record requires valid IPv6");
|
|
}
|
|
break;
|
|
case "CNAME":
|
|
case "NS":
|
|
if (!content || content.includes(" ")) {
|
|
throw new ValidationError("CNAME/NS requires valid hostname");
|
|
}
|
|
break;
|
|
case "TXT":
|
|
if (!content || content.length > 2048) {
|
|
throw new ValidationError("TXT content length 1-2048");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
function certStatusFromExpiry(daysLeft) {
|
|
if (daysLeft < 0) return CERT_EXPIRED;
|
|
if (daysLeft <= 30) return CERT_WARNING;
|
|
return CERT_OK;
|
|
}
|
|
function shouldMonitorService(service, group) {
|
|
if (!service.enabled) return false;
|
|
if (!service.service_group_id) return true;
|
|
return group?.enabled ?? false;
|
|
}
|
|
function isValidIpv4(ip) {
|
|
const parts = ip.split(".");
|
|
if (parts.length !== 4) return false;
|
|
return parts.every((p) => {
|
|
const n = Number(p);
|
|
return Number.isInteger(n) && n >= 0 && n <= 255;
|
|
});
|
|
}
|
|
|
|
// src/subdomain.ts
|
|
function dnsNameToSubdomainLabel(recordName, zoneName) {
|
|
const rn = recordName.trim().replace(/\.+$/, "");
|
|
const zn = zoneName.trim().replace(/\.+$/, "");
|
|
if (!rn || !zn) return null;
|
|
if (rn === "*") return "*";
|
|
const wildcardFqdn = `*.${zn}`;
|
|
if (rn.toLowerCase() === wildcardFqdn.toLowerCase()) return "*";
|
|
if (rn.toLowerCase() === zn.toLowerCase()) return "@";
|
|
const zoneSuffix = `.${zn}`;
|
|
if (rn.toLowerCase().endsWith(zoneSuffix.toLowerCase())) {
|
|
const prefix = rn.slice(0, rn.length - zoneSuffix.length);
|
|
return prefix || "@";
|
|
}
|
|
if (!rn.includes(".")) return rn;
|
|
return null;
|
|
}
|
|
function subdomainLabelToFqdn(label, zoneName) {
|
|
return label === "@" ? zoneName : `${label}.${zoneName}`;
|
|
}
|
|
function normalizeDnsRecordName(recordName, zoneName) {
|
|
const label = dnsNameToSubdomainLabel(recordName, zoneName);
|
|
if (label == null) {
|
|
return recordName.trim().replace(/\.+$/, "");
|
|
}
|
|
return subdomainLabelToFqdn(label, zoneName);
|
|
}
|
|
function dnsRecordNamesMatch(left, right, zoneName) {
|
|
return normalizeDnsRecordName(left, zoneName).toLowerCase() === normalizeDnsRecordName(right, zoneName).toLowerCase();
|
|
}
|
|
|
|
// src/parse-fqdn.ts
|
|
function fqdnToDisplay(hostname, zoneName) {
|
|
if (hostname === "@") {
|
|
return zoneName;
|
|
}
|
|
return `${hostname}.${zoneName}`;
|
|
}
|
|
function parseFqdn(fqdn, knownZones) {
|
|
const normalized = fqdn.trim().toLowerCase();
|
|
if (!normalized) {
|
|
return null;
|
|
}
|
|
const zones = [...knownZones].sort((a, b) => b.length - a.length);
|
|
for (const zone of zones) {
|
|
const zoneLower = zone.toLowerCase();
|
|
if (normalized === zoneLower) {
|
|
return {
|
|
zoneName: zone,
|
|
hostname: "@",
|
|
fqdn: fqdnToDisplay("@", zone)
|
|
};
|
|
}
|
|
const suffix = `.${zoneLower}`;
|
|
if (normalized.endsWith(suffix)) {
|
|
const prefix = normalized.slice(0, -suffix.length);
|
|
if (prefix) {
|
|
return {
|
|
zoneName: zone,
|
|
hostname: prefix,
|
|
fqdn: fqdnToDisplay(prefix, zone)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function bindingToFqdn(binding) {
|
|
return binding.fqdn ?? fqdnToDisplay(binding.hostname, binding.zone_name);
|
|
}
|
|
|
|
// src/schemas.ts
|
|
import { z } from "zod";
|
|
var certMonitoringSchema = z.enum(["auto", "required", "skipped"]);
|
|
var groupSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
slug: z.string(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var groupWithStatsSchema = groupSchema.extend({
|
|
domain_count: z.number()
|
|
});
|
|
var serviceGroupTypeSchema = z.enum([
|
|
"vpn",
|
|
"network",
|
|
"internet",
|
|
"bgp",
|
|
"custom"
|
|
]);
|
|
var serviceGroupSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
type: serviceGroupTypeSchema.catch("custom"),
|
|
icon: z.string().nullable(),
|
|
domain: z.string().nullable(),
|
|
enabled: z.boolean(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var serviceSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
slug: z.string(),
|
|
service_group_id: z.number().nullable().optional(),
|
|
subdomain: z.string().optional(),
|
|
enabled: z.boolean().optional(),
|
|
computed_fqdn: z.string().nullable().optional(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var serviceDomainBindingSchema = z.object({
|
|
binding_id: z.number(),
|
|
domain_id: z.number(),
|
|
zone_name: z.string(),
|
|
hostname: z.string(),
|
|
fqdn: z.string(),
|
|
record_type: z.enum(["A", "CNAME"]).default("A"),
|
|
target_ips: z.array(z.string()).optional(),
|
|
target_ip: z.string().nullable().optional(),
|
|
target_cname: z.string().nullable().optional(),
|
|
sync_status: z.string().nullable()
|
|
}).transform((binding) => ({
|
|
...binding,
|
|
target_ips: binding.target_ips && binding.target_ips.length > 0 ? binding.target_ips : binding.target_ip ? [binding.target_ip] : [],
|
|
target_cname: binding.target_cname?.trim() || null,
|
|
record_type: binding.target_cname?.trim() ? "CNAME" : binding.record_type ?? "A"
|
|
}));
|
|
var serviceViewSchema = serviceSchema.extend({
|
|
subdomain: z.string().default(""),
|
|
enabled: z.boolean().default(false),
|
|
ips: z.array(z.string()).default([]),
|
|
domains: z.array(serviceDomainBindingSchema).default([])
|
|
});
|
|
var serviceGroupViewSchema = serviceGroupSchema.extend({
|
|
services: z.array(serviceViewSchema).default([])
|
|
});
|
|
var serviceGroupsResponseSchema = z.object({
|
|
groups: z.array(serviceGroupViewSchema).default([]),
|
|
ungrouped: z.array(serviceViewSchema).default([])
|
|
});
|
|
var domainSchema = z.object({
|
|
id: z.number(),
|
|
group_id: z.number().nullable(),
|
|
zone_name: z.string(),
|
|
cf_zone_id: z.string(),
|
|
status: z.string(),
|
|
cert_monitoring: certMonitoringSchema.default("auto"),
|
|
last_synced_at: z.string().nullable(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var domainListItemSchema = domainSchema.extend({
|
|
group_name: z.string().nullable(),
|
|
service_count: z.number()
|
|
});
|
|
var serviceBindingSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
service_id: z.number(),
|
|
hostname: z.string(),
|
|
dns_record_id: z.number().nullable(),
|
|
zone_name: z.string(),
|
|
group_id: z.number().nullable(),
|
|
group_name: z.string().nullable(),
|
|
service_name: z.string(),
|
|
service_slug: z.string(),
|
|
target_ip: z.string().nullable(),
|
|
target_ips: z.array(z.string()).optional(),
|
|
sync_status: z.string().nullable(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
}).transform((binding) => ({
|
|
...binding,
|
|
target_ips: binding.target_ips && binding.target_ips.length > 0 ? binding.target_ips : binding.target_ip ? [binding.target_ip] : []
|
|
}));
|
|
var dnsRecordSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
cf_record_id: z.string().nullable(),
|
|
record_type: z.string(),
|
|
name: z.string(),
|
|
content: z.string(),
|
|
ttl: z.number(),
|
|
proxied: z.boolean(),
|
|
priority: z.number().nullable(),
|
|
sync_status: z.string(),
|
|
origin: z.string(),
|
|
last_error: z.string().nullable(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var certificateSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
subdomain_id: z.number().nullable(),
|
|
hostname: z.string(),
|
|
expires_at: z.string().nullable(),
|
|
last_checked_at: z.string().nullable(),
|
|
last_error: z.string().nullable(),
|
|
status: z.string(),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var createGroupSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
|
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug")
|
|
});
|
|
var ipv4Schema = z.string().regex(
|
|
/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
|
|
"\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 IPv4"
|
|
);
|
|
var serviceDomainInputSchema = z.object({
|
|
fqdn: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 FQDN"),
|
|
target_ips: z.array(ipv4Schema).optional(),
|
|
target_cname: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 CNAME-\u0446\u0435\u043B\u044C").optional()
|
|
}).superRefine((data, ctx) => {
|
|
const hasIps = (data.target_ips?.length ?? 0) > 0;
|
|
const hasCname = Boolean(data.target_cname?.trim());
|
|
if (!hasIps && !hasCname) {
|
|
ctx.addIssue({
|
|
code: "custom",
|
|
message: "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 IP \u0438\u043B\u0438 CNAME-\u0446\u0435\u043B\u044C",
|
|
path: ["target_ips"]
|
|
});
|
|
}
|
|
if (hasIps && hasCname) {
|
|
ctx.addIssue({
|
|
code: "custom",
|
|
message: "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043B\u0438\u0431\u043E IP, \u043B\u0438\u0431\u043E CNAME-\u0446\u0435\u043B\u044C",
|
|
path: ["target_cname"]
|
|
});
|
|
}
|
|
});
|
|
var createServiceSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
|
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug")
|
|
});
|
|
var createServiceWithConfigSchema = createServiceSchema.extend({
|
|
service_group_id: z.number().nullable().optional(),
|
|
ips: z.array(ipv4Schema).default([]),
|
|
domains: z.array(serviceDomainInputSchema).default([])
|
|
});
|
|
var createServiceBindingSchema = z.object({
|
|
domain_id: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043E\u043C\u0435\u043D"),
|
|
service_id: z.string().min(1, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441"),
|
|
hostname: z.string().optional(),
|
|
target_ip: z.string().optional()
|
|
});
|
|
var updateDomainGroupSchema = z.object({
|
|
group_id: z.number().nullable(),
|
|
status: z.string().optional()
|
|
});
|
|
var createDomainSchema = z.object({
|
|
zone_name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u0437\u043E\u043D\u044B"),
|
|
group_id: z.string()
|
|
});
|
|
var loginSchema = z.object({
|
|
username: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F"),
|
|
password: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0430\u0440\u043E\u043B\u044C")
|
|
});
|
|
var createDnsRecordSchema = z.object({
|
|
record_type: z.enum(["A", "AAAA", "CNAME", "TXT", "MX"]),
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F"),
|
|
content: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435"),
|
|
ttl: z.number().int().min(1),
|
|
proxied: z.boolean()
|
|
});
|
|
var subdomainSchema = z.object({
|
|
id: z.number(),
|
|
domain_id: z.number(),
|
|
name: z.string(),
|
|
fqdn: z.string(),
|
|
enabled: z.boolean(),
|
|
cert_monitoring: certMonitoringSchema.default("auto"),
|
|
created_at: z.string(),
|
|
updated_at: z.string()
|
|
});
|
|
var createSubdomainSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043F\u043E\u0434\u0434\u043E\u043C\u0435\u043D\u0430")
|
|
});
|
|
var updateSubdomainSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u0438\u043C\u044F \u043F\u043E\u0434\u0434\u043E\u043C\u0435\u043D\u0430").optional(),
|
|
enabled: z.boolean().optional(),
|
|
cert_monitoring: certMonitoringSchema.optional()
|
|
});
|
|
var updateDomainSchema = z.object({
|
|
group_id: z.number().nullable().optional(),
|
|
status: z.string().optional(),
|
|
cert_monitoring: certMonitoringSchema.optional()
|
|
});
|
|
var updateServiceConfigSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435").optional(),
|
|
slug: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 slug").optional(),
|
|
service_group_id: z.number().nullable().optional(),
|
|
ips: z.array(ipv4Schema).optional(),
|
|
domains: z.array(serviceDomainInputSchema).optional()
|
|
});
|
|
var createServiceGroupSchema = z.object({
|
|
name: z.string().min(1, "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435"),
|
|
type: serviceGroupTypeSchema.default("custom"),
|
|
icon: z.string().nullable().optional(),
|
|
domain: z.string().nullable().optional()
|
|
});
|
|
var toggleEnabledSchema = z.object({
|
|
enabled: z.boolean()
|
|
});
|
|
var reorderServicesSchema = z.object({
|
|
group_id: z.union([z.number(), z.null()]).optional().default(null),
|
|
service_ids: z.array(z.number().int().positive()).min(1)
|
|
});
|
|
export {
|
|
CERT_ERROR,
|
|
CERT_EXPIRED,
|
|
CERT_MONITORING_VALUES,
|
|
CERT_MONITOR_AUTO,
|
|
CERT_MONITOR_REQUIRED,
|
|
CERT_MONITOR_SKIPPED,
|
|
CERT_OK,
|
|
CERT_UNKNOWN,
|
|
CERT_WARNING,
|
|
SYNC_CONFLICT,
|
|
SYNC_ERROR,
|
|
SYNC_PENDING_DELETE,
|
|
SYNC_PENDING_PUSH,
|
|
SYNC_SYNCED,
|
|
ValidationError,
|
|
bindingToFqdn,
|
|
certMonitoringSchema,
|
|
certStatusFromExpiry,
|
|
certificateSchema,
|
|
createDnsRecordSchema,
|
|
createDomainSchema,
|
|
createGroupSchema,
|
|
createServiceBindingSchema,
|
|
createServiceGroupSchema,
|
|
createServiceSchema,
|
|
createServiceWithConfigSchema,
|
|
createSubdomainSchema,
|
|
dnsNameToSubdomainLabel,
|
|
dnsRecordNamesMatch,
|
|
dnsRecordSchema,
|
|
domainListItemSchema,
|
|
domainSchema,
|
|
fqdnToDisplay,
|
|
groupSchema,
|
|
groupWithStatsSchema,
|
|
isValidIpv4,
|
|
loginSchema,
|
|
normalizeDnsRecordName,
|
|
parseFqdn,
|
|
reorderServicesSchema,
|
|
serviceBindingSchema,
|
|
serviceDomainBindingSchema,
|
|
serviceGroupSchema,
|
|
serviceGroupTypeSchema,
|
|
serviceGroupViewSchema,
|
|
serviceGroupsResponseSchema,
|
|
serviceSchema,
|
|
serviceViewSchema,
|
|
shouldMonitorService,
|
|
subdomainLabelToFqdn,
|
|
subdomainSchema,
|
|
toggleEnabledSchema,
|
|
updateDomainGroupSchema,
|
|
updateDomainSchema,
|
|
updateServiceConfigSchema,
|
|
updateSubdomainSchema,
|
|
validateDnsRecord
|
|
};
|