码桶

发现社区成员的开源项目

dns-settings.js7.1 KB
import { logAudit } from '../../_audit.js';
import { fireWebhook } from '../../_webhook.js';

async function cfGet(cfHeaders, zoneId, setting) {
    const res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/settings/${setting}`, {
        headers: { ...cfHeaders, 'Content-Type': 'application/json' }
    });
    return res.json();
}

async function cfPatch(cfHeaders, zoneId, setting, value) {
    const res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/settings/${setting}`, {
        method: 'PATCH',
        headers: { ...cfHeaders, 'Content-Type': 'application/json' },
        body: JSON.stringify({ value })
    });
    return res.json();
}

export async function onRequestGet(context) {
    const { cfHeaders } = context.data;
    const { zoneId } = context.params;
    try {
        const [cnameRes, dnssecRes] = await Promise.all([
            cfGet(cfHeaders, zoneId, 'cname_flattening'),
            fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
                headers: { ...cfHeaders, 'Content-Type': 'application/json' }
            }).then(r => r.json()).catch(() => ({ success: false, result: {} }))
        ]);

        const settings = {
            cname_flattening: cnameRes.success && cnameRes.result ? cnameRes.result.value : null,
        };

        return new Response(JSON.stringify({
            success: true,
            settings,
            dnssec: dnssecRes.success ? dnssecRes.result : null,
            errors: []
        }), { headers: { 'Content-Type': 'application/json' } });
    } catch (e) {
        return new Response(JSON.stringify({ success: false, errors: [{ message: e.message || 'Failed to fetch DNS settings' }] }), { status: 500, headers: { 'Content-Type': 'application/json' } });
    }
}

export async function onRequestPost(context) {
    const { cfHeaders } = context.data;
    const { zoneId } = context.params;
    const body = await context.request.json();
    const username = context.data.user?.username || 'client';
    const kv = context.env.CF_DNS_KV;
    const { action } = body;

    if (action === 'update') {
        const { setting, value } = body;
        if (setting !== 'cname_flattening') {
            return new Response(JSON.stringify({ success: false, errors: [{ message: `Invalid setting: "${setting}"` }] }), { status: 400, headers: { 'Content-Type': 'application/json' } });
        }
        try {
            const data = await cfPatch(cfHeaders, zoneId, setting, value);
            if (data.success) {
                await logAudit(kv, username, `dns.${setting}`, `Set ${setting} to ${value} (zone: ${zoneId})`);
                await fireWebhook(kv, { type: `dns.${setting}`, username, detail: `Set ${setting} to ${value} (zone: ${zoneId})` });
            }
            return new Response(JSON.stringify({ success: data.success, result: data.result?.value ?? null, errors: data.errors || [] }), { status: data.success ? 200 : 400, headers: { 'Content-Type': 'application/json' } });
        } catch (e) {
            return new Response(JSON.stringify({ success: false, errors: [{ message: e.message }] }), { status: 500, headers: { 'Content-Type': 'application/json' } });
        }
    }

    if (action === 'enable_dnssec') {
        try {
            // Try PATCH first (works if DNSSEC was previously created/disabled)
            let res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
                method: 'PATCH',
                headers: { ...cfHeaders, 'Content-Type': 'application/json' },
                body: JSON.stringify({ status: 'active' })
            });
            let data = await res.json();
            // If PATCH fails (e.g. DNSSEC never created), fall back to POST
            if (!data.success) {
                res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
                    method: 'POST',
                    headers: { ...cfHeaders, 'Content-Type': 'application/json' },
                    body: JSON.stringify({})
                });
                data = await res.json();
            }
            if (data.success) {
                await logAudit(kv, username, 'dns.dnssec_enable', `Enabled DNSSEC (zone: ${zoneId})`);
                await fireWebhook(kv, { type: 'dns.dnssec_enable', username, detail: `Enabled DNSSEC (zone: ${zoneId})` });
            }
            // Re-fetch current DNSSEC state so badge always reflects truth
            const freshRes = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
                headers: { ...cfHeaders, 'Content-Type': 'application/json' }
            });
            const freshData = await freshRes.json();
            const dnssecResult = freshData.success ? freshData.result : (data.result || {});
            return new Response(JSON.stringify({ success: data.success, dnssec: dnssecResult, errors: data.errors || [] }), { status: data.success ? 200 : 400, headers: { 'Content-Type': 'application/json' } });
        } catch (e) {
            return new Response(JSON.stringify({ success: false, errors: [{ message: e.message }] }), { status: 500, headers: { 'Content-Type': 'application/json' } });
        }
    }

    if (action === 'disable_dnssec') {
        try {
            // Use PATCH to disable (more reliable than DELETE)
            let res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
                method: 'PATCH',
                headers: { ...cfHeaders, 'Content-Type': 'application/json' },
                body: JSON.stringify({ status: 'disabled' })
            });
            let data = await res.json();
            // Fall back to DELETE if PATCH doesn't work
            if (!data.success) {
                res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
                    method: 'DELETE',
                    headers: { ...cfHeaders, 'Content-Type': 'application/json' }
                });
                data = await res.json();
            }
            if (data.success) {
                await logAudit(kv, username, 'dns.dnssec_disable', `Disabled DNSSEC (zone: ${zoneId})`);
            }
            // Re-fetch to get accurate state
            const freshRes = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
                headers: { ...cfHeaders, 'Content-Type': 'application/json' }
            });
            const freshData = await freshRes.json();
            const dnssecResult = freshData.success ? freshData.result : (data.result || { status: 'disabled' });
            return new Response(JSON.stringify({ success: data.success, dnssec: dnssecResult, errors: data.errors || [] }), { status: data.success ? 200 : 400, headers: { 'Content-Type': 'application/json' } });
        } catch (e) {
            return new Response(JSON.stringify({ success: false, errors: [{ message: e.message }] }), { status: 500, headers: { 'Content-Type': 'application/json' } });
        }
    }

    return new Response(JSON.stringify({ success: false, errors: [{ message: `Unknown action: "${action}"` }] }), { status: 400, headers: { 'Content-Type': 'application/json' } });
}