@tapflowio/relay 0.8.1 → 0.9.0
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.
- package/dist/RelayServer.d.ts +13 -0
- package/dist/RelayServer.d.ts.map +1 -1
- package/dist/RelayServer.js +80 -9
- package/dist/RelayServer.js.map +1 -1
- package/dist/api/auth.d.ts +3 -2
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +37 -3
- package/dist/api/auth.js.map +1 -1
- package/dist/api/builds.d.ts.map +1 -1
- package/dist/api/builds.js +14 -13
- package/dist/api/builds.js.map +1 -1
- package/dist/api/comments.d.ts.map +1 -1
- package/dist/api/comments.js +23 -10
- package/dist/api/comments.js.map +1 -1
- package/dist/api/team.d.ts.map +1 -1
- package/dist/api/team.js +4 -2
- package/dist/api/team.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/cert/AcmeCertProvider.d.ts +55 -0
- package/dist/lib/cert/AcmeCertProvider.d.ts.map +1 -0
- package/dist/lib/cert/AcmeCertProvider.js +62 -0
- package/dist/lib/cert/AcmeCertProvider.js.map +1 -0
- package/dist/lib/cert/AcmeClientIssuer.d.ts +23 -0
- package/dist/lib/cert/AcmeClientIssuer.d.ts.map +1 -0
- package/dist/lib/cert/AcmeClientIssuer.js +54 -0
- package/dist/lib/cert/AcmeClientIssuer.js.map +1 -0
- package/dist/lib/cert/CertProvider.d.ts +30 -0
- package/dist/lib/cert/CertProvider.d.ts.map +1 -0
- package/dist/lib/cert/CertProvider.js +4 -0
- package/dist/lib/cert/CertProvider.js.map +1 -0
- package/dist/lib/cert/CloudflareDnsProvider.d.ts +40 -0
- package/dist/lib/cert/CloudflareDnsProvider.d.ts.map +1 -0
- package/dist/lib/cert/CloudflareDnsProvider.js +93 -0
- package/dist/lib/cert/CloudflareDnsProvider.js.map +1 -0
- package/dist/lib/cert/DiskCertStore.d.ts +10 -0
- package/dist/lib/cert/DiskCertStore.d.ts.map +1 -0
- package/dist/lib/cert/DiskCertStore.js +35 -0
- package/dist/lib/cert/DiskCertStore.js.map +1 -0
- package/dist/lib/cert/DnsProvider.d.ts +7 -0
- package/dist/lib/cert/DnsProvider.d.ts.map +1 -0
- package/dist/lib/cert/DnsProvider.js +2 -0
- package/dist/lib/cert/DnsProvider.js.map +1 -0
- package/dist/lib/cert/ImportCertProvider.d.ts +20 -0
- package/dist/lib/cert/ImportCertProvider.d.ts.map +1 -0
- package/dist/lib/cert/ImportCertProvider.js +24 -0
- package/dist/lib/cert/ImportCertProvider.js.map +1 -0
- package/dist/lib/cert/VercelDnsProvider.d.ts +38 -0
- package/dist/lib/cert/VercelDnsProvider.d.ts.map +1 -0
- package/dist/lib/cert/VercelDnsProvider.js +146 -0
- package/dist/lib/cert/VercelDnsProvider.js.map +1 -0
- package/dist/lib/cert/accountKey.d.ts +2 -0
- package/dist/lib/cert/accountKey.d.ts.map +1 -0
- package/dist/lib/cert/accountKey.js +23 -0
- package/dist/lib/cert/accountKey.js.map +1 -0
- package/dist/lib/cert/addressPublisher.d.ts +20 -0
- package/dist/lib/cert/addressPublisher.d.ts.map +1 -0
- package/dist/lib/cert/addressPublisher.js +103 -0
- package/dist/lib/cert/addressPublisher.js.map +1 -0
- package/dist/lib/cert/createCertProvider.d.ts +20 -0
- package/dist/lib/cert/createCertProvider.d.ts.map +1 -0
- package/dist/lib/cert/createCertProvider.js +25 -0
- package/dist/lib/cert/createCertProvider.js.map +1 -0
- package/dist/lib/cert/dnsRegistry.d.ts +24 -0
- package/dist/lib/cert/dnsRegistry.d.ts.map +1 -0
- package/dist/lib/cert/dnsRegistry.js +36 -0
- package/dist/lib/cert/dnsRegistry.js.map +1 -0
- package/dist/lib/cert/index.d.ts +23 -0
- package/dist/lib/cert/index.d.ts.map +1 -0
- package/dist/lib/cert/index.js +12 -0
- package/dist/lib/cert/index.js.map +1 -0
- package/dist/lib/cert/parseCert.d.ts +3 -0
- package/dist/lib/cert/parseCert.d.ts.map +1 -0
- package/dist/lib/cert/parseCert.js +6 -0
- package/dist/lib/cert/parseCert.js.map +1 -0
- package/dist/lib/cert/renewal.d.ts +13 -0
- package/dist/lib/cert/renewal.d.ts.map +1 -0
- package/dist/lib/cert/renewal.js +28 -0
- package/dist/lib/cert/renewal.js.map +1 -0
- package/dist/lib/clientAddress.d.ts +12 -0
- package/dist/lib/clientAddress.d.ts.map +1 -0
- package/dist/lib/clientAddress.js +46 -0
- package/dist/lib/clientAddress.js.map +1 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +69 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/cors.d.ts +2 -0
- package/dist/lib/cors.d.ts.map +1 -0
- package/dist/lib/cors.js +18 -0
- package/dist/lib/cors.js.map +1 -0
- package/dist/lib/csrf.d.ts +3 -0
- package/dist/lib/csrf.d.ts.map +1 -0
- package/dist/lib/csrf.js +42 -0
- package/dist/lib/csrf.js.map +1 -0
- package/dist/lib/loadEnvFile.d.ts +2 -0
- package/dist/lib/loadEnvFile.d.ts.map +1 -0
- package/dist/lib/loadEnvFile.js +17 -0
- package/dist/lib/loadEnvFile.js.map +1 -0
- package/dist/lib/proxyConfig.d.ts +6 -0
- package/dist/lib/proxyConfig.d.ts.map +1 -0
- package/dist/lib/proxyConfig.js +27 -0
- package/dist/lib/proxyConfig.js.map +1 -0
- package/dist/lib/publicUrl.d.ts +3 -0
- package/dist/lib/publicUrl.d.ts.map +1 -0
- package/dist/lib/publicUrl.js +17 -0
- package/dist/lib/publicUrl.js.map +1 -0
- package/dist/lib/uploads.d.ts +2 -0
- package/dist/lib/uploads.d.ts.map +1 -0
- package/dist/lib/uploads.js +14 -0
- package/dist/lib/uploads.js.map +1 -0
- package/dist/middleware/rateLimit.d.ts +18 -0
- package/dist/middleware/rateLimit.d.ts.map +1 -0
- package/dist/middleware/rateLimit.js +55 -0
- package/dist/middleware/rateLimit.js.map +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +13 -1
- package/dist/router.js.map +1 -1
- package/dist/server.js +44 -5
- package/dist/server.js.map +1 -1
- package/package.json +6 -5
- package/public/assets/AppCenter-CaY73Tvf.js +16 -0
- package/public/assets/AppCenter-CaY73Tvf.js.br +0 -0
- package/public/assets/Default-DQiKj0NT.js +1 -0
- package/public/assets/Default-DQiKj0NT.js.br +0 -0
- package/public/assets/Invite-B4MMcQtw.js +1 -0
- package/public/assets/Invite-B4MMcQtw.js.br +0 -0
- package/public/assets/MacResources-BfZAJ5BP.js +5 -0
- package/public/assets/MacResources-BfZAJ5BP.js.br +0 -0
- package/public/assets/NotFound-D85RKzwv.js +1 -0
- package/public/assets/NotFound-D85RKzwv.js.br +0 -0
- package/public/assets/QASession-B9FSUtZo.js +130 -0
- package/public/assets/QASession-B9FSUtZo.js.br +0 -0
- package/public/assets/ResetPassword-Degmb6sl.js +1 -0
- package/public/assets/ResetPassword-Degmb6sl.js.br +0 -0
- package/public/assets/Setup-DKOFxDo2.js +1 -0
- package/public/assets/Setup-DKOFxDo2.js.br +0 -0
- package/public/assets/Team-Bgspan2B.js +6 -0
- package/public/assets/Team-Bgspan2B.js.br +0 -0
- package/public/assets/Tokens-C3u48FUG.js +6 -0
- package/public/assets/Tokens-C3u48FUG.js.br +0 -0
- package/public/assets/alert-dialog-Ci_cWoVo.js +7 -0
- package/public/assets/alert-dialog-Ci_cWoVo.js.br +0 -0
- package/public/assets/badge-Cktrrdvf.js +1 -0
- package/public/assets/badge-Cktrrdvf.js.br +0 -0
- package/public/assets/build-format-Blzidb1D.js +11 -0
- package/public/assets/build-format-Blzidb1D.js.br +0 -0
- package/public/assets/dialog-BuZjQWBQ.js +11 -0
- package/public/assets/dialog-BuZjQWBQ.js.br +0 -0
- package/public/assets/index-BI7E3MD-.css +1 -0
- package/public/assets/index-BI7E3MD-.css.br +0 -0
- package/public/assets/index-ClAB0eKw.js +258 -0
- package/public/assets/index-ClAB0eKw.js.br +0 -0
- package/public/assets/inter-latin-ext-standard-normal-DpK-iCPk.woff2 +0 -0
- package/public/assets/inter-latin-standard-normal-BwkfbSeq.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/public/assets/pencil-BoxokH2R.js +6 -0
- package/public/assets/pencil-BoxokH2R.js.br +0 -0
- package/public/assets/plus-C0Y4Yk9P.js +6 -0
- package/public/assets/plus-C0Y4Yk9P.js.br +0 -0
- package/public/assets/table-NBRnBazr.js +1 -0
- package/public/assets/table-NBRnBazr.js.br +0 -0
- package/public/assets/tabs-CB9a8dpY.js +1 -0
- package/public/assets/tabs-CB9a8dpY.js.br +0 -0
- package/public/assets/tinyh264.worker-DiJ50Hai.js.br +0 -0
- package/public/favicon.svg.br +0 -0
- package/public/index.html +2 -2
- package/public/index.html.br +0 -0
- package/public/logo-dark.svg.br +0 -0
- package/public/logo.svg.br +0 -0
- package/public/assets/index-Bhoumejm.js +0 -520
- package/public/assets/index-DX5Pf9T6.css +0 -1
- package/public/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/public/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/public/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
- package/public/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
- package/public/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
- package/public/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
- package/public/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/public/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/public/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
- package/public/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
- package/public/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
- package/public/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
- package/public/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/public/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/public/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
- package/public/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
- package/public/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
- package/public/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
- package/public/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/public/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/public/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
- package/public/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
- package/public/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
- package/public/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
- package/public/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/public/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/public/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/public/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/public/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/public/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/public/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/public/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/public/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
- package/public/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
- package/public/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
- package/public/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
- package/public/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/public/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/public/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- package/public/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
- package/public/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
- package/public/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- package/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Cloudflare DNS-01 solver (issue #232). 사용자 자기 Cloudflare 계정 토큰으로 동작한다(BYO).
|
|
2
|
+
// 토큰은 config가 아니라 env(TAPFLOW_CLOUDFLARE_TOKEN)에서 읽는다 — cloudflareDnsFromEnv 참고.
|
|
3
|
+
const DEFAULT_API_BASE = 'https://api.cloudflare.com/client/v4';
|
|
4
|
+
const TTL_SECONDS = 60;
|
|
5
|
+
// DNS 공급자 API 호출이 무한 대기하지 않도록 하는 요청 타임아웃.
|
|
6
|
+
export const DNS_API_TIMEOUT_MS = 30_000;
|
|
7
|
+
export class CloudflareDnsProvider {
|
|
8
|
+
name = 'cloudflare';
|
|
9
|
+
token;
|
|
10
|
+
fetchFn;
|
|
11
|
+
apiBase;
|
|
12
|
+
fixedZoneName;
|
|
13
|
+
zoneIdCache = new Map();
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
if (!opts.token)
|
|
16
|
+
throw new Error('Cloudflare API token is required (set TAPFLOW_CLOUDFLARE_TOKEN)');
|
|
17
|
+
this.token = opts.token;
|
|
18
|
+
this.fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
19
|
+
this.apiBase = opts.apiBase ?? DEFAULT_API_BASE;
|
|
20
|
+
this.fixedZoneName = opts.zoneName;
|
|
21
|
+
}
|
|
22
|
+
async setTxtRecord(fqdn, value) {
|
|
23
|
+
const zoneId = await this.zoneIdFor(fqdn);
|
|
24
|
+
const name = `_acme-challenge.${fqdn}`;
|
|
25
|
+
const existing = await this.findRecords(zoneId, 'TXT', name);
|
|
26
|
+
if (existing.some((r) => r.content === value))
|
|
27
|
+
return;
|
|
28
|
+
await this.request(`/zones/${zoneId}/dns_records`, 'POST', { type: 'TXT', name, content: value, ttl: TTL_SECONDS });
|
|
29
|
+
}
|
|
30
|
+
async removeTxtRecord(fqdn, value) {
|
|
31
|
+
const zoneId = await this.zoneIdFor(fqdn);
|
|
32
|
+
const name = `_acme-challenge.${fqdn}`;
|
|
33
|
+
const matches = (await this.findRecords(zoneId, 'TXT', name)).filter((r) => r.content === value);
|
|
34
|
+
for (const r of matches) {
|
|
35
|
+
await this.request(`/zones/${zoneId}/dns_records/${r.id}`, 'DELETE');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async upsertAddressRecord(fqdn, ip) {
|
|
39
|
+
const zoneId = await this.zoneIdFor(fqdn);
|
|
40
|
+
const type = ip.includes(':') ? 'AAAA' : 'A';
|
|
41
|
+
const existing = await this.findRecords(zoneId, type, fqdn);
|
|
42
|
+
const payload = { type, name: fqdn, content: ip, ttl: TTL_SECONDS };
|
|
43
|
+
if (existing.length > 0) {
|
|
44
|
+
await this.request(`/zones/${zoneId}/dns_records/${existing[0].id}`, 'PATCH', payload);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
await this.request(`/zones/${zoneId}/dns_records`, 'POST', payload);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async zoneIdFor(fqdn) {
|
|
51
|
+
const cacheKey = this.fixedZoneName ?? fqdn;
|
|
52
|
+
const cached = this.zoneIdCache.get(cacheKey);
|
|
53
|
+
if (cached)
|
|
54
|
+
return cached;
|
|
55
|
+
const zones = await this.request('/zones', 'GET');
|
|
56
|
+
const candidates = this.fixedZoneName
|
|
57
|
+
? zones.filter((z) => z.name === this.fixedZoneName)
|
|
58
|
+
: zones.filter((z) => fqdn === z.name || fqdn.endsWith(`.${z.name}`));
|
|
59
|
+
// 가장 구체적인(최장 suffix) zone 우선
|
|
60
|
+
candidates.sort((a, b) => b.name.length - a.name.length);
|
|
61
|
+
const zone = candidates[0];
|
|
62
|
+
if (!zone)
|
|
63
|
+
throw new Error(`No Cloudflare zone found for ${fqdn}`);
|
|
64
|
+
this.zoneIdCache.set(cacheKey, zone.id);
|
|
65
|
+
return zone.id;
|
|
66
|
+
}
|
|
67
|
+
async findRecords(zoneId, type, name) {
|
|
68
|
+
const qs = new URLSearchParams({ type, name }).toString();
|
|
69
|
+
return this.request(`/zones/${zoneId}/dns_records?${qs}`, 'GET');
|
|
70
|
+
}
|
|
71
|
+
async request(path, method, body) {
|
|
72
|
+
const res = await this.fetchFn(`${this.apiBase}${path}`, {
|
|
73
|
+
method,
|
|
74
|
+
headers: { Authorization: `Bearer ${this.token}`, 'content-type': 'application/json' },
|
|
75
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
76
|
+
signal: AbortSignal.timeout(DNS_API_TIMEOUT_MS),
|
|
77
|
+
});
|
|
78
|
+
const data = (await res.json());
|
|
79
|
+
if (!res.ok || !data.success) {
|
|
80
|
+
const msg = data.errors?.map((e) => e.message).join('; ') || `HTTP ${res.status}`;
|
|
81
|
+
throw new Error(`Cloudflare API error: ${msg}`);
|
|
82
|
+
}
|
|
83
|
+
return data.result;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** env(TAPFLOW_CLOUDFLARE_TOKEN)에서 토큰을 읽어 provider를 만든다. 토큰 미설정 시 throw. */
|
|
87
|
+
export function cloudflareDnsFromEnv(zoneName) {
|
|
88
|
+
const token = process.env.TAPFLOW_CLOUDFLARE_TOKEN;
|
|
89
|
+
if (!token)
|
|
90
|
+
throw new Error('TAPFLOW_CLOUDFLARE_TOKEN is not set');
|
|
91
|
+
return new CloudflareDnsProvider({ token, zoneName });
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=CloudflareDnsProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CloudflareDnsProvider.js","sourceRoot":"","sources":["../../../src/lib/cert/CloudflareDnsProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CertStore } from './AcmeCertProvider.js';
|
|
2
|
+
import type { CertMaterial } from './CertProvider.js';
|
|
3
|
+
export declare class DiskCertStore implements CertStore {
|
|
4
|
+
private readonly certPath;
|
|
5
|
+
private readonly keyPath;
|
|
6
|
+
constructor(dir: string);
|
|
7
|
+
load(): Promise<CertMaterial | null>;
|
|
8
|
+
save(material: CertMaterial): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=DiskCertStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiskCertStore.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/DiskCertStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,qBAAa,aAAc,YAAW,SAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;gBAEpB,GAAG,EAAE,MAAM;IAKjB,IAAI,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAUpC,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAUlD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { parseCertNotAfter } from './parseCert.js';
|
|
4
|
+
// cert/key를 디스크에 영속화한다. 만료는 저장값이 아니라 cert에서 파싱(드리프트 방지).
|
|
5
|
+
// 키 파일은 0600(JWT secret과 동일 패턴) — 개인키는 소유자만 읽는다.
|
|
6
|
+
export class DiskCertStore {
|
|
7
|
+
certPath;
|
|
8
|
+
keyPath;
|
|
9
|
+
constructor(dir) {
|
|
10
|
+
this.certPath = path.join(dir, 'cert.pem');
|
|
11
|
+
this.keyPath = path.join(dir, 'key.pem');
|
|
12
|
+
}
|
|
13
|
+
async load() {
|
|
14
|
+
try {
|
|
15
|
+
const cert = fs.readFileSync(this.certPath, 'utf-8');
|
|
16
|
+
const key = fs.readFileSync(this.keyPath, 'utf-8');
|
|
17
|
+
return { cert, key, expiresAt: parseCertNotAfter(cert) };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async save(material) {
|
|
24
|
+
fs.mkdirSync(path.dirname(this.certPath), { recursive: true });
|
|
25
|
+
fs.writeFileSync(this.certPath, material.cert, { mode: 0o644 });
|
|
26
|
+
fs.writeFileSync(this.keyPath, material.key, { mode: 0o600 });
|
|
27
|
+
try {
|
|
28
|
+
fs.chmodSync(this.keyPath, 0o600);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// best-effort on platforms without POSIX permissions
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=DiskCertStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiskCertStore.js","sourceRoot":"","sources":["../../../src/lib/cert/DiskCertStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAGvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAElD,yDAAyD;AACzD,iDAAiD;AACjD,MAAM,OAAO,aAAa;IACP,QAAQ,CAAQ;IAChB,OAAO,CAAQ;IAEhC,YAAY,GAAW;QACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAA;QAC1C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;IAC1C,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YACpD,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAClD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAA;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAsB;QAC/B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9D,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC/D,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;QAC7D,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,qDAAqD;QACvD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface DnsProvider {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
setTxtRecord(fqdn: string, value: string): Promise<void>;
|
|
4
|
+
removeTxtRecord(fqdn: string, value: string): Promise<void>;
|
|
5
|
+
upsertAddressRecord?(fqdn: string, ip: string): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=DnsProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DnsProvider.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/DnsProvider.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3D,mBAAmB,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DnsProvider.js","sourceRoot":"","sources":["../../../src/lib/cert/DnsProvider.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CertProvider, CertMaterial, CertStrategy } from './CertProvider.js';
|
|
2
|
+
export interface ImportCertProviderOptions {
|
|
3
|
+
certPath: string;
|
|
4
|
+
keyPath: string;
|
|
5
|
+
/** 테스트 주입용. 기본 fs.readFileSync(utf-8). */
|
|
6
|
+
readFileFn?: (filePath: string) => string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 사용자가 직접 마련한 cert/key 파일을 로드하는 CertProvider.
|
|
10
|
+
* ACME를 돌리지 않으므로 갱신은 사용자 책임 — renewIfNeeded는 항상 null.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ImportCertProvider implements CertProvider {
|
|
13
|
+
private readonly opts;
|
|
14
|
+
readonly strategy: CertStrategy;
|
|
15
|
+
private readonly readFile;
|
|
16
|
+
constructor(opts: ImportCertProviderOptions);
|
|
17
|
+
ensureCert(): Promise<CertMaterial>;
|
|
18
|
+
renewIfNeeded(): Promise<CertMaterial | null>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=ImportCertProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImportCertProvider.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/ImportCertProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGjF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IAIzC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;gBAE1B,IAAI,EAAE,yBAAyB;IAItD,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC;IAMnC,aAAa,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;CAGpD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { parseCertNotAfter } from './parseCert.js';
|
|
3
|
+
/**
|
|
4
|
+
* 사용자가 직접 마련한 cert/key 파일을 로드하는 CertProvider.
|
|
5
|
+
* ACME를 돌리지 않으므로 갱신은 사용자 책임 — renewIfNeeded는 항상 null.
|
|
6
|
+
*/
|
|
7
|
+
export class ImportCertProvider {
|
|
8
|
+
opts;
|
|
9
|
+
strategy = 'import-cert';
|
|
10
|
+
readFile;
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.opts = opts;
|
|
13
|
+
this.readFile = opts.readFileFn ?? ((filePath) => fs.readFileSync(filePath, 'utf-8'));
|
|
14
|
+
}
|
|
15
|
+
async ensureCert() {
|
|
16
|
+
const cert = this.readFile(this.opts.certPath);
|
|
17
|
+
const key = this.readFile(this.opts.keyPath);
|
|
18
|
+
return { cert, key, expiresAt: parseCertNotAfter(cert) };
|
|
19
|
+
}
|
|
20
|
+
async renewIfNeeded() {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=ImportCertProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImportCertProvider.js","sourceRoot":"","sources":["../../../src/lib/cert/ImportCertProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AASlD;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IAIA;IAHpB,QAAQ,GAAiB,aAAa,CAAA;IAC9B,QAAQ,CAA8B;IAEvD,YAA6B,IAA+B;QAA/B,SAAI,GAAJ,IAAI,CAA2B;QAC1D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,QAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;IAC/F,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC5C,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAA;IAC1D,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,OAAO,IAAI,CAAA;IACb,CAAC;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DnsProvider } from './DnsProvider.js';
|
|
2
|
+
import { type FetchLike } from './CloudflareDnsProvider.js';
|
|
3
|
+
export interface VercelDnsProviderOptions {
|
|
4
|
+
token: string;
|
|
5
|
+
/** 명시하면 도메인 디스커버리(GET /v5/domains)를 생략. */
|
|
6
|
+
zoneName?: string;
|
|
7
|
+
/** 팀 계정이면 teamId. */
|
|
8
|
+
teamId?: string;
|
|
9
|
+
/** 테스트 주입용. 기본 global fetch. */
|
|
10
|
+
fetchFn?: FetchLike;
|
|
11
|
+
/** 기본 https://api.vercel.com */
|
|
12
|
+
apiBase?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class VercelDnsProvider implements DnsProvider {
|
|
15
|
+
readonly name = "vercel";
|
|
16
|
+
private readonly token;
|
|
17
|
+
private readonly fetchFn;
|
|
18
|
+
private readonly apiBase;
|
|
19
|
+
private readonly fixedZoneName?;
|
|
20
|
+
private readonly teamId?;
|
|
21
|
+
private readonly zoneCache;
|
|
22
|
+
constructor(opts: VercelDnsProviderOptions);
|
|
23
|
+
setTxtRecord(fqdn: string, value: string): Promise<void>;
|
|
24
|
+
removeTxtRecord(fqdn: string, value: string): Promise<void>;
|
|
25
|
+
upsertAddressRecord(fqdn: string, ip: string): Promise<void>;
|
|
26
|
+
private subnameOf;
|
|
27
|
+
private url;
|
|
28
|
+
private zoneFor;
|
|
29
|
+
private findRecords;
|
|
30
|
+
private listAllDomains;
|
|
31
|
+
private listAllRecords;
|
|
32
|
+
private createRecord;
|
|
33
|
+
private deleteRecord;
|
|
34
|
+
private request;
|
|
35
|
+
}
|
|
36
|
+
/** env(TAPFLOW_VERCEL_TOKEN)에서 토큰을 읽어 provider를 만든다. 미설정 시 throw. (팀이면 TAPFLOW_VERCEL_TEAM_ID) */
|
|
37
|
+
export declare function vercelDnsFromEnv(zoneName?: string): VercelDnsProvider;
|
|
38
|
+
//# sourceMappingURL=VercelDnsProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VercelDnsProvider.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/VercelDnsProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAS/E,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qBAAqB;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gCAAgC;IAChC,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AASD,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,IAAI,YAAW;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;gBAE1C,IAAI,EAAE,wBAAwB;IASpC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxD,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlE,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,GAAG;YAOG,OAAO;YAWP,WAAW;YAQX,cAAc;YAkBd,cAAc;YAkBd,YAAY;YAIZ,YAAY;YAIZ,OAAO;CActB;AAED,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAIrE"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { DNS_API_TIMEOUT_MS } from './CloudflareDnsProvider.js';
|
|
2
|
+
// Vercel DNS-01 solver (issue #232). 사용자 자기 Vercel 계정 토큰으로 동작한다(BYO).
|
|
3
|
+
// 토큰은 config가 아니라 env(TAPFLOW_VERCEL_TOKEN)에서 읽는다(vercelDnsFromEnv).
|
|
4
|
+
// Vercel은 개별 레코드(id 보유) 모델: name은 도메인 기준 상대 subname(루트는 ''), TXT 값은 따옴표 없이.
|
|
5
|
+
const DEFAULT_API_BASE = 'https://api.vercel.com';
|
|
6
|
+
const TTL_SECONDS = 60;
|
|
7
|
+
export class VercelDnsProvider {
|
|
8
|
+
name = 'vercel';
|
|
9
|
+
token;
|
|
10
|
+
fetchFn;
|
|
11
|
+
apiBase;
|
|
12
|
+
fixedZoneName;
|
|
13
|
+
teamId;
|
|
14
|
+
zoneCache = new Map();
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
if (!opts.token)
|
|
17
|
+
throw new Error('Vercel API token is required (set TAPFLOW_VERCEL_TOKEN)');
|
|
18
|
+
this.token = opts.token;
|
|
19
|
+
this.fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
20
|
+
this.apiBase = opts.apiBase ?? DEFAULT_API_BASE;
|
|
21
|
+
this.fixedZoneName = opts.zoneName;
|
|
22
|
+
this.teamId = opts.teamId;
|
|
23
|
+
}
|
|
24
|
+
async setTxtRecord(fqdn, value) {
|
|
25
|
+
const recordName = `_acme-challenge.${fqdn}`;
|
|
26
|
+
const zone = await this.zoneFor(recordName);
|
|
27
|
+
const subname = this.subnameOf(recordName, zone);
|
|
28
|
+
const existing = await this.findRecords(zone, 'TXT', subname);
|
|
29
|
+
if (existing.some((r) => r.value === value || r.value === `"${value}"`))
|
|
30
|
+
return;
|
|
31
|
+
await this.createRecord(zone, { type: 'TXT', name: subname, value, ttl: TTL_SECONDS });
|
|
32
|
+
}
|
|
33
|
+
async removeTxtRecord(fqdn, value) {
|
|
34
|
+
const recordName = `_acme-challenge.${fqdn}`;
|
|
35
|
+
const zone = await this.zoneFor(recordName);
|
|
36
|
+
const subname = this.subnameOf(recordName, zone);
|
|
37
|
+
const matches = (await this.findRecords(zone, 'TXT', subname)).filter((r) => r.value === value || r.value === `"${value}"`);
|
|
38
|
+
for (const r of matches)
|
|
39
|
+
await this.deleteRecord(zone, r.id);
|
|
40
|
+
}
|
|
41
|
+
async upsertAddressRecord(fqdn, ip) {
|
|
42
|
+
const zone = await this.zoneFor(fqdn);
|
|
43
|
+
const subname = this.subnameOf(fqdn, zone);
|
|
44
|
+
const type = ip.includes(':') ? 'AAAA' : 'A';
|
|
45
|
+
// Vercel엔 레코드 PATCH가 번거로우니 기존 동type 레코드 삭제 후 재생성(upsert).
|
|
46
|
+
for (const r of await this.findRecords(zone, type, subname))
|
|
47
|
+
await this.deleteRecord(zone, r.id);
|
|
48
|
+
await this.createRecord(zone, { type, name: subname, value: ip, ttl: TTL_SECONDS });
|
|
49
|
+
}
|
|
50
|
+
// --- internals ---
|
|
51
|
+
subnameOf(fqdn, zone) {
|
|
52
|
+
if (fqdn === zone)
|
|
53
|
+
return '';
|
|
54
|
+
if (fqdn.endsWith(`.${zone}`))
|
|
55
|
+
return fqdn.slice(0, -(zone.length + 1));
|
|
56
|
+
throw new Error(`${fqdn} is not under Vercel zone ${zone}`);
|
|
57
|
+
}
|
|
58
|
+
url(path, params = {}) {
|
|
59
|
+
const q = new URLSearchParams(params);
|
|
60
|
+
if (this.teamId)
|
|
61
|
+
q.set('teamId', this.teamId);
|
|
62
|
+
const qs = q.toString();
|
|
63
|
+
return `${this.apiBase}${path}${qs ? `?${qs}` : ''}`;
|
|
64
|
+
}
|
|
65
|
+
async zoneFor(fqdn) {
|
|
66
|
+
if (this.fixedZoneName)
|
|
67
|
+
return this.fixedZoneName;
|
|
68
|
+
const cached = this.zoneCache.get(fqdn);
|
|
69
|
+
if (cached)
|
|
70
|
+
return cached;
|
|
71
|
+
const names = await this.listAllDomains();
|
|
72
|
+
const zone = names.filter((n) => fqdn === n || fqdn.endsWith(`.${n}`)).sort((a, b) => b.length - a.length)[0];
|
|
73
|
+
if (!zone)
|
|
74
|
+
throw new Error(`No Vercel domain found for ${fqdn}`);
|
|
75
|
+
this.zoneCache.set(fqdn, zone);
|
|
76
|
+
return zone;
|
|
77
|
+
}
|
|
78
|
+
async findRecords(zone, type, subname) {
|
|
79
|
+
const fullName = subname === '' ? zone : `${subname}.${zone}`;
|
|
80
|
+
const records = await this.listAllRecords(zone);
|
|
81
|
+
return records.filter((r) => r.type === type && (r.name === subname || r.name === fullName));
|
|
82
|
+
}
|
|
83
|
+
// Vercel 목록 API는 페이지네이션됨(기본/limit 초과 시). pagination.next(타임스탬프)를 until로 넘겨 끝까지 순회.
|
|
84
|
+
// >100 도메인/레코드 계정에서 "No Vercel domain found"나 누락으로 인한 idempotency 깨짐 방지.
|
|
85
|
+
async listAllDomains() {
|
|
86
|
+
const names = [];
|
|
87
|
+
let until;
|
|
88
|
+
for (let page = 0; page < 100; page++) {
|
|
89
|
+
const params = { limit: '100' };
|
|
90
|
+
if (until)
|
|
91
|
+
params.until = until;
|
|
92
|
+
const body = await this.request(this.url('/v5/domains', params), 'GET');
|
|
93
|
+
for (const d of body.domains)
|
|
94
|
+
names.push(d.name);
|
|
95
|
+
const next = body.pagination?.next;
|
|
96
|
+
if (!next)
|
|
97
|
+
break;
|
|
98
|
+
until = String(next);
|
|
99
|
+
}
|
|
100
|
+
return names;
|
|
101
|
+
}
|
|
102
|
+
async listAllRecords(zone) {
|
|
103
|
+
const all = [];
|
|
104
|
+
let until;
|
|
105
|
+
for (let page = 0; page < 100; page++) {
|
|
106
|
+
const params = { limit: '100' };
|
|
107
|
+
if (until)
|
|
108
|
+
params.until = until;
|
|
109
|
+
const body = await this.request(this.url(`/v5/domains/${zone}/records`, params), 'GET');
|
|
110
|
+
all.push(...(body.records ?? []));
|
|
111
|
+
const next = body.pagination?.next;
|
|
112
|
+
if (!next)
|
|
113
|
+
break;
|
|
114
|
+
until = String(next);
|
|
115
|
+
}
|
|
116
|
+
return all;
|
|
117
|
+
}
|
|
118
|
+
async createRecord(zone, record) {
|
|
119
|
+
await this.request(this.url(`/v2/domains/${zone}/records`), 'POST', record);
|
|
120
|
+
}
|
|
121
|
+
async deleteRecord(zone, recordId) {
|
|
122
|
+
await this.request(this.url(`/v2/domains/${zone}/records/${recordId}`), 'DELETE');
|
|
123
|
+
}
|
|
124
|
+
async request(url, method, body) {
|
|
125
|
+
const res = await this.fetchFn(url, {
|
|
126
|
+
method,
|
|
127
|
+
headers: { Authorization: `Bearer ${this.token}`, 'content-type': 'application/json' },
|
|
128
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
129
|
+
signal: AbortSignal.timeout(DNS_API_TIMEOUT_MS),
|
|
130
|
+
});
|
|
131
|
+
const data = (await res.json().catch(() => null));
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
const msg = data?.error?.message || `HTTP ${res.status}`;
|
|
134
|
+
throw new Error(`Vercel API error: ${msg}`);
|
|
135
|
+
}
|
|
136
|
+
return data;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** env(TAPFLOW_VERCEL_TOKEN)에서 토큰을 읽어 provider를 만든다. 미설정 시 throw. (팀이면 TAPFLOW_VERCEL_TEAM_ID) */
|
|
140
|
+
export function vercelDnsFromEnv(zoneName) {
|
|
141
|
+
const token = process.env.TAPFLOW_VERCEL_TOKEN;
|
|
142
|
+
if (!token)
|
|
143
|
+
throw new Error('TAPFLOW_VERCEL_TOKEN is not set');
|
|
144
|
+
return new VercelDnsProvider({ token, zoneName, teamId: process.env.TAPFLOW_VERCEL_TEAM_ID || undefined });
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=VercelDnsProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VercelDnsProvider.js","sourceRoot":"","sources":["../../../src/lib/cert/VercelDnsProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accountKey.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/accountKey.ts"],"names":[],"mappings":"AAKA,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAe7G"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
// ACME 계정 키를 디스크에 캐시한다. 매 발급마다 새 키로 새 LE 계정을 등록하면
|
|
4
|
+
// Accounts-per-IP 한도(3h/10)에 닿을 수 있으므로 재사용한다. 키 파일은 0600.
|
|
5
|
+
export async function loadOrCreateAccountKey(filePath, create) {
|
|
6
|
+
try {
|
|
7
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// not yet created
|
|
11
|
+
}
|
|
12
|
+
const key = await create();
|
|
13
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
14
|
+
fs.writeFileSync(filePath, key, { mode: 0o600 });
|
|
15
|
+
try {
|
|
16
|
+
fs.chmodSync(filePath, 0o600);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// best-effort on platforms without POSIX permissions
|
|
20
|
+
}
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=accountKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accountKey.js","sourceRoot":"","sources":["../../../src/lib/cert/accountKey.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,kDAAkD;AAClD,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAgB,EAAE,MAA6B;IAC1F,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,CAAA;IAC1B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACzD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAChD,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;IACvD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DnsProvider } from './DnsProvider.js';
|
|
2
|
+
export interface AddressPublisherTls {
|
|
3
|
+
domain: string;
|
|
4
|
+
dnsProvider: string;
|
|
5
|
+
/** 자동 감지 대신 쓸 고정 IP. */
|
|
6
|
+
address?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AddressPublisherOptions {
|
|
9
|
+
intervalMs?: number;
|
|
10
|
+
/** IP 소스 주입(테스트/오버라이드). 기본 = address ?? detectLanIPv4. */
|
|
11
|
+
getIp?: () => Promise<string | null>;
|
|
12
|
+
/** DnsProvider 주입(테스트). 기본 = dnsProvider별 env 팩토리. */
|
|
13
|
+
provider?: DnsProvider;
|
|
14
|
+
/** IP 불변이어도 이 틱 수마다 강제 재발행(self-heal). 기본 12(~1h). */
|
|
15
|
+
reassertEveryTicks?: number;
|
|
16
|
+
onError?: (err: unknown) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function detectLanIPv4(): Promise<string | null>;
|
|
19
|
+
export declare function startAddressPublisher(tls: AddressPublisherTls, opts?: AddressPublisherOptions): () => void;
|
|
20
|
+
//# sourceMappingURL=addressPublisher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressPublisher.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/addressPublisher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAYnD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACpC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,sDAAsD;IACtD,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACjC;AAGD,wBAAgB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBtD;AAGD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,mBAAmB,EAAE,IAAI,GAAE,uBAA4B,GAAG,MAAM,IAAI,CAkD9G"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import dgram from 'dgram';
|
|
2
|
+
import { createLogger } from '@tapflowio/agent-core';
|
|
3
|
+
import { dnsProviders } from './dnsRegistry.js';
|
|
4
|
+
const logger = createLogger('relay:dns');
|
|
5
|
+
// DHCP로 IP가 바뀔 수 있어 cert 갱신(12h)보다 자주 재확인한다.
|
|
6
|
+
const DEFAULT_INTERVAL_MS = 5 * 60_000;
|
|
7
|
+
// IP 불변이어도 이 틱 수마다 강제 재발행 — 외부에서 지워지거나 바뀐 레코드를 self-heal(기본 ~1h).
|
|
8
|
+
const DEFAULT_REASSERT_TICKS = 12;
|
|
9
|
+
// connect 콜백/error 둘 다 안 오는 극단 케이스에서 영구 await 방지.
|
|
10
|
+
const DETECT_TIMEOUT_MS = 2_000;
|
|
11
|
+
// UDP connect(패킷 미전송)로 기본 라우트 인터페이스 IPv4를 얻는다 — networkInterfaces()는 VPN/멀티NIC 오감지 위험.
|
|
12
|
+
export function detectLanIPv4() {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const sock = dgram.createSocket('udp4');
|
|
15
|
+
let settled = false;
|
|
16
|
+
const done = (ip) => {
|
|
17
|
+
if (settled)
|
|
18
|
+
return;
|
|
19
|
+
settled = true;
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
try {
|
|
22
|
+
sock.close();
|
|
23
|
+
}
|
|
24
|
+
catch { /* already closed */ }
|
|
25
|
+
resolve(ip && ip !== '0.0.0.0' ? ip : null);
|
|
26
|
+
};
|
|
27
|
+
const timer = setTimeout(() => done(null), DETECT_TIMEOUT_MS);
|
|
28
|
+
timer.unref?.();
|
|
29
|
+
sock.once('error', () => done(null));
|
|
30
|
+
try {
|
|
31
|
+
sock.connect(53, '8.8.8.8', () => {
|
|
32
|
+
try {
|
|
33
|
+
done(sock.address().address);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
done(null);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
done(null);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// byo-api-token일 때 LAN IP를 자기 도메인 A 레코드로 발행 — 팀원은 DNS를 손대지 않고 도메인만 연다.
|
|
46
|
+
export function startAddressPublisher(tls, opts = {}) {
|
|
47
|
+
const dns = opts.provider ?? dnsProviders.get(tls.dnsProvider)?.fromEnv();
|
|
48
|
+
if (!dns) {
|
|
49
|
+
logger.warn(`unknown dnsProvider "${tls.dnsProvider}" — skipping address auto-publish`);
|
|
50
|
+
return () => { };
|
|
51
|
+
}
|
|
52
|
+
const upsert = dns.upsertAddressRecord?.bind(dns);
|
|
53
|
+
if (!upsert) {
|
|
54
|
+
logger.warn(`${dns.name} cannot publish A records — skipping address auto-publish`);
|
|
55
|
+
return () => { };
|
|
56
|
+
}
|
|
57
|
+
const getIp = opts.getIp ?? (tls.address ? () => Promise.resolve(tls.address ?? null) : detectLanIPv4);
|
|
58
|
+
const reassertEvery = opts.reassertEveryTicks ?? DEFAULT_REASSERT_TICKS;
|
|
59
|
+
let last = null;
|
|
60
|
+
let skipped = 0;
|
|
61
|
+
const publish = async () => {
|
|
62
|
+
try {
|
|
63
|
+
const ip = await getIp();
|
|
64
|
+
if (!ip) {
|
|
65
|
+
logger.warn(`could not determine LAN IP to publish ${tls.domain}`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// 보통은 IP 불변이면 skip하되, reassertEvery 틱마다 한 번은 강제 재발행해 외부 변경을 self-heal한다.
|
|
69
|
+
if (ip === last && skipped < reassertEvery) {
|
|
70
|
+
skipped++;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await upsert(tls.domain, ip);
|
|
74
|
+
last = ip;
|
|
75
|
+
skipped = 0;
|
|
76
|
+
logger.info(`published ${tls.domain} A -> ${ip}`);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (opts.onError)
|
|
80
|
+
opts.onError(err);
|
|
81
|
+
else
|
|
82
|
+
logger.warn(`failed to publish ${tls.domain} A record: ${String(err)}`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
// setInterval이 이전 upsert 완료 전 다음 publish를 시작하면 쓰기가 역순으로 끝나 stale IP가 남을 수 있다.
|
|
86
|
+
let inFlight = false;
|
|
87
|
+
const tick = async () => {
|
|
88
|
+
if (inFlight)
|
|
89
|
+
return;
|
|
90
|
+
inFlight = true;
|
|
91
|
+
try {
|
|
92
|
+
await publish();
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
inFlight = false;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
void tick();
|
|
99
|
+
const timer = setInterval(() => void tick(), opts.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
100
|
+
timer.unref?.();
|
|
101
|
+
return () => clearInterval(timer);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=addressPublisher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressPublisher.js","sourceRoot":"","sources":["../../../src/lib/cert/addressPublisher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CertProvider } from './CertProvider.js';
|
|
2
|
+
export type TlsConfig = {
|
|
3
|
+
mode: 'byo-api-token';
|
|
4
|
+
domain: string;
|
|
5
|
+
dnsProvider: string;
|
|
6
|
+
} | {
|
|
7
|
+
mode: 'import-cert';
|
|
8
|
+
certPath: string;
|
|
9
|
+
keyPath: string;
|
|
10
|
+
};
|
|
11
|
+
export interface CreateCertProviderDeps {
|
|
12
|
+
dataDir: string;
|
|
13
|
+
/** ACME 계정 이메일. 기본 env TAPFLOW_ACME_EMAIL. */
|
|
14
|
+
email?: string;
|
|
15
|
+
/** LE 스테이징 사용(테스트). 기본 env TAPFLOW_ACME_STAGING === '1'. */
|
|
16
|
+
staging?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** config.tls를 적절한 CertProvider로 매핑한다. 비밀(토큰)은 env에서 읽는다. */
|
|
19
|
+
export declare function createCertProvider(tls: TlsConfig, deps: CreateCertProviderDeps): CertProvider;
|
|
20
|
+
//# sourceMappingURL=createCertProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createCertProvider.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/createCertProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AASrD,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,sBAAsB,GAAG,YAAY,CAe7F"}
|