Files
AriWeb-Suite/modules/webmaker/lib/dns/porkbun.js
T

37 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// ============================================================================
// Porkbun DNS 공급자 (v1.5.36, 요구 5) — Porkbun 에 등록(또는 Porkbun 네임서버를 쓰는) 도메인의 레코드 관리
//
// 접속 정보: 보관소 종류 'porkbun' — 키(secret) = Secret API Key, 설정 API_KEY=… (공개 API 키). 도메인마다 Porkbun 화면에서 "API Access" 를 켜야 한다.
// API: https://api.porkbun.com/api/json/v3/ (JSON, 모든 호출이 POST 이고 본문에 apikey·secretapikey 를 넣는다). 시험용 대체 주소: PORKBUN_API_BASE.
// 영역은 "계정에 있는 도메인" 그 자체라 따로 만들지 않는다(createZone = 계정 보유 확인). 레코드마다 id 가 있어 지울 때 이름·종류·값으로 id 를 찾는다.
// ============================================================================
const httpError = (m, s = 400) => Object.assign(new Error(m), { status: s });
const base = () => (process.env.PORKBUN_API_BASE || 'https://api.porkbun.com/api/json/v3').replace(/\/+$/, '');
function creds() { let x = null; try { x = require('../vault').resolve('porkbun'); } catch (_) {} const key = x && x.settings && (x.settings.API_KEY || x.settings.PORKBUN_API_KEY); if (!x || !x.secret || !key) return null; return { apikey: key, secretapikey: x.secret }; }
const ready = () => !!creds();
/** 호출: 응답 { status: 'SUCCESS'|'ERROR', message, ... } */
async function call(path, body) {
const c = creds(); if (!c) throw httpError('Porkbun 접속 정보가 없습니다 (관리자 콘솔 › 설정 › 외부 연동 접속 정보 › Porkbun: API_KEY 설정과 Secret API Key)', 503);
let r; try { r = await fetch(`${base()}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...c, ...(body || {}) }), signal: AbortSignal.timeout(25000) }); } catch (e) { throw httpError(`Porkbun 연결 실패: ${e.message}`, 502); }
const j = await r.json().catch(() => ({}));
if (!r.ok || j.status !== 'SUCCESS') throw httpError(`Porkbun 오류: ${j.message || `HTTP ${r.status}`}`, r.status === 403 || /invalid api|not authorized|api access/i.test(j.message || '') ? 403 : r.status >= 500 ? 502 : 400);
return j;
}
const low = (d) => String(d).toLowerCase().replace(/\.$/, '');
async function listZones() { const j = await call('/domain/listAll', { start: '0', includeLabels: 'no' }); return (j.domains || []).map((d) => ({ id: d.domain, name: low(d.domain), status: d.status, expires: d.expireDate || null, nameServers: [] })); }
async function getZone(domain) { const z = (await listZones()).find((x) => x.name === low(domain)); if (!z) return null; try { const ns = await call(`/domain/getNs/${low(domain)}`); z.nameServers = ns.ns || []; } catch (_) {} return z; }
async function createZone(domain) { const z = await getZone(domain); if (!z) throw httpError(`Porkbun 계정에 ${domain} 이(가) 없습니다 — Porkbun 에서 등록한(또는 이전한) 도메인만 관리할 수 있습니다`, 404); return z; }
async function deleteZone(domain) { const recs = await rawRecords(domain); for (const r of recs) if (!(r.type === 'NS' && r.rel === '')) await call(`/dns/delete/${low(domain)}/${r.id}`); return { ok: true, existed: true, cleared: recs.length }; }
async function rawRecords(domain) { const j = await call(`/dns/retrieve/${low(domain)}`); const d = low(domain); return (j.records || []).map((r) => { const n = low(r.name); return { id: r.id, rel: n === d ? '' : n.endsWith('.' + d) ? n.slice(0, -(d.length + 1)) : n, type: r.type, value: r.content, ttl: Number(r.ttl) || 600, prio: r.prio != null && r.prio !== '' ? Number(r.prio) : undefined }; }); }
async function listRecords(domain) { return (await rawRecords(domain)).map(({ rel, type, value, ttl, prio }) => ({ name: rel, type, value, ttl, prio })); }
async function updateRecords(domain, { add = [], remove = [] }) {
let n = 0;
if (remove.length) { const cur = await rawRecords(domain); for (const r of remove) { const hit = cur.find((x) => x.rel === String(r.name || '') && x.type === r.type && String(x.value) === String(r.value)); if (hit) { await call(`/dns/delete/${low(domain)}/${hit.id}`); n += 1; } } }
for (const r of add) { await call(`/dns/create/${low(domain)}`, { name: r.name || '', type: r.type, content: r.value, ttl: String(Math.max(600, r.ttl || 600)), ...(r.prio != null ? { prio: String(r.prio) } : {}) }); n += 1; }
return { ok: true, changes: n };
}
async function nameServers(domain) { const j = await call(`/domain/getNs/${low(domain)}`); return j.ns || []; }
async function setNameServers(domain, ns) { await call(`/domain/updateNs/${low(domain)}`, { ns }); return { ok: true }; }
module.exports = { id: 'porkbun', label: 'Porkbun', ready, creds, call, listZones, getZone, createZone, deleteZone, listRecords, updateRecords, nameServers, setNameServers };