@aigne/afs-dns 1.11.0-beta.6

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/clouddns/adapter.ts","../src/errors/index.ts","../src/permissions/types.ts","../src/permissions/checker.ts","../src/security/audit-log.ts","../src/security/input-sanitizer.ts","../src/security/rate-limiter.ts","../src/types/record.ts","../src/provider/dns-provider.ts","../src/route53/parser.ts","../src/route53/adapter.ts","../src/validation/name-validator.ts","../src/validation/write-validator.ts"],"sourcesContent":["/**\n * Cloud DNS Adapter\n *\n * Google Cloud DNS implementation of the DNS provider interface.\n */\n\nimport type { DNSRecord, DNSRecordSet } from \"../types/record.js\";\nimport type { DNSZone } from \"../types/zone.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Google Cloud DNS Zone interface (from @google-cloud/dns)\n */\ninterface CloudDNSZoneInterface {\n name: string;\n metadata: {\n dnsName: string;\n id: string;\n nameServers?: string[];\n creationTime?: string;\n };\n getRecords(): Promise<[CloudDNSRecordInterface[]]>;\n createChange(config: {\n add?: CloudDNSRecordInterface | CloudDNSRecordInterface[];\n delete?: CloudDNSRecordInterface | CloudDNSRecordInterface[];\n }): Promise<[{ id: string; status: string }]>;\n record(\n type: string,\n config: { name: string; ttl: number; data: string | string[] },\n ): CloudDNSRecordInterface;\n}\n\n/**\n * Google Cloud DNS Record interface\n */\ninterface CloudDNSRecordInterface {\n name: string;\n type: string;\n ttl: number;\n data: string[];\n}\n\n/**\n * Google Cloud DNS client interface (from @google-cloud/dns)\n */\ninterface CloudDNSClientInterface {\n getZones(): Promise<[CloudDNSZoneInterface[]]>;\n zone(name: string): CloudDNSZoneInterface | null;\n}\n\nexport interface CloudDNSAdapterOptions {\n /** Google Cloud Project ID */\n projectId?: string;\n /** Path to service account key file */\n keyFilename?: string;\n /** Pre-configured client (for testing) */\n client?: CloudDNSClientInterface;\n}\n\n// ============================================================================\n// Adapter Implementation\n// ============================================================================\n\nexport class CloudDNSAdapter {\n private client: CloudDNSClientInterface;\n private zoneCache = new Map<string, CloudDNSZoneInterface>();\n\n constructor(options: CloudDNSAdapterOptions = {}) {\n if (options.client) {\n this.client = options.client;\n } else {\n // Dynamically import @google-cloud/dns to avoid hard dependency\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { DNS } = require(\"@google-cloud/dns\");\n this.client = new DNS({\n projectId: options.projectId,\n keyFilename: options.keyFilename,\n });\n }\n }\n\n // ==========================================================================\n // Read Operations\n // ==========================================================================\n\n /**\n * List all managed zones\n */\n async listZones(): Promise<DNSZone[]> {\n const [zones] = await this.client.getZones();\n\n return zones.map((zone) => ({\n domain: zone.metadata.dnsName.replace(/\\.$/, \"\"),\n id: zone.metadata.id,\n provider: \"clouddns\",\n nameservers: zone.metadata.nameServers ?? [],\n recordCount: undefined,\n }));\n }\n\n /**\n * List all record names in a zone\n */\n async listRecords(zoneDomain: string): Promise<string[]> {\n const zone = await this.getZone(zoneDomain);\n const [records] = await zone.getRecords();\n\n const normalizedDomain = zoneDomain.endsWith(\".\") ? zoneDomain : `${zoneDomain}.`;\n\n // Get unique names and add _zone\n const names = new Set<string>();\n for (const record of records) {\n const name = this.extractRecordName(record.name, normalizedDomain);\n names.add(name);\n }\n names.add(\"_zone\");\n\n return Array.from(names);\n }\n\n /**\n * Get all records for a specific name\n */\n async getRecord(zoneDomain: string, name: string): Promise<DNSRecordSet> {\n const zone = await this.getZone(zoneDomain);\n const [records] = await zone.getRecords();\n\n const normalizedDomain = zoneDomain.endsWith(\".\") ? zoneDomain : `${zoneDomain}.`;\n const fqdn = name === \"@\" ? zoneDomain : `${name}.${zoneDomain}`;\n const targetName = name === \"@\" ? normalizedDomain : `${name}.${normalizedDomain}`;\n\n // Find matching records\n const matchingRecords = records.filter((r) => r.name === targetName);\n\n return {\n fqdn,\n records: matchingRecords.map((r) => this.parseRecord(r)),\n };\n }\n\n /**\n * Get zone metadata\n */\n async getZoneMetadata(zoneDomain: string): Promise<DNSZone> {\n const zone = await this.getZone(zoneDomain);\n\n return {\n domain: zone.metadata.dnsName.replace(/\\.$/, \"\"),\n id: zone.metadata.id,\n provider: \"clouddns\",\n nameservers: zone.metadata.nameServers ?? [],\n recordCount: undefined,\n };\n }\n\n // ==========================================================================\n // Write Operations\n // ==========================================================================\n\n /**\n * Set records for a name (create or update)\n */\n async setRecord(\n zoneDomain: string,\n name: string,\n records: DNSRecord[],\n _options?: { force?: boolean },\n ): Promise<void> {\n const zone = await this.getZone(zoneDomain);\n const normalizedDomain = zoneDomain.endsWith(\".\") ? zoneDomain : `${zoneDomain}.`;\n const fqdn = name === \"@\" ? normalizedDomain : `${name}.${normalizedDomain}`;\n\n // Check for existing records to delete first\n const [existingRecords] = await zone.getRecords();\n const toDelete = existingRecords.filter((r) => {\n if (r.name !== fqdn) return false;\n // Match by type if we're setting the same type\n return records.some((newRec) => newRec.type === r.type);\n });\n\n for (const record of records) {\n const cloudRecord = this.buildCloudRecord(zone, fqdn, record);\n\n const changeConfig: {\n add: CloudDNSRecordInterface;\n delete?: CloudDNSRecordInterface[];\n } = { add: cloudRecord };\n\n // If there are existing records of the same type, delete them first\n const matchingDeletes = toDelete.filter((r) => r.type === record.type);\n if (matchingDeletes.length > 0) {\n changeConfig.delete = matchingDeletes;\n }\n\n await zone.createChange(changeConfig);\n }\n }\n\n /**\n * Delete records for a name\n */\n async deleteRecord(\n zoneDomain: string,\n name: string,\n type?: string,\n _options?: { force?: boolean },\n ): Promise<void> {\n const zone = await this.getZone(zoneDomain);\n const normalizedDomain = zoneDomain.endsWith(\".\") ? zoneDomain : `${zoneDomain}.`;\n const fqdn = name === \"@\" ? normalizedDomain : `${name}.${normalizedDomain}`;\n\n // Get existing records\n const [existingRecords] = await zone.getRecords();\n const toDelete = existingRecords.filter((r) => {\n if (r.name !== fqdn) return false;\n if (type && r.type !== type) return false;\n return true;\n });\n\n if (toDelete.length === 0) {\n return; // Nothing to delete\n }\n\n await zone.createChange({ delete: toDelete });\n }\n\n // ==========================================================================\n // Helper Methods\n // ==========================================================================\n\n /**\n * Get zone by domain name\n */\n private async getZone(zoneDomain: string): Promise<CloudDNSZoneInterface> {\n // Check cache\n if (this.zoneCache.has(zoneDomain)) {\n return this.zoneCache.get(zoneDomain)!;\n }\n\n // Find zone by listing all zones\n const [zones] = await this.client.getZones();\n const normalizedDomain = zoneDomain.endsWith(\".\") ? zoneDomain : `${zoneDomain}.`;\n\n const zone = zones.find((z) => z.metadata.dnsName === normalizedDomain);\n\n if (!zone) {\n const error = new Error(`Zone not found: ${zoneDomain}`);\n error.name = \"ZoneNotFound\";\n throw error;\n }\n\n this.zoneCache.set(zoneDomain, zone);\n return zone;\n }\n\n /**\n * Extract record name from FQDN\n */\n private extractRecordName(fqdn: string, zoneDomain: string): string {\n const normalizedFqdn = fqdn.endsWith(\".\") ? fqdn : `${fqdn}.`;\n const normalizedZone = zoneDomain.endsWith(\".\") ? zoneDomain : `${zoneDomain}.`;\n\n if (normalizedFqdn === normalizedZone) {\n return \"@\";\n }\n\n // Remove zone suffix\n const name = normalizedFqdn.replace(\n new RegExp(`\\\\.?${normalizedZone.replace(/\\./g, \"\\\\.\")}$`),\n \"\",\n );\n return name || \"@\";\n }\n\n /**\n * Parse Cloud DNS record to DNSRecord\n */\n private parseRecord(record: CloudDNSRecordInterface): DNSRecord {\n const { type, ttl, data } = record;\n\n switch (type) {\n case \"A\":\n case \"AAAA\":\n case \"NS\":\n case \"TXT\":\n case \"PTR\":\n return { type: type as DNSRecord[\"type\"], ttl, values: data };\n\n case \"CNAME\":\n return { type: \"CNAME\", ttl, values: data[0] ?? \"\" };\n\n case \"MX\":\n return {\n type: \"MX\",\n ttl,\n values: data.map((v) => {\n const [priority, value] = v.split(\" \", 2);\n return { priority: Number.parseInt(priority ?? \"0\", 10), value: value ?? \"\" };\n }),\n };\n\n case \"SRV\":\n return {\n type: \"SRV\",\n ttl,\n values: data.map((v) => {\n const [priority, weight, port, target] = v.split(\" \", 4);\n return {\n priority: Number.parseInt(priority ?? \"0\", 10),\n weight: Number.parseInt(weight ?? \"0\", 10),\n port: Number.parseInt(port ?? \"0\", 10),\n target: target ?? \"\",\n };\n }),\n };\n\n case \"CAA\":\n return {\n type: \"CAA\",\n ttl,\n values: data.map((v) => {\n const match = v.match(/^(\\d+)\\s+(\\w+)\\s+\"?(.+?)\"?$/);\n if (match) {\n return {\n flags: Number.parseInt(match[1] ?? \"0\", 10),\n tag: match[2] ?? \"\",\n value: match[3] ?? \"\",\n };\n }\n return { flags: 0, tag: \"\", value: v };\n }),\n };\n\n case \"SOA\": {\n const soaParts = data[0]?.split(\" \") ?? [];\n return {\n type: \"SOA\",\n ttl,\n values: {\n mname: soaParts[0] ?? \"\",\n rname: soaParts[1] ?? \"\",\n serial: Number.parseInt(soaParts[2] ?? \"0\", 10),\n refresh: Number.parseInt(soaParts[3] ?? \"0\", 10),\n retry: Number.parseInt(soaParts[4] ?? \"0\", 10),\n expire: Number.parseInt(soaParts[5] ?? \"0\", 10),\n minimum: Number.parseInt(soaParts[6] ?? \"0\", 10),\n },\n };\n }\n\n default:\n return { type: type as DNSRecord[\"type\"], ttl, values: data };\n }\n }\n\n /**\n * Build Cloud DNS record from DNSRecord\n */\n private buildCloudRecord(\n zone: CloudDNSZoneInterface,\n fqdn: string,\n record: DNSRecord,\n ): CloudDNSRecordInterface {\n const { type, ttl, values } = record;\n let data: string[];\n\n switch (type) {\n case \"A\":\n case \"AAAA\":\n case \"NS\":\n case \"TXT\":\n case \"PTR\":\n data = Array.isArray(values) ? (values as string[]) : [values as string];\n break;\n\n case \"CNAME\":\n data = [typeof values === \"string\" ? values : ((values as string[])[0] ?? \"\")];\n break;\n\n case \"MX\":\n data = (values as Array<{ priority: number; value: string }>).map(\n (mx) => `${mx.priority} ${mx.value}`,\n );\n break;\n\n case \"SRV\":\n data = (\n values as Array<{ priority: number; weight: number; port: number; target: string }>\n ).map((srv) => `${srv.priority} ${srv.weight} ${srv.port} ${srv.target}`);\n break;\n\n case \"CAA\":\n data = (values as Array<{ flags: number; tag: string; value: string }>).map(\n (caa) => `${caa.flags} ${caa.tag} \"${caa.value}\"`,\n );\n break;\n\n default:\n data = Array.isArray(values) ? (values as string[]) : [values as string];\n }\n\n return zone.record(type.toLowerCase(), { name: fqdn, ttl, data });\n }\n}\n","/**\n * DNS Provider Errors\n *\n * Custom error types and Route53 error mapping.\n */\n\n// ============================================================================\n// Base Error Classes\n// ============================================================================\n\nexport class AFSDNSError extends Error {\n readonly code: string;\n readonly cause?: Error;\n\n constructor(message: string, options?: { code?: string; cause?: Error }) {\n super(message);\n this.name = \"AFSDNSError\";\n this.code = options?.code ?? \"DNS_ERROR\";\n this.cause = options?.cause;\n }\n}\n\nexport class AFSDNSNotFoundError extends AFSDNSError {\n constructor(message: string, options?: { cause?: Error }) {\n super(message, { code: \"DNS_NOT_FOUND\", ...options });\n this.name = \"AFSDNSNotFoundError\";\n }\n}\n\nexport class AFSDNSPermissionDeniedError extends AFSDNSError {\n constructor(message: string, options?: { cause?: Error }) {\n super(message, { code: \"DNS_PERMISSION_DENIED\", ...options });\n this.name = \"AFSDNSPermissionDeniedError\";\n }\n}\n\nexport class AFSDNSRateLimitError extends AFSDNSError {\n readonly retryAfter: number;\n\n constructor(message: string, options?: { cause?: Error; retryAfter?: number }) {\n super(message, { code: \"DNS_RATE_LIMIT\", ...options });\n this.name = \"AFSDNSRateLimitError\";\n this.retryAfter = options?.retryAfter ?? 1;\n }\n}\n\nexport class AFSDNSInvalidArgumentError extends AFSDNSError {\n constructor(message: string, options?: { cause?: Error }) {\n super(message, { code: \"DNS_INVALID_ARGUMENT\", ...options });\n this.name = \"AFSDNSInvalidArgumentError\";\n }\n}\n\nexport class AFSDNSConflictError extends AFSDNSError {\n constructor(message: string, options?: { cause?: Error }) {\n super(message, { code: \"DNS_CONFLICT\", ...options });\n this.name = \"AFSDNSConflictError\";\n }\n}\n\nexport class AFSDNSNetworkError extends AFSDNSError {\n constructor(message: string, options?: { cause?: Error }) {\n super(message, { code: \"DNS_NETWORK_ERROR\", ...options });\n this.name = \"AFSDNSNetworkError\";\n }\n}\n\n// ============================================================================\n// Error Mapping\n// ============================================================================\n\n/**\n * Map Route53 error to AFS DNS error\n */\nexport function mapRoute53Error(error: Error): AFSDNSError {\n const errorName = error.name;\n const sanitizedMessage = sanitizeErrorMessage(error.message);\n\n switch (errorName) {\n case \"NoSuchHostedZone\":\n return new AFSDNSNotFoundError(\"Zone not found\", { cause: error });\n\n case \"AccessDenied\":\n case \"AccessDeniedException\":\n return new AFSDNSPermissionDeniedError(\"Access denied to DNS zone\", { cause: error });\n\n case \"Throttling\":\n case \"ThrottlingException\":\n return new AFSDNSRateLimitError(\"Rate limit exceeded, please retry\", {\n cause: error,\n retryAfter: extractRetryAfter(error),\n });\n\n case \"InvalidInput\":\n case \"InvalidChangeBatch\":\n return new AFSDNSInvalidArgumentError(`Invalid DNS record: ${sanitizedMessage}`, {\n cause: error,\n });\n\n case \"PriorRequestNotComplete\":\n return new AFSDNSConflictError(\"Previous DNS change is still processing, please retry\", {\n cause: error,\n });\n\n case \"NetworkingError\":\n case \"ECONNREFUSED\":\n case \"ETIMEDOUT\":\n return new AFSDNSNetworkError(\"Network error connecting to DNS service\", {\n cause: error,\n });\n\n case \"ServiceUnavailable\":\n return new AFSDNSNetworkError(\"DNS service temporarily unavailable\", {\n cause: error,\n });\n\n default:\n return new AFSDNSError(`DNS operation failed: ${sanitizedMessage}`, {\n cause: error,\n });\n }\n}\n\n/**\n * Check if an error is retryable\n */\nexport function isRetryableError(error: Error): boolean {\n const retryableErrors = [\n \"Throttling\",\n \"ThrottlingException\",\n \"PriorRequestNotComplete\",\n \"ServiceUnavailable\",\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n ];\n\n return retryableErrors.includes(error.name);\n}\n\n/**\n * Sanitize error message to remove sensitive information\n */\nexport function sanitizeErrorMessage(message: string): string {\n let sanitized = message;\n\n // Remove AWS account IDs (12 digits)\n sanitized = sanitized.replace(/\\b\\d{12}\\b/g, \"***\");\n\n // Remove ARNs\n sanitized = sanitized.replace(/arn:aws:[^:\\s]+:[^:\\s]*:[^:\\s]*:[^\\s]+/g, \"arn:aws:***\");\n\n // Remove hosted zone IDs\n sanitized = sanitized.replace(/\\/hostedzone\\/[A-Z0-9]+/g, \"/hostedzone/***\");\n sanitized = sanitized.replace(/\\bZ[A-Z0-9]{10,}\\b/g, \"***\");\n\n // Remove IAM user/role names\n sanitized = sanitized.replace(/user\\/[^\\s,]+/g, \"user/***\");\n sanitized = sanitized.replace(/role\\/[^\\s,]+/g, \"role/***\");\n\n return sanitized;\n}\n\n/**\n * Extract retry-after hint from error if available\n */\nfunction extractRetryAfter(_error: Error): number {\n // Default to 1 second for throttling\n return 1;\n}\n","/**\n * DNS Permission Types\n *\n * Defines dangerous operations and permission presets.\n */\n\n/**\n * Dangerous operations that are blocked by default\n */\nexport type DangerousOperation = \"modify_ns\" | \"modify_root\" | \"modify_wildcard\" | \"delete_zone\";\n\n/**\n * Permission presets\n */\nexport type PermissionPreset = \"safe\" | \"standard\" | \"full\";\n\n/**\n * Permission configuration\n */\nexport interface PermissionConfig {\n /** Preset mode */\n preset?: PermissionPreset;\n /** Explicit dangerous operation permissions */\n dangerous?: Partial<Record<DangerousOperation, boolean>>;\n}\n\n/**\n * Preset definitions\n */\nexport const PRESETS: Record<\n PermissionPreset,\n {\n allowDelete: boolean;\n dangerous: Record<DangerousOperation, boolean>;\n }\n> = {\n safe: {\n allowDelete: false,\n dangerous: {\n modify_ns: false,\n modify_root: false,\n modify_wildcard: false,\n delete_zone: false,\n },\n },\n standard: {\n allowDelete: true,\n dangerous: {\n modify_ns: false,\n modify_root: false,\n modify_wildcard: false,\n delete_zone: false,\n },\n },\n full: {\n allowDelete: true,\n dangerous: {\n modify_ns: true,\n modify_root: true,\n modify_wildcard: true,\n delete_zone: false, // Never included in preset\n },\n },\n};\n","/**\n * DNS Permission Checker\n *\n * Validates operations against configured permissions.\n */\n\nimport { type DangerousOperation, type PermissionConfig, PRESETS } from \"./types.js\";\n\n/**\n * Operation context for permission checking\n */\nexport interface OperationContext {\n /** Record name (e.g., \"www\", \"@\", \"*\") */\n name: string;\n /** Record type (e.g., \"A\", \"NS\") */\n type?: string;\n /** Operation type */\n operation: \"read\" | \"write\" | \"delete\";\n}\n\n/**\n * Permission check result\n */\nexport interface PermissionResult {\n allowed: boolean;\n reason?: string;\n requiredConfig?: string;\n deniedOperation?: string;\n}\n\n/**\n * Check if an operation is allowed\n */\nexport function checkPermission(\n context: OperationContext,\n config: PermissionConfig,\n): PermissionResult {\n // Read is always allowed\n if (context.operation === \"read\") {\n return { allowed: true };\n }\n\n // Get effective permissions\n const effective = getEffectivePermissions(config);\n\n // Check delete permission (from preset)\n if (context.operation === \"delete\" && !effective.allowDelete) {\n return {\n allowed: false,\n reason: \"Delete operations are not allowed with 'safe' preset\",\n requiredConfig: 'preset = \"standard\" or \"full\"',\n deniedOperation: \"delete\",\n };\n }\n\n // Check dangerous operations\n const dangerousOp = detectDangerousOperation(context);\n if (dangerousOp) {\n if (!effective.dangerous[dangerousOp]) {\n return {\n allowed: false,\n reason: `Operation '${dangerousOp}' is not allowed`,\n requiredConfig: `[mounts.options.dangerous]\\n${dangerousOp} = true`,\n deniedOperation: dangerousOp,\n };\n }\n }\n\n return { allowed: true };\n}\n\n/**\n * Get effective permissions by merging preset with explicit config\n */\nexport function getEffectivePermissions(config: PermissionConfig): {\n allowDelete: boolean;\n dangerous: Record<DangerousOperation, boolean>;\n} {\n // Start with preset (default to standard)\n const preset = config.preset ?? \"standard\";\n const presetConfig = PRESETS[preset];\n\n // Deep copy to avoid mutating the preset\n const result = {\n allowDelete: presetConfig.allowDelete,\n dangerous: { ...presetConfig.dangerous },\n };\n\n // Override with explicit dangerous config\n if (config.dangerous) {\n for (const [key, value] of Object.entries(config.dangerous)) {\n if (value !== undefined) {\n result.dangerous[key as DangerousOperation] = value;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Detect if an operation is dangerous\n */\nexport function detectDangerousOperation(context: OperationContext): DangerousOperation | null {\n const { name, type, operation } = context;\n\n // Read is never dangerous\n if (operation === \"read\") {\n return null;\n }\n\n // Modifying root domain (@)\n if (name === \"@\") {\n return \"modify_root\";\n }\n\n // Modifying NS records\n if (type === \"NS\") {\n return \"modify_ns\";\n }\n\n // Modifying wildcard records\n if (name === \"*\" || name.startsWith(\"*.\")) {\n return \"modify_wildcard\";\n }\n\n return null;\n}\n\n/**\n * Create a user-friendly error message for permission denial\n */\nexport function formatPermissionError(result: PermissionResult): string {\n let message = `Permission denied: ${result.reason}`;\n if (result.requiredConfig) {\n message += `\\n\\nTo enable this operation, add to your config:\\n${result.requiredConfig}`;\n }\n return message;\n}\n","/**\n * Audit Logger\n *\n * Logs DNS write operations for security auditing.\n */\n\nexport interface AuditLogEntry {\n timestamp: number;\n action: \"create\" | \"update\" | \"delete\" | \"denied\";\n zone: string;\n recordName: string;\n recordType?: string;\n values?: unknown[];\n recordCount?: number;\n operation?: string;\n reason?: string;\n context?: {\n userId?: string;\n sessionId?: string;\n [key: string]: unknown;\n };\n}\n\nexport interface AuditLogConfig {\n /** Custom handler for log entries */\n handler?: (entry: AuditLogEntry) => void;\n /** Whether to mask sensitive values */\n maskValues?: boolean;\n /** Whether to store entries in memory */\n storeInMemory?: boolean;\n /** Maximum entries to keep in memory */\n maxEntries?: number;\n}\n\nexport interface WriteLogParams {\n zone: string;\n recordName: string;\n recordType: string;\n action: \"create\" | \"update\";\n values: unknown[];\n context?: Record<string, unknown>;\n}\n\nexport interface DeleteLogParams {\n zone: string;\n recordName: string;\n recordType?: string;\n context?: Record<string, unknown>;\n}\n\nexport interface PermissionDeniedLogParams {\n zone: string;\n recordName: string;\n operation: string;\n reason: string;\n context?: Record<string, unknown>;\n}\n\nexport class AuditLogger {\n private readonly config: AuditLogConfig;\n private entries: AuditLogEntry[] = [];\n\n constructor(config: AuditLogConfig = {}) {\n this.config = config;\n }\n\n /**\n * Log a write operation\n */\n logWrite(params: WriteLogParams): void {\n const entry: AuditLogEntry = {\n timestamp: Date.now(),\n action: params.action,\n zone: params.zone,\n recordName: params.recordName,\n recordType: params.recordType,\n values: this.config.maskValues ? this.maskValues(params.values) : params.values,\n recordCount: Array.isArray(params.values) ? params.values.length : 1,\n context: params.context,\n };\n\n this.emit(entry);\n }\n\n /**\n * Log a delete operation\n */\n logDelete(params: DeleteLogParams): void {\n const entry: AuditLogEntry = {\n timestamp: Date.now(),\n action: \"delete\",\n zone: params.zone,\n recordName: params.recordName,\n recordType: params.recordType,\n context: params.context,\n };\n\n this.emit(entry);\n }\n\n /**\n * Log a permission denied event\n */\n logPermissionDenied(params: PermissionDeniedLogParams): void {\n const entry: AuditLogEntry = {\n timestamp: Date.now(),\n action: \"denied\",\n zone: params.zone,\n recordName: params.recordName,\n operation: params.operation,\n reason: params.reason,\n context: params.context,\n };\n\n this.emit(entry);\n }\n\n /**\n * Get recent log entries (if storeInMemory is enabled)\n */\n getRecentEntries(count: number): AuditLogEntry[] {\n return this.entries.slice(-count).reverse();\n }\n\n /**\n * Get entries by zone (if storeInMemory is enabled)\n */\n getEntriesByZone(zone: string): AuditLogEntry[] {\n return this.entries.filter((e) => e.zone === zone);\n }\n\n /**\n * Emit a log entry\n */\n private emit(entry: AuditLogEntry): void {\n // Call custom handler if provided\n if (this.config.handler) {\n this.config.handler(entry);\n }\n\n // Store in memory if configured\n if (this.config.storeInMemory) {\n this.entries.push(entry);\n\n // Trim to max entries\n if (this.config.maxEntries && this.entries.length > this.config.maxEntries) {\n this.entries = this.entries.slice(-this.config.maxEntries);\n }\n }\n }\n\n /**\n * Mask sensitive values\n */\n private maskValues(values: unknown[]): unknown[] {\n return values.map((v) => {\n if (typeof v === \"string\") {\n return this.maskString(v);\n }\n return v;\n });\n }\n\n /**\n * Mask sensitive parts of a string\n */\n private maskString(value: string): string {\n // Mask email addresses\n return value.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g, \"***@***.***\");\n }\n}\n","/**\n * Input Sanitizer\n *\n * Sanitizes DNS record values to prevent injection attacks.\n */\n\nexport interface SanitizeResult {\n valid: boolean;\n value?: string;\n error?: string;\n}\n\n/**\n * Sanitize a TXT record value\n */\nexport function sanitizeTXTValue(value: string): string {\n // TXT records can contain almost any content\n // Just ensure proper escaping of backslashes\n return value;\n}\n\n/**\n * Reject strings containing null bytes\n */\nexport function rejectNullBytes(value: string | string[]): void {\n const values = Array.isArray(value) ? value : [value];\n\n for (const v of values) {\n if (v.includes(\"\\x00\")) {\n throw new Error(\"Value contains null byte which is not allowed\");\n }\n }\n}\n\n/**\n * Validate input length based on type\n */\nexport function validateInputLength(\n value: string,\n type: \"domain\" | \"label\" | \"txt\",\n): { valid: boolean; error?: string } {\n switch (type) {\n case \"domain\":\n if (value.length > 253) {\n return { valid: false, error: \"Domain name exceeds 253 characters\" };\n }\n break;\n\n case \"label\": {\n // Check each label\n const labels = value.split(\".\");\n for (const label of labels) {\n if (label.length > 63) {\n return { valid: false, error: \"Label exceeds 63 characters\" };\n }\n }\n break;\n }\n\n case \"txt\":\n // TXT records can be long, but set a reasonable limit\n if (value.length > 4000) {\n return { valid: false, error: \"TXT value exceeds 4000 characters\" };\n }\n break;\n }\n\n return { valid: true };\n}\n\n/**\n * Escape special characters based on record type\n */\nexport function escapeSpecialCharacters(value: string, recordType: string): string {\n if (!value) return \"\";\n\n switch (recordType) {\n case \"TXT\":\n // TXT records may need semicolon handling in some contexts\n // but Route53 handles this transparently\n return value;\n\n default:\n // Other record types don't need special escaping\n return value;\n }\n}\n\n/**\n * Sanitize a record value based on type\n */\nexport function sanitizeRecordValue(value: string, recordType: string): SanitizeResult {\n // Check for null bytes\n try {\n rejectNullBytes(value);\n } catch (e) {\n return { valid: false, error: (e as Error).message };\n }\n\n // Validate length\n const lengthType = recordType === \"TXT\" ? \"txt\" : \"domain\";\n const lengthResult = validateInputLength(value, lengthType);\n if (!lengthResult.valid) {\n return { valid: false, error: lengthResult.error };\n }\n\n // Escape special characters\n const sanitized = escapeSpecialCharacters(value, recordType);\n\n return { valid: true, value: sanitized };\n}\n","/**\n * Rate Limiter\n *\n * Implements rate limiting for Route53 API calls.\n * Route53 allows 5 requests per second per account.\n */\n\nexport interface RateLimiterConfig {\n /** Maximum requests per window */\n maxRequests: number;\n /** Window size in milliseconds */\n windowMs: number;\n /** Whether to queue requests instead of rejecting */\n queueRequests?: boolean;\n}\n\nexport class RateLimiter {\n private tokens: number;\n private lastRefill: number;\n private readonly config: RateLimiterConfig;\n private queue: Array<() => void> = [];\n\n constructor(config: RateLimiterConfig) {\n this.config = config;\n this.tokens = config.maxRequests;\n this.lastRefill = Date.now();\n }\n\n /**\n * Try to acquire a token without waiting\n * @returns true if token acquired, false if rate limited\n */\n async tryAcquire(): Promise<boolean> {\n this.refillTokens();\n\n if (this.tokens > 0) {\n this.tokens--;\n return true;\n }\n\n return false;\n }\n\n /**\n * Acquire a token, waiting if necessary\n * Only works if queueRequests is enabled\n */\n async acquire(): Promise<void> {\n this.refillTokens();\n\n if (this.tokens > 0) {\n this.tokens--;\n return;\n }\n\n if (!this.config.queueRequests) {\n throw new Error(\"Rate limit exceeded\");\n }\n\n // Wait for token\n return new Promise((resolve) => {\n this.queue.push(resolve);\n this.scheduleQueueProcessing();\n });\n }\n\n /**\n * Get remaining tokens\n */\n getRemaining(): number {\n this.refillTokens();\n return this.tokens;\n }\n\n /**\n * Get time until next refill in milliseconds\n */\n getResetTime(): number {\n const elapsed = Date.now() - this.lastRefill;\n return Math.max(0, this.config.windowMs - elapsed);\n }\n\n /**\n * Refill tokens based on elapsed time\n */\n private refillTokens(): void {\n const now = Date.now();\n const elapsed = now - this.lastRefill;\n\n if (elapsed >= this.config.windowMs) {\n this.tokens = this.config.maxRequests;\n this.lastRefill = now;\n }\n }\n\n /**\n * Schedule processing of queued requests\n */\n private scheduleQueueProcessing(): void {\n if (this.queue.length === 0) return;\n\n const delay = this.getResetTime();\n setTimeout(() => {\n this.refillTokens();\n this.processQueue();\n }, delay);\n }\n\n /**\n * Process queued requests\n */\n private processQueue(): void {\n while (this.queue.length > 0 && this.tokens > 0) {\n const resolve = this.queue.shift();\n if (resolve) {\n this.tokens--;\n resolve();\n }\n }\n\n // Schedule next batch if queue not empty\n if (this.queue.length > 0) {\n this.scheduleQueueProcessing();\n }\n }\n}\n\n/**\n * Create a rate limiter with Route53 defaults\n */\nexport function createRoute53RateLimiter(overrides?: Partial<RateLimiterConfig>): RateLimiter {\n return new RateLimiter({\n maxRequests: 5,\n windowMs: 1000,\n queueRequests: false,\n ...overrides,\n });\n}\n","/**\n * DNS Record Types\n *\n * Standard DNS record types following RFC specifications.\n * These types are shared across all DNS provider implementations.\n */\n\n// ============================================================================\n// Record Type Definitions\n// ============================================================================\n\n/**\n * Supported DNS record types\n */\nexport type DNSRecordType =\n | \"A\"\n | \"AAAA\"\n | \"CNAME\"\n | \"MX\"\n | \"TXT\"\n | \"NS\"\n | \"SRV\"\n | \"CAA\"\n | \"PTR\"\n | \"SOA\";\n\n/**\n * MX record value with priority\n */\nexport interface MXValue {\n priority: number; // 0-65535\n value: string; // Mail server hostname\n}\n\n/**\n * SRV record value\n */\nexport interface SRVValue {\n priority: number; // 0-65535\n weight: number; // 0-65535\n port: number; // 0-65535\n target: string; // Target hostname\n}\n\n/**\n * CAA record value\n */\nexport interface CAAValue {\n flags: number; // 0-255\n tag: \"issue\" | \"issuewild\" | \"iodef\" | string;\n value: string;\n}\n\n/**\n * SOA record value (read-only)\n */\nexport interface SOAValue {\n mname: string; // Primary nameserver\n rname: string; // Admin email (in DNS format)\n serial: number;\n refresh: number;\n retry: number;\n expire: number;\n minimum: number; // Negative cache TTL\n}\n\n/**\n * Record values type - varies by record type\n * - A, AAAA, NS, TXT, PTR: string[] (multiple values)\n * - CNAME: string (single value, for type safety use string[] with one element)\n * - MX: MXValue[]\n * - SRV: SRVValue[]\n * - CAA: CAAValue[]\n * - SOA: SOAValue\n */\nexport type DNSRecordValues = string | string[] | MXValue[] | SRVValue[] | CAAValue[] | SOAValue;\n\n/**\n * A single DNS record\n */\nexport interface DNSRecord {\n type: DNSRecordType;\n ttl: number;\n values: DNSRecordValues;\n}\n\n/**\n * A set of records for a single name (e.g., www.example.com)\n */\nexport interface DNSRecordSet {\n fqdn: string;\n records: DNSRecord[];\n}\n\n// ============================================================================\n// Validation Result\n// ============================================================================\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n}\n\n// ============================================================================\n// Validation Functions\n// ============================================================================\n\nconst IPV4_REGEX = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/;\nconst _IPV6_REGEX =\n /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$|^::$|^::1$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;\nconst _HOSTNAME_REGEX = /^(?!-)[a-zA-Z0-9-]{1,63}(?<!-)(\\.[a-zA-Z0-9-]{1,63})*\\.?$/;\n\n/**\n * Check if string is a valid IPv4 address\n */\nfunction isValidIPv4(ip: string): boolean {\n const match = ip.match(IPV4_REGEX);\n if (!match) return false;\n\n for (let i = 1; i <= 4; i++) {\n const octetStr = match[i];\n if (!octetStr) return false;\n const octet = Number.parseInt(octetStr, 10);\n if (octet < 0 || octet > 255) return false;\n }\n return true;\n}\n\n/**\n * Check if string is a valid IPv6 address\n */\nfunction isValidIPv6(ip: string): boolean {\n // Handle common forms\n if (ip === \"::1\" || ip === \"::\") return true;\n\n // Check for multiple :: (invalid)\n const doubleColonCount = (ip.match(/::/g) || []).length;\n if (doubleColonCount > 1) return false;\n\n // Validate format\n const parts = ip.split(\":\");\n if (parts.length < 3 || parts.length > 8) return false;\n\n for (const part of parts) {\n if (part === \"\") continue; // Empty part from ::\n if (!/^[0-9a-fA-F]{1,4}$/.test(part)) return false;\n }\n\n return true;\n}\n\n/**\n * Check if string is a valid hostname (not IP)\n */\nfunction isValidHostname(hostname: string): boolean {\n if (!hostname) return false;\n\n // Reject if it looks like an IP\n if (isValidIPv4(hostname) || isValidIPv6(hostname)) return false;\n\n // Check hostname format\n const normalized = hostname.endsWith(\".\") ? hostname.slice(0, -1) : hostname;\n const labels = normalized.split(\".\");\n\n for (const label of labels) {\n if (!label) return false;\n if (label.length > 63) return false;\n if (label.startsWith(\"-\") || label.endsWith(\"-\")) return false;\n if (!/^[a-zA-Z0-9-]+$/.test(label)) return false;\n }\n\n return true;\n}\n\n/**\n * Validate A record values\n */\nexport function validateARecord(values: string[]): ValidationResult {\n if (!values || values.length === 0) {\n return { valid: false, error: \"A record requires at least one IPv4 address\" };\n }\n\n for (const value of values) {\n if (!isValidIPv4(value)) {\n return { valid: false, error: `Invalid IPv4 address: ${value}` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Validate AAAA record values\n */\nexport function validateAAAARecord(values: string[]): ValidationResult {\n if (!values || values.length === 0) {\n return { valid: false, error: \"AAAA record requires at least one IPv6 address\" };\n }\n\n for (const value of values) {\n if (!isValidIPv6(value)) {\n return { valid: false, error: `Invalid IPv6 address: ${value}` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Validate CNAME record value\n */\nexport function validateCNAMERecord(value: string): ValidationResult {\n if (!value) {\n return { valid: false, error: \"CNAME record requires a hostname\" };\n }\n\n if (!isValidHostname(value)) {\n return { valid: false, error: `Invalid hostname for CNAME: ${value}` };\n }\n\n return { valid: true };\n}\n\n/**\n * Validate MX record values\n */\nexport function validateMXRecord(values: MXValue[]): ValidationResult {\n if (!values || values.length === 0) {\n return { valid: false, error: \"MX record requires at least one value\" };\n }\n\n for (const mx of values) {\n if (mx.priority < 0 || mx.priority > 65535) {\n return { valid: false, error: `MX priority must be 0-65535, got: ${mx.priority}` };\n }\n if (!isValidHostname(mx.value)) {\n return { valid: false, error: `Invalid hostname for MX: ${mx.value}` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Validate TXT record values\n */\nexport function validateTXTRecord(values: string[]): ValidationResult {\n // TXT records can contain almost anything\n // Empty array would be unusual but technically values can be empty strings\n if (!values) {\n return { valid: false, error: \"TXT record requires values array\" };\n }\n return { valid: true };\n}\n\n/**\n * Validate NS record values\n */\nexport function validateNSRecord(values: string[]): ValidationResult {\n if (!values || values.length === 0) {\n return { valid: false, error: \"NS record requires at least one nameserver\" };\n }\n\n for (const value of values) {\n if (!isValidHostname(value)) {\n return { valid: false, error: `Invalid hostname for NS: ${value}` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Validate SRV record values\n */\nexport function validateSRVRecord(values: SRVValue[]): ValidationResult {\n if (!values || values.length === 0) {\n return { valid: false, error: \"SRV record requires at least one value\" };\n }\n\n for (const srv of values) {\n if (srv.priority < 0 || srv.priority > 65535) {\n return { valid: false, error: `SRV priority must be 0-65535, got: ${srv.priority}` };\n }\n if (srv.weight < 0 || srv.weight > 65535) {\n return { valid: false, error: `SRV weight must be 0-65535, got: ${srv.weight}` };\n }\n if (srv.port < 0 || srv.port > 65535) {\n return { valid: false, error: `SRV port must be 0-65535, got: ${srv.port}` };\n }\n if (!isValidHostname(srv.target) && srv.target !== \".\") {\n return { valid: false, error: `Invalid hostname for SRV target: ${srv.target}` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Validate CAA record values\n */\nexport function validateCAARecord(values: CAAValue[]): ValidationResult {\n if (!values || values.length === 0) {\n return { valid: false, error: \"CAA record requires at least one value\" };\n }\n\n for (const caa of values) {\n if (caa.flags < 0 || caa.flags > 255) {\n return { valid: false, error: `CAA flags must be 0-255, got: ${caa.flags}` };\n }\n if (!caa.tag) {\n return { valid: false, error: \"CAA record requires a tag\" };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Validate TTL value\n */\nexport function validateTTL(ttl: number): ValidationResult {\n if (!Number.isInteger(ttl)) {\n return { valid: false, error: \"TTL must be an integer\" };\n }\n if (ttl < 0) {\n return { valid: false, error: \"TTL cannot be negative\" };\n }\n if (ttl > 2147483647) {\n return { valid: false, error: \"TTL cannot exceed 2147483647\" };\n }\n return { valid: true };\n}\n\n/**\n * Record validation result with optional errors array\n */\nexport interface RecordValidationResult {\n valid: boolean;\n errors?: string[];\n}\n\n/**\n * Validate a complete DNS record\n */\nexport function validateRecord(record: DNSRecord): RecordValidationResult {\n const errors: string[] = [];\n\n // Validate type is present\n if (!record.type) {\n errors.push(\"Record type is required\");\n return { valid: false, errors };\n }\n\n // Validate TTL if present\n if (record.ttl !== undefined) {\n const ttlResult = validateTTL(record.ttl);\n if (!ttlResult.valid) {\n errors.push(ttlResult.error!);\n }\n }\n\n // Validate values based on type\n let valuesResult: ValidationResult;\n switch (record.type) {\n case \"A\":\n valuesResult = validateARecord(record.values as string[]);\n break;\n case \"AAAA\":\n valuesResult = validateAAAARecord(record.values as string[]);\n break;\n case \"CNAME\": {\n const cnameValue = Array.isArray(record.values)\n ? ((record.values[0] as string | undefined) ?? \"\")\n : (record.values as string);\n valuesResult = validateCNAMERecord(cnameValue);\n break;\n }\n case \"MX\":\n valuesResult = validateMXRecord(record.values as MXValue[]);\n break;\n case \"TXT\":\n valuesResult = validateTXTRecord(record.values as string[]);\n break;\n case \"NS\":\n valuesResult = validateNSRecord(record.values as string[]);\n break;\n case \"SRV\":\n valuesResult = validateSRVRecord(record.values as SRVValue[]);\n break;\n case \"CAA\":\n valuesResult = validateCAARecord(record.values as CAAValue[]);\n break;\n case \"PTR\": {\n // PTR is similar to CNAME\n const ptrValue = Array.isArray(record.values)\n ? ((record.values[0] as string | undefined) ?? \"\")\n : (record.values as string);\n valuesResult = validateCNAMERecord(ptrValue);\n break;\n }\n case \"SOA\":\n // SOA is typically read-only, but basic validation\n valuesResult = { valid: true };\n break;\n default:\n valuesResult = { valid: false, error: `Unknown record type: ${record.type}` };\n }\n\n if (!valuesResult.valid) {\n errors.push(valuesResult.error!);\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n };\n}\n","/**\n * DNS Provider\n *\n * AFS module for DNS zone management via various providers.\n */\n\nimport type {\n AFSAccessMode,\n AFSDeleteOptions,\n AFSDeleteResult,\n AFSEntry,\n AFSListOptions,\n AFSListResult,\n AFSModule,\n AFSReadOptions,\n AFSReadResult,\n AFSWriteEntryPayload,\n AFSWriteOptions,\n AFSWriteResult,\n} from \"@aigne/afs\";\nimport {\n checkPermission,\n formatPermissionError,\n type OperationContext,\n} from \"../permissions/checker.js\";\nimport type { PermissionConfig } from \"../permissions/types.js\";\nimport { type AuditLogConfig, AuditLogger } from \"../security/audit-log.js\";\nimport { rejectNullBytes, sanitizeRecordValue } from \"../security/input-sanitizer.js\";\nimport { createRoute53RateLimiter, type RateLimiter } from \"../security/rate-limiter.js\";\nimport type { DNSAdapter } from \"../types/adapter.js\";\nimport type { DNSRecord } from \"../types/record.js\";\nimport { validateRecord } from \"../types/record.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DNSProviderOptions {\n /** Zone domain name (e.g., \"example.com\") */\n zone: string;\n\n /** DNS adapter (Route53, Cloud DNS, etc.) */\n adapter: DNSAdapter;\n\n /** Access mode (readonly or readwrite) */\n accessMode?: AFSAccessMode;\n\n /** Permission configuration for dangerous operations */\n permissions?: PermissionConfig;\n\n /** Audit logging configuration */\n auditLog?: AuditLogConfig;\n\n /** Enable rate limiting (default: true for Route53) */\n rateLimiting?: boolean;\n}\n\n// ============================================================================\n// Provider Implementation\n// ============================================================================\n\nexport class DNSProvider implements AFSModule {\n readonly name: string;\n readonly description: string;\n readonly accessMode: AFSAccessMode;\n\n private zone: string;\n private adapter: DNSAdapter;\n private permissions: PermissionConfig;\n private auditLogger: AuditLogger;\n private rateLimiter: RateLimiter | null;\n\n constructor(options: DNSProviderOptions) {\n this.zone = options.zone;\n this.adapter = options.adapter;\n this.accessMode = options.accessMode ?? \"readonly\";\n this.permissions = options.permissions ?? { preset: \"standard\" };\n this.auditLogger = new AuditLogger(options.auditLog);\n this.rateLimiter = options.rateLimiting !== false ? createRoute53RateLimiter() : null;\n\n this.name = `dns:${this.zone}`;\n this.description = `DNS zone: ${this.zone}`;\n }\n\n // ==========================================================================\n // List Operation\n // ==========================================================================\n\n async list(path: string, options?: AFSListOptions): Promise<AFSListResult> {\n const normalizedPath = this.normalizePath(path);\n const maxDepth = options?.maxDepth ?? 1;\n\n // Root listing\n if (normalizedPath === \"/\") {\n const entries: AFSEntry[] = [];\n\n // Get record names for children count and child entries\n const names = await this.adapter.listRecords(this.zone);\n\n // Zone root entry\n entries.push({\n id: this.zone,\n path: \"/\",\n summary: `DNS zone: ${this.zone}`,\n metadata: {\n kind: \"dns:zone\",\n childrenCount: names.length,\n },\n });\n\n // Include immediate children (maxDepth >= 1 means include direct children)\n if (maxDepth >= 1) {\n for (const name of names) {\n entries.push({\n id: name,\n path: `/${name}`,\n summary: name === \"_zone\" ? \"Zone metadata\" : `DNS record: ${name}`,\n metadata: {\n kind: name === \"_zone\" ? \"dns:zone-meta\" : \"dns:record\",\n childrenCount: 0,\n },\n });\n }\n }\n\n return { data: entries };\n }\n\n // Single record listing\n const recordName = this.extractRecordName(normalizedPath);\n const recordSet = await this.adapter.getRecord(this.zone, recordName);\n\n if (recordSet.records.length === 0 && recordName !== \"_zone\") {\n return { data: [] };\n }\n\n return {\n data: [\n {\n id: recordName,\n path: normalizedPath,\n summary: recordName === \"_zone\" ? \"Zone metadata\" : `DNS record: ${recordName}`,\n metadata: {\n kind: recordName === \"_zone\" ? \"dns:zone-meta\" : \"dns:record\",\n childrenCount: 0,\n },\n },\n ],\n };\n }\n\n // ==========================================================================\n // Read Operation\n // ==========================================================================\n\n async read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult> {\n const normalizedPath = this.normalizePath(path);\n const { recordName, type } = this.parsePathWithQuery(normalizedPath);\n\n // Read zone metadata\n if (recordName === \"_zone\") {\n const metadata = await this.adapter.getZoneMetadata(this.zone);\n return {\n data: {\n id: \"_zone\",\n path: \"/_zone\",\n summary: `Zone metadata for ${this.zone}`,\n metadata: { kind: \"dns:zone-meta\" },\n content: metadata,\n },\n };\n }\n\n // Read record\n const recordSet = await this.adapter.getRecord(this.zone, recordName);\n\n // Filter by type if specified\n let records = recordSet.records;\n if (type) {\n records = records.filter((r) => r.type === type);\n }\n\n // Return undefined if no records found\n if (records.length === 0) {\n return { data: undefined };\n }\n\n return {\n data: {\n id: recordName,\n path: normalizedPath.split(\"?\")[0] ?? normalizedPath,\n summary: `DNS records for ${recordSet.fqdn}`,\n metadata: { kind: \"dns:record\" },\n content: {\n fqdn: recordSet.fqdn,\n records,\n },\n },\n };\n }\n\n // ==========================================================================\n // Write Operation\n // ==========================================================================\n\n async write(\n path: string,\n content: AFSWriteEntryPayload,\n _options?: AFSWriteOptions,\n ): Promise<AFSWriteResult> {\n // Check access mode\n if (this.accessMode !== \"readwrite\") {\n throw new Error(`DNS provider is readonly, cannot write to ${path}`);\n }\n\n const normalizedPath = this.normalizePath(path);\n const recordName = this.extractRecordName(normalizedPath);\n\n // Cannot write to _zone\n if (recordName === \"_zone\") {\n throw new Error(\"Cannot write to _zone metadata\");\n }\n\n // Extract record from content\n const record = content.content as DNSRecord;\n if (!record || !record.type) {\n throw new Error(\"Invalid record content: must include type\");\n }\n\n // Sanitize input values\n this.sanitizeRecordValues(record);\n\n // Validate record\n const validation = validateRecord(record);\n if (!validation.valid) {\n throw new Error(`Invalid record: ${validation.errors?.join(\", \")}`);\n }\n\n // Check permissions\n const context: OperationContext = {\n name: recordName,\n type: record.type,\n operation: \"write\",\n };\n const permResult = checkPermission(context, this.permissions);\n if (!permResult.allowed) {\n // Log permission denial\n this.auditLogger.logPermissionDenied({\n zone: this.zone,\n recordName,\n operation: permResult.deniedOperation ?? \"write\",\n reason: formatPermissionError(permResult),\n });\n throw new Error(formatPermissionError(permResult));\n }\n\n // Apply rate limiting\n await this.acquireRateLimit();\n\n // Determine if this is create or update\n const existing = await this.adapter.getRecord(this.zone, recordName);\n const action = existing.records.length > 0 ? \"update\" : \"create\";\n\n // Write to adapter\n await this.adapter.setRecord(this.zone, recordName, [record]);\n\n // Log the write operation\n this.auditLogger.logWrite({\n zone: this.zone,\n recordName,\n recordType: record.type,\n action,\n values: this.extractValues(record),\n });\n\n return {\n data: {\n id: recordName,\n path: normalizedPath,\n summary: `Updated DNS record: ${recordName}`,\n metadata: { kind: \"dns:record\" },\n content: record,\n },\n };\n }\n\n // ==========================================================================\n // Delete Operation\n // ==========================================================================\n\n async delete(path: string, _options?: AFSDeleteOptions): Promise<AFSDeleteResult> {\n // Check access mode\n if (this.accessMode !== \"readwrite\") {\n throw new Error(`DNS provider is readonly, cannot delete ${path}`);\n }\n\n const normalizedPath = this.normalizePath(path);\n const { recordName, type } = this.parsePathWithQuery(normalizedPath);\n\n // Cannot delete _zone\n if (recordName === \"_zone\") {\n throw new Error(\"Cannot delete _zone metadata\");\n }\n\n // Check permissions for delete\n const context: OperationContext = {\n name: recordName,\n type,\n operation: \"delete\",\n };\n const permResult = checkPermission(context, this.permissions);\n if (!permResult.allowed) {\n // Log permission denial\n this.auditLogger.logPermissionDenied({\n zone: this.zone,\n recordName,\n operation: permResult.deniedOperation ?? \"delete\",\n reason: formatPermissionError(permResult),\n });\n throw new Error(formatPermissionError(permResult));\n }\n\n // Apply rate limiting\n await this.acquireRateLimit();\n\n // Delete from adapter\n await this.adapter.deleteRecord(this.zone, recordName, type);\n\n // Log the delete operation\n this.auditLogger.logDelete({\n zone: this.zone,\n recordName,\n recordType: type,\n });\n\n return {\n message: `Record ${recordName}${type ? ` (${type})` : \"\"} deleted`,\n };\n }\n\n // ==========================================================================\n // Helper Methods\n // ==========================================================================\n\n private normalizePath(path: string): string {\n // Ensure leading slash\n if (!path.startsWith(\"/\")) {\n return `/${path}`;\n }\n return path;\n }\n\n private extractRecordName(path: string): string {\n // Remove leading slash and query params\n const cleanPath = path.replace(/^\\//, \"\").split(\"?\")[0] ?? \"\";\n return cleanPath || \"@\";\n }\n\n private parsePathWithQuery(path: string): { recordName: string; type?: string } {\n const [pathPart, queryPart] = path.split(\"?\");\n const recordName = this.extractRecordName(pathPart ?? path);\n\n let type: string | undefined;\n if (queryPart) {\n const params = new URLSearchParams(queryPart);\n type = params.get(\"type\") ?? undefined;\n }\n\n return { recordName, type };\n }\n\n /**\n * Sanitize record values to prevent injection attacks\n */\n private sanitizeRecordValues(record: DNSRecord): void {\n const { values } = record;\n\n if (typeof values === \"string\") {\n rejectNullBytes(values);\n const result = sanitizeRecordValue(values, record.type);\n if (!result.valid) {\n throw new Error(`Invalid record value: ${result.error}`);\n }\n } else if (Array.isArray(values)) {\n for (const value of values) {\n if (typeof value === \"string\") {\n rejectNullBytes(value);\n const result = sanitizeRecordValue(value, record.type);\n if (!result.valid) {\n throw new Error(`Invalid record value: ${result.error}`);\n }\n }\n }\n }\n }\n\n /**\n * Acquire rate limit token (waits if necessary)\n */\n private async acquireRateLimit(): Promise<void> {\n if (this.rateLimiter) {\n const acquired = await this.rateLimiter.tryAcquire();\n if (!acquired) {\n throw new Error(\"Rate limit exceeded. Please retry later.\");\n }\n }\n }\n\n /**\n * Extract values from a record for audit logging\n */\n private extractValues(record: DNSRecord): unknown[] {\n const { values } = record;\n if (typeof values === \"string\") {\n return [values];\n }\n if (Array.isArray(values)) {\n return values;\n }\n return [values];\n }\n}\n","/**\n * Route53 Response Parser\n *\n * Parses AWS Route53 API responses into standard DNS types.\n */\n\nimport type { CAAValue, DNSRecord, MXValue, SOAValue, SRVValue } from \"../types/record.js\";\nimport type { DNSZone } from \"../types/zone.js\";\n\n// ============================================================================\n// Route53 Types (from AWS SDK responses)\n// ============================================================================\n\nexport interface Route53HostedZone {\n Id: string;\n Name: string;\n CallerReference: string;\n Config: {\n PrivateZone: boolean;\n Comment?: string;\n };\n ResourceRecordSetCount?: number;\n}\n\nexport interface Route53ResourceRecord {\n Value: string;\n}\n\nexport interface Route53AliasTarget {\n HostedZoneId: string;\n DNSName: string;\n EvaluateTargetHealth: boolean;\n}\n\nexport interface Route53ResourceRecordSet {\n Name: string;\n Type: string;\n TTL?: number;\n ResourceRecords?: Route53ResourceRecord[];\n AliasTarget?: Route53AliasTarget;\n SetIdentifier?: string;\n Weight?: number;\n Region?: string;\n GeoLocation?: Record<string, string>;\n Failover?: string;\n HealthCheckId?: string;\n}\n\n// ============================================================================\n// Parsed Record with Alias Support\n// ============================================================================\n\nexport interface ParsedDNSRecord extends DNSRecord {\n alias?: {\n hostedZoneId: string;\n dnsName: string;\n evaluateTargetHealth: boolean;\n };\n}\n\nexport interface ParsedRecordSet {\n name: string;\n records: ParsedDNSRecord[];\n}\n\n// ============================================================================\n// Parser Functions\n// ============================================================================\n\n/**\n * Parse Route53 hosted zones into DNSZone format\n */\nexport function parseHostedZones(zones: Route53HostedZone[]): DNSZone[] {\n return zones.map((zone) => ({\n domain: zone.Name.replace(/\\.$/, \"\"), // Remove trailing dot\n id: zone.Id.replace(\"/hostedzone/\", \"\"), // Extract just the ID\n provider: \"route53\",\n nameservers: [], // Will be populated separately if needed\n recordCount: zone.ResourceRecordSetCount,\n }));\n}\n\n/**\n * Parse Route53 resource record sets into grouped records by name\n */\nexport function parseResourceRecordSets(\n recordSets: Route53ResourceRecordSet[],\n zoneName: string,\n): ParsedRecordSet[] {\n const grouped = new Map<string, ParsedDNSRecord[]>();\n\n for (const rs of recordSets) {\n const name = normalizeRecordName(rs.Name, zoneName);\n const record = parseResourceRecordSet(rs);\n\n if (!grouped.has(name)) {\n grouped.set(name, []);\n }\n grouped.get(name)!.push(record);\n }\n\n return Array.from(grouped.entries()).map(([name, records]) => ({\n name,\n records,\n }));\n}\n\n/**\n * Parse a single Route53 resource record set\n */\nfunction parseResourceRecordSet(rs: Route53ResourceRecordSet): ParsedDNSRecord {\n const type = rs.Type as DNSRecord[\"type\"];\n\n // Handle alias records\n if (rs.AliasTarget) {\n return {\n type,\n ttl: 0, // Alias records don't have TTL\n values: [],\n alias: {\n hostedZoneId: rs.AliasTarget.HostedZoneId,\n dnsName: rs.AliasTarget.DNSName,\n evaluateTargetHealth: rs.AliasTarget.EvaluateTargetHealth,\n },\n };\n }\n\n const ttl = rs.TTL ?? 0;\n const rawValues = rs.ResourceRecords?.map((r) => r.Value) ?? [];\n\n // Parse values based on record type\n const values = parseRecordValues(type, rawValues);\n\n return {\n type,\n ttl,\n values,\n };\n}\n\n/**\n * Parse record values based on type\n */\nfunction parseRecordValues(type: string, rawValues: string[]): DNSRecord[\"values\"] {\n switch (type) {\n case \"A\":\n case \"AAAA\":\n case \"NS\":\n case \"TXT\":\n case \"PTR\":\n return rawValues;\n\n case \"CNAME\":\n // CNAME has single value\n return rawValues[0] ?? \"\";\n\n case \"MX\":\n return rawValues.map(parseMXValue);\n\n case \"SRV\":\n return rawValues.map(parseSRVValue);\n\n case \"CAA\":\n return rawValues.map(parseCAAValue);\n\n case \"SOA\":\n return parseSOAValue(rawValues[0] ?? \"\");\n\n default:\n return rawValues;\n }\n}\n\n/**\n * Parse MX record value: \"10 mail.example.com\"\n */\nfunction parseMXValue(value: string): MXValue {\n const parts = value.split(/\\s+/, 2);\n return {\n priority: Number.parseInt(parts[0] ?? \"0\", 10),\n value: parts[1] ?? \"\",\n };\n}\n\n/**\n * Parse SRV record value: \"10 5 5060 sip.example.com\"\n */\nfunction parseSRVValue(value: string): SRVValue {\n const parts = value.split(/\\s+/, 4);\n return {\n priority: Number.parseInt(parts[0] ?? \"0\", 10),\n weight: Number.parseInt(parts[1] ?? \"0\", 10),\n port: Number.parseInt(parts[2] ?? \"0\", 10),\n target: parts[3] ?? \"\",\n };\n}\n\n/**\n * Parse CAA record value: '0 issue \"letsencrypt.org\"'\n */\nfunction parseCAAValue(value: string): CAAValue {\n // Format: flags tag \"value\"\n const match = value.match(/^(\\d+)\\s+(\\w+)\\s+\"?([^\"]*)\"?$/);\n if (!match) {\n return { flags: 0, tag: \"\", value: \"\" };\n }\n return {\n flags: Number.parseInt(match[1] ?? \"0\", 10),\n tag: match[2] ?? \"\",\n value: match[3] ?? \"\",\n };\n}\n\n/**\n * Parse SOA record value\n */\nfunction parseSOAValue(value: string): SOAValue {\n // Format: mname rname serial refresh retry expire minimum\n const parts = value.split(/\\s+/);\n return {\n mname: parts[0] ?? \"\",\n rname: parts[1] ?? \"\",\n serial: Number.parseInt(parts[2] ?? \"0\", 10) || 0,\n refresh: Number.parseInt(parts[3] ?? \"0\", 10) || 0,\n retry: Number.parseInt(parts[4] ?? \"0\", 10) || 0,\n expire: Number.parseInt(parts[5] ?? \"0\", 10) || 0,\n minimum: Number.parseInt(parts[6] ?? \"0\", 10) || 0,\n };\n}\n\n/**\n * Normalize a Route53 record name to our format\n *\n * - Removes trailing dot\n * - Converts apex to @\n * - Extracts subdomain part\n */\nexport function normalizeRecordName(fqdn: string, zoneName: string): string {\n // Remove trailing dot\n const name = fqdn.endsWith(\".\") ? fqdn.slice(0, -1) : fqdn;\n\n // Normalize zone name for comparison\n const normalizedZone = zoneName.endsWith(\".\") ? zoneName.slice(0, -1) : zoneName;\n\n // Check if it's the apex\n if (name.toLowerCase() === normalizedZone.toLowerCase()) {\n return \"@\";\n }\n\n // Extract subdomain part\n const suffix = `.${normalizedZone}`;\n if (name.toLowerCase().endsWith(suffix.toLowerCase())) {\n return name.slice(0, -suffix.length);\n }\n\n return name;\n}\n","/**\n * Route53 DNS Adapter\n *\n * AWS Route53 implementation of the DNS provider interface.\n */\n\nimport {\n type Change,\n ChangeResourceRecordSetsCommand,\n GetHostedZoneCommand,\n type HostedZone,\n ListHostedZonesCommand,\n ListResourceRecordSetsCommand,\n type ResourceRecordSet,\n Route53Client,\n} from \"@aws-sdk/client-route-53\";\nimport type { DNSRecord, DNSRecordSet } from \"../types/record.js\";\nimport type { DNSZone } from \"../types/zone.js\";\nimport {\n parseHostedZones,\n parseResourceRecordSets,\n type Route53HostedZone,\n type Route53ResourceRecordSet,\n} from \"./parser.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface Route53AdapterOptions {\n /** AWS Region */\n region?: string;\n /** Custom endpoint (for LocalStack) */\n endpoint?: string;\n /** AWS Credentials */\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n };\n /** Pre-configured client (for testing) */\n client?: Route53Client;\n}\n\n// ============================================================================\n// Adapter Implementation\n// ============================================================================\n\nexport class Route53Adapter {\n private client: Route53Client;\n private zoneIdCache = new Map<string, string>();\n\n constructor(options: Route53AdapterOptions = {}) {\n if (options.client) {\n this.client = options.client;\n } else {\n this.client = new Route53Client({\n region: options.region ?? \"us-east-1\",\n endpoint: options.endpoint,\n credentials: options.credentials,\n });\n }\n }\n\n // ==========================================================================\n // Read Operations\n // ==========================================================================\n\n /**\n * List all hosted zones\n */\n async listZones(): Promise<DNSZone[]> {\n const zones: HostedZone[] = [];\n let marker: string | undefined;\n\n do {\n const response = await this.client.send(new ListHostedZonesCommand({ Marker: marker }));\n\n if (response.HostedZones) {\n zones.push(...response.HostedZones);\n }\n\n marker = response.IsTruncated ? response.NextMarker : undefined;\n } while (marker);\n\n return parseHostedZones(zones as Route53HostedZone[]);\n }\n\n /**\n * List all record names in a zone\n */\n async listRecords(zoneDomain: string): Promise<string[]> {\n const zoneId = await this.getZoneId(zoneDomain);\n const recordSets = await this.fetchAllRecordSets(zoneId);\n\n const parsed = parseResourceRecordSets(recordSets as Route53ResourceRecordSet[], zoneDomain);\n\n // Get unique names and add _zone\n const names = new Set<string>();\n for (const rs of parsed) {\n names.add(rs.name);\n }\n names.add(\"_zone\");\n\n return Array.from(names);\n }\n\n /**\n * Get all records for a specific name\n */\n async getRecord(zoneDomain: string, name: string): Promise<DNSRecordSet> {\n const zoneId = await this.getZoneId(zoneDomain);\n const recordSets = await this.fetchAllRecordSets(zoneId);\n\n const parsed = parseResourceRecordSets(recordSets as Route53ResourceRecordSet[], zoneDomain);\n\n // Find matching name\n const match = parsed.find((rs) => rs.name === name);\n\n // Build FQDN\n const fqdn = name === \"@\" ? zoneDomain : `${name}.${zoneDomain}`;\n\n return {\n fqdn,\n records: match?.records ?? [],\n };\n }\n\n /**\n * Get zone metadata\n */\n async getZoneMetadata(zoneDomain: string): Promise<DNSZone> {\n const zoneId = await this.getZoneId(zoneDomain);\n\n const response = await this.client.send(new GetHostedZoneCommand({ Id: zoneId }));\n\n const zone = response.HostedZone!;\n const nameservers = response.DelegationSet?.NameServers ?? [];\n\n return {\n domain: zone.Name!.replace(/\\.$/, \"\"),\n id: zone.Id!.replace(\"/hostedzone/\", \"\"),\n provider: \"route53\",\n nameservers,\n recordCount: zone.ResourceRecordSetCount,\n };\n }\n\n // ==========================================================================\n // Write Operations (Phase 2)\n // ==========================================================================\n\n /**\n * Set records for a name (create or update)\n */\n async setRecord(\n zoneDomain: string,\n name: string,\n records: DNSRecord[],\n _options?: { force?: boolean },\n ): Promise<void> {\n const zoneId = await this.getZoneId(zoneDomain);\n const fqdn = this.buildFQDN(name, zoneDomain);\n\n const changes: Change[] = [];\n\n for (const record of records) {\n changes.push({\n Action: \"UPSERT\",\n ResourceRecordSet: this.buildResourceRecordSet(fqdn, record),\n });\n }\n\n await this.client.send(\n new ChangeResourceRecordSetsCommand({\n HostedZoneId: zoneId,\n ChangeBatch: {\n Changes: changes,\n Comment: `AFS DNS Provider: set ${name}`,\n },\n }),\n );\n }\n\n /**\n * Delete records for a name\n */\n async deleteRecord(\n zoneDomain: string,\n name: string,\n type?: string,\n _options?: { force?: boolean },\n ): Promise<void> {\n const zoneId = await this.getZoneId(zoneDomain);\n const fqdn = this.buildFQDN(name, zoneDomain);\n\n // Fetch current records to get exact values (required for DELETE)\n const recordSets = await this.fetchAllRecordSets(zoneId);\n const toDelete = recordSets.filter((rs) => {\n const rsName = rs.Name?.replace(/\\.$/, \"\");\n const targetName = fqdn.replace(/\\.$/, \"\");\n if (rsName !== targetName) return false;\n if (type && rs.Type !== type) return false;\n return true;\n });\n\n if (toDelete.length === 0) {\n return; // Nothing to delete\n }\n\n const changes: Change[] = toDelete.map((rs) => ({\n Action: \"DELETE\" as const,\n ResourceRecordSet: rs,\n }));\n\n await this.client.send(\n new ChangeResourceRecordSetsCommand({\n HostedZoneId: zoneId,\n ChangeBatch: {\n Changes: changes,\n Comment: `AFS DNS Provider: delete ${name}`,\n },\n }),\n );\n }\n\n // ==========================================================================\n // Helper Methods\n // ==========================================================================\n\n /**\n * Get zone ID from domain name\n */\n private async getZoneId(zoneDomain: string): Promise<string> {\n // Check cache\n if (this.zoneIdCache.has(zoneDomain)) {\n return this.zoneIdCache.get(zoneDomain)!;\n }\n\n // Find zone\n const zones = await this.listZones();\n const zone = zones.find((z) => z.domain.toLowerCase() === zoneDomain.toLowerCase());\n\n if (!zone) {\n const error = new Error(`Zone not found: ${zoneDomain}`);\n error.name = \"NoSuchHostedZone\";\n throw error;\n }\n\n this.zoneIdCache.set(zoneDomain, zone.id);\n return zone.id;\n }\n\n /**\n * Fetch all record sets for a zone (handles pagination)\n */\n private async fetchAllRecordSets(zoneId: string): Promise<ResourceRecordSet[]> {\n const recordSets: ResourceRecordSet[] = [];\n let startRecordName: string | undefined;\n let startRecordType: string | undefined;\n\n do {\n const response = await this.client.send(\n new ListResourceRecordSetsCommand({\n HostedZoneId: zoneId,\n StartRecordName: startRecordName,\n StartRecordType: startRecordType as any,\n }),\n );\n\n if (response.ResourceRecordSets) {\n recordSets.push(...response.ResourceRecordSets);\n }\n\n if (response.IsTruncated) {\n startRecordName = response.NextRecordName;\n startRecordType = response.NextRecordType;\n } else {\n startRecordName = undefined;\n }\n } while (startRecordName);\n\n return recordSets;\n }\n\n /**\n * Build FQDN from name and zone\n */\n private buildFQDN(name: string, zoneDomain: string): string {\n if (name === \"@\") {\n return `${zoneDomain}.`;\n }\n return `${name}.${zoneDomain}.`;\n }\n\n /**\n * Build Route53 ResourceRecordSet from DNSRecord\n */\n private buildResourceRecordSet(fqdn: string, record: DNSRecord): ResourceRecordSet {\n const base: ResourceRecordSet = {\n Name: fqdn,\n Type: record.type as any,\n TTL: record.ttl,\n };\n\n // Convert values to Route53 format\n const resourceRecords = this.buildResourceRecords(record);\n if (resourceRecords.length > 0) {\n base.ResourceRecords = resourceRecords;\n }\n\n return base;\n }\n\n /**\n * Build Route53 ResourceRecords from DNSRecord values\n */\n private buildResourceRecords(record: DNSRecord): Array<{ Value: string }> {\n const { type, values } = record;\n\n switch (type) {\n case \"A\":\n case \"AAAA\":\n case \"NS\":\n case \"TXT\":\n case \"PTR\":\n return (values as string[]).map((v) => ({ Value: v }));\n\n case \"CNAME\":\n return [{ Value: typeof values === \"string\" ? values : ((values as string[])[0] ?? \"\") }];\n\n case \"MX\":\n return (values as Array<{ priority: number; value: string }>).map((mx) => ({\n Value: `${mx.priority} ${mx.value}`,\n }));\n\n case \"SRV\":\n return (\n values as Array<{ priority: number; weight: number; port: number; target: string }>\n ).map((srv) => ({ Value: `${srv.priority} ${srv.weight} ${srv.port} ${srv.target}` }));\n\n case \"CAA\":\n return (values as Array<{ flags: number; tag: string; value: string }>).map((caa) => ({\n Value: `${caa.flags} ${caa.tag} \"${caa.value}\"`,\n }));\n\n default:\n return [];\n }\n }\n}\n","/**\n * DNS Name Validation\n *\n * Validates domain names and record names per RFC 1035.\n */\n\nimport type { ValidationResult } from \"../types/record.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst MAX_LABEL_LENGTH = 63;\nconst MAX_DOMAIN_LENGTH = 253;\n\n// Valid label characters: a-z, 0-9, hyphen (case insensitive)\n// Labels cannot start or end with hyphen\nconst LABEL_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/;\n\n// Service label can start with underscore (_dmarc, _acme-challenge, etc.)\nconst SERVICE_LABEL_REGEX = /^_[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^_[a-zA-Z0-9]$/;\n\n// ============================================================================\n// Domain Name Validation\n// ============================================================================\n\n/**\n * Validate a domain name per RFC 1035\n *\n * @param domain - Domain name to validate (e.g., \"example.com\")\n * @returns Validation result\n */\nexport function validateDomainName(domain: string): ValidationResult {\n if (!domain) {\n return { valid: false, error: \"Domain name cannot be empty\" };\n }\n\n // Remove trailing dot if present (FQDN format)\n const normalized = domain.endsWith(\".\") ? domain.slice(0, -1) : domain;\n\n // Check total length\n if (normalized.length > MAX_DOMAIN_LENGTH) {\n return { valid: false, error: `Domain name exceeds ${MAX_DOMAIN_LENGTH} characters` };\n }\n\n // Split into labels\n const labels = normalized.split(\".\");\n\n // Check for empty labels (consecutive dots or leading dot)\n for (let i = 0; i < labels.length; i++) {\n const label = labels[i];\n\n if (!label) {\n return { valid: false, error: \"Domain name contains empty label\" };\n }\n\n // Check label length\n if (label.length > MAX_LABEL_LENGTH) {\n return { valid: false, error: `Label \"${label}\" exceeds ${MAX_LABEL_LENGTH} characters` };\n }\n\n // Check for leading/trailing hyphen\n if (label.startsWith(\"-\")) {\n return { valid: false, error: `Label \"${label}\" cannot start with hyphen` };\n }\n if (label.endsWith(\"-\")) {\n return { valid: false, error: `Label \"${label}\" cannot end with hyphen` };\n }\n\n // Check valid characters (allow xn-- for punycode)\n if (!LABEL_REGEX.test(label) && !label.startsWith(\"xn--\")) {\n return { valid: false, error: `Label \"${label}\" contains invalid characters` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Normalize a domain name to lowercase\n *\n * DNS is case-insensitive per RFC 1035.\n *\n * @param domain - Domain name to normalize\n * @returns Normalized lowercase domain name\n */\nexport function normalizeDomainName(domain: string): string {\n // Remove trailing dot and convert to lowercase\n const normalized = domain.endsWith(\".\") ? domain.slice(0, -1) : domain;\n return normalized.toLowerCase();\n}\n\n// ============================================================================\n// Record Name Validation\n// ============================================================================\n\n/**\n * Validate a record name within a zone\n *\n * Record names can be:\n * - @ for apex (root domain)\n * - * for wildcard\n * - *.subdomain for scoped wildcard\n * - Simple names like \"www\", \"api\"\n * - Multi-level like \"blog.dev\"\n * - Service records like \"_dmarc\", \"_acme-challenge\"\n *\n * @param name - Record name to validate\n * @param zone - The zone this record belongs to\n * @returns Validation result\n */\nexport function validateRecordName(name: string, zone: string): ValidationResult {\n if (!name) {\n return { valid: false, error: \"Record name cannot be empty\" };\n }\n\n // @ is valid for apex\n if (name === \"@\") {\n return { valid: true };\n }\n\n // * is valid for wildcard at apex\n if (name === \"*\") {\n return { valid: true };\n }\n\n // Check for wildcard in invalid position\n if (name.includes(\"*\")) {\n // Wildcard must be at the start\n if (!name.startsWith(\"*.\") && name !== \"*\") {\n return { valid: false, error: \"Wildcard (*) can only appear at the start of a record name\" };\n }\n }\n\n // Remove wildcard prefix for further validation\n const nameWithoutWildcard = name.startsWith(\"*.\") ? name.slice(2) : name;\n\n // Check total FQDN length\n const fqdn = `${nameWithoutWildcard}.${zone}`;\n if (fqdn.length > MAX_DOMAIN_LENGTH) {\n return { valid: false, error: `Full domain name would exceed ${MAX_DOMAIN_LENGTH} characters` };\n }\n\n // Split into labels and validate each\n const labels = nameWithoutWildcard.split(\".\");\n\n for (const label of labels) {\n if (!label) {\n return { valid: false, error: \"Record name contains empty label\" };\n }\n\n if (label.length > MAX_LABEL_LENGTH) {\n return { valid: false, error: `Label \"${label}\" exceeds ${MAX_LABEL_LENGTH} characters` };\n }\n\n // Allow service labels starting with underscore\n const isServiceLabel = label.startsWith(\"_\");\n\n if (isServiceLabel) {\n if (!SERVICE_LABEL_REGEX.test(label)) {\n return { valid: false, error: `Invalid service label: ${label}` };\n }\n } else {\n // Regular label validation\n if (label.startsWith(\"-\") || label.endsWith(\"-\")) {\n return { valid: false, error: `Label \"${label}\" cannot start or end with hyphen` };\n }\n\n if (!LABEL_REGEX.test(label)) {\n return { valid: false, error: `Label \"${label}\" contains invalid characters` };\n }\n }\n }\n\n return { valid: true };\n}\n","/**\n * Write Validator\n *\n * Validates DNS record write operations for RFC compliance and safety.\n */\n\nimport type { CAAValue, DNSRecord, MXValue, SRVValue } from \"../types/record.js\";\nimport { validateRecord } from \"../types/record.js\";\n\nexport interface WriteValidationResult {\n valid: boolean;\n error?: string;\n}\n\n/**\n * Validate a complete write operation\n */\nexport function validateWriteOperation(\n recordName: string,\n records: DNSRecord[],\n): WriteValidationResult {\n // Check record set is not empty\n const setResult = validateRecordSet(records);\n if (!setResult.valid) return setResult;\n\n // Check SOA protection\n const soaResult = checkSOAProtection(records);\n if (!soaResult.valid) return soaResult;\n\n // Check CNAME at apex\n const apexResult = checkCNAMEAtApex(recordName, records);\n if (!apexResult.valid) return apexResult;\n\n // Check CNAME conflicts\n const conflictResult = checkCNAMEConflict(records);\n if (!conflictResult.valid) return conflictResult;\n\n // Check duplicate values\n const dupeResult = checkDuplicateValues(records);\n if (!dupeResult.valid) return dupeResult;\n\n return { valid: true };\n}\n\n/**\n * Validate a record set (array of records)\n */\nexport function validateRecordSet(records: DNSRecord[]): WriteValidationResult {\n if (!records || records.length === 0) {\n return { valid: false, error: \"Record set must contain at least one record\" };\n }\n\n for (const record of records) {\n const result = validateRecord(record);\n if (!result.valid) {\n return { valid: false, error: result.errors?.join(\", \") };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Check for CNAME conflicts - CNAME cannot coexist with other record types\n */\nexport function checkCNAMEConflict(records: DNSRecord[]): WriteValidationResult {\n const hasCNAME = records.some((r) => r.type === \"CNAME\");\n const hasOtherTypes = records.some((r) => r.type !== \"CNAME\");\n\n if (hasCNAME && hasOtherTypes) {\n return {\n valid: false,\n error: \"CNAME cannot coexist with other record types at the same name\",\n };\n }\n\n // Also check for multiple CNAME records (only one allowed)\n const cnameCount = records.filter((r) => r.type === \"CNAME\").length;\n if (cnameCount > 1) {\n return {\n valid: false,\n error: \"Only one CNAME record is allowed per name\",\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Check CNAME at apex - CNAME is not allowed at the root domain\n */\nexport function checkCNAMEAtApex(recordName: string, records: DNSRecord[]): WriteValidationResult {\n const hasCNAME = records.some((r) => r.type === \"CNAME\");\n\n if (hasCNAME && recordName === \"@\") {\n return {\n valid: false,\n error: \"CNAME records are not allowed at the apex (root domain)\",\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Check SOA protection - SOA records cannot be written\n */\nexport function checkSOAProtection(records: DNSRecord[]): WriteValidationResult {\n const hasSOA = records.some((r) => r.type === \"SOA\");\n\n if (hasSOA) {\n return {\n valid: false,\n error: \"SOA records are managed by the DNS provider and cannot be modified\",\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Check for duplicate values within records\n */\nexport function checkDuplicateValues(records: DNSRecord[]): WriteValidationResult {\n for (const record of records) {\n const result = checkRecordForDuplicates(record);\n if (!result.valid) return result;\n }\n\n return { valid: true };\n}\n\n/**\n * Check a single record for duplicate values\n */\nfunction checkRecordForDuplicates(record: DNSRecord): WriteValidationResult {\n const { type, values } = record;\n\n // Skip types that have single values\n if (type === \"CNAME\" || type === \"SOA\") {\n return { valid: true };\n }\n\n // For array-based values, check for duplicates\n if (Array.isArray(values)) {\n const seen = new Set<string>();\n\n for (const value of values) {\n const key = serializeValue(value, type);\n if (seen.has(key)) {\n return {\n valid: false,\n error: `Duplicate value found in ${type} record: ${key}`,\n };\n }\n seen.add(key);\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Serialize a record value to a string for comparison\n */\nfunction serializeValue(value: unknown, type: string): string {\n if (typeof value === \"string\") {\n return value;\n }\n\n if (type === \"MX\") {\n const mx = value as MXValue;\n return `${mx.priority}:${mx.value}`;\n }\n\n if (type === \"SRV\") {\n const srv = value as SRVValue;\n return `${srv.priority}:${srv.weight}:${srv.port}:${srv.target}`;\n }\n\n if (type === \"CAA\") {\n const caa = value as CAAValue;\n return `${caa.flags}:${caa.tag}:${caa.value}`;\n }\n\n return JSON.stringify(value);\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@aigne/afs-dns",
3
+ "version": "1.11.0-beta.6",
4
+ "description": "AFS DNS Provider - Unified DNS management with Route53 and Cloud DNS support",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.mts",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "dependencies": {
25
+ "@aws-sdk/client-route-53": "^3.705.0",
26
+ "@aigne/afs": "1.11.0-beta.6"
27
+ },
28
+ "devDependencies": {
29
+ "@types/bun": "latest",
30
+ "tsdown": "^0.20.0-beta.3",
31
+ "typescript": "^5.9.2"
32
+ },
33
+ "peerDependencies": {
34
+ "@google-cloud/dns": "^4.0.0",
35
+ "@aigne/afs": "1.11.0-beta.6"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@google-cloud/dns": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "scripts": {
43
+ "build": "tsdown",
44
+ "dev": "tsdown --watch",
45
+ "test": "bun test",
46
+ "test:e2e": "bun test test/e2e",
47
+ "test:e2e:setup": "bun run test/e2e/setup-localstack.ts",
48
+ "test:e2e:run": "LOCALSTACK=1 LOCALSTACK_ENDPOINT=http://localhost:4567 bun test test/e2e",
49
+ "test:e2e:full": "pnpm run test:e2e:setup && pnpm run test:e2e:run",
50
+ "check-types": "tsc --noEmit"
51
+ }
52
+ }