UNPKG

cloudflare-dyndns-updater

Version:

A TypeScript Express application that dynamically updates DNS records on Cloudflare with the current public IP address

214 lines 8.11 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateRdpId = exports.apiKeyMiddleware = exports.app = void 0; const express_1 = __importDefault(require("express")); const axios_1 = __importDefault(require("axios")); const dotenv_1 = __importDefault(require("dotenv")); const crypto_1 = __importDefault(require("crypto")); dotenv_1.default.config(); const app = (0, express_1.default)(); exports.app = app; const port = process.env.PORT || 5555; app.set('json spaces', 2); const dnsRecords = process.env.CF_DNS ? process.env.CF_DNS.split(",") : []; /** * Generate a unique RDP identifier * Format is 16 hex characters followed by "RDP" * @returns {string} Generated RDP ID */ const generateRdpId = () => { // Generate 8 random bytes (16 hex characters) const randomBytes = crypto_1.default.randomBytes(8).toString('hex').toUpperCase(); // Add RDP prefix return `${randomBytes}-RDP`; }; exports.generateRdpId = generateRdpId; // Middleware to check the API key const apiKeyMiddleware = (req, res, next) => { const apiKey = req.header("rdp-key") || req.query.api_key; if (!apiKey) { // No API key provided return res.status(400).json({ status: "failed", request_id: req.rdpId, message: "Missing api_key or rdp-key Header" }); } else if (apiKey !== process.env.RDP_API_KEY) { // API key is provided but is incorrect return res.status(401).json({ status: "failed", request_id: req.rdpId, message: "Invalid API Key" }); } else { // API key is correct, proceed to the route next(); } }; exports.apiKeyMiddleware = apiKeyMiddleware; // Add RDP ID middleware to all routes app.use((req, res, next) => { // Generate and attach an RDP ID to the request const rdpId = generateRdpId(); req.rdpId = rdpId; // Add RDP ID to response headers res.setHeader('RDP-Request-ID', rdpId); next(); }); // Root route is now the informational page (no auth required) app.get("/", (req, res) => { res.json({ name: "RDP Dynamic DNS Updater", version: "1.0.0", request_id: req.rdpId, endpoints: [ { path: "/update", method: "GET", description: "Update DNS records with current IP (requires API key)" }, { path: "/health", method: "GET", description: "Service health check (no auth required)" }, { path: "/update-all", method: "GET", description: "Update all DNS records (requires API key)" } ], status: "operational" }); }); // Health check endpoint (no auth required) app.get("/health", (req, res) => { res.status(200).json({ status: "healthy", request_id: req.rdpId, timestamp: new Date().toISOString() }); }); // Main DNS update endpoint now moved to /update app.get("/update", apiKeyMiddleware, async (req, res) => { try { // Check if required environment variables are set if (!process.env.CF_ZONE || !process.env.CF_MAIL || !process.env.CF_AUTH) { return res.status(500).json({ status: "failed", request_id: req.rdpId, message: "Missing Cloudflare configuration. Please check your environment variables." }); } if (dnsRecords.length === 0) { return res.status(500).json({ status: "failed", request_id: req.rdpId, message: "No DNS records specified. Please check your CF_DNS environment variable." }); } // Get current IP address const response = await axios_1.default.get("https://api.ipify.org?format=json"); const ip = response.data.ip; console.log(`Current IP address: ${ip}`); const updateResults = await Promise.all(dnsRecords.map(async (dnsRecord) => { try { // Fetch current record data const currentRecord = await axios_1.default.get(`https://api.cloudflare.com/client/v4/zones/${process.env.CF_ZONE}/dns_records/${dnsRecord}`, { headers: { "X-Auth-Email": process.env.CF_MAIL, "Authorization": `Bearer ${process.env.CF_AUTH}`, "Content-Type": "application/json" }, }); if (!currentRecord.data.result) { throw new Error(`Failed to retrieve DNS record ${dnsRecord}`); } const recordData = currentRecord.data.result; console.log(`Updating DNS record: ${recordData.name} (${dnsRecord})`); // Update the record with new IP const update = await axios_1.default.put(`https://api.cloudflare.com/client/v4/zones/${process.env.CF_ZONE}/dns_records/${dnsRecord}`, { type: recordData.type, name: recordData.name, content: ip, ttl: recordData.ttl, proxied: recordData.proxied, }, { headers: { "X-Auth-Email": process.env.CF_MAIL, "Authorization": `Bearer ${process.env.CF_AUTH}`, "Content-Type": "application/json" }, }); return { id: dnsRecord, name: recordData.name, success: update.data.success, status: update.status }; } catch (error) { console.error(`Error updating record ${dnsRecord}:`, error); return { id: dnsRecord, success: false, error: error instanceof Error ? error.message : "Unknown error" }; } })); const success = updateResults.every((result) => result.success === true); res.json({ status: success ? "success" : "partial_failure", ip, request_id: req.rdpId, records: updateResults }); } catch (error) { console.error("Error in update process:", error); res.status(500).json({ status: "failed", request_id: req.rdpId, error: error instanceof Error ? error.message : "An unknown error occurred" }); } }); // Add protected routes that require authentication app.get("/update-all", apiKeyMiddleware, async (req, res) => { try { // Logic to update all DNS records res.json({ status: "success", request_id: req.rdpId, message: "All DNS records updated successfully" }); } catch (error) { console.error("Error:", error); res.status(500).json({ status: "failed", request_id: req.rdpId, error: error instanceof Error ? error.message : "Unknown error" }); } }); // Error handling middleware app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.status(500).json({ status: "error", request_id: req.rdpId, message: "Internal server error", error: process.env.NODE_ENV === 'production' ? undefined : err.message }); }); // 404 handler - must be last app.use((req, res) => { res.status(404).json({ status: "failed", request_id: req.rdpId, message: "Endpoint not found" }); }); // Start the server only if directly executed, not if imported as a module if (require.main === module) { app.listen(port, () => { console.log(`Server running on port ${port}`); console.log(`Configured to update ${dnsRecords.length} DNS records`); }); } exports.default = app; //# sourceMappingURL=index.js.map