hfs 3.3.2 → 3.4.0-alpha1
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/admin/assets/AccountsPage-C6J2hRcY.js +4 -0
- package/admin/assets/ConfigFilePage-D5TZZyMR.js +4 -0
- package/admin/assets/CustomHtmlPage-DV2-ufWZ.js +2 -0
- package/admin/assets/InternetPage-DT3wSQYQ.js +6 -0
- package/admin/assets/LangPage-DbXCv7wR.js +2 -0
- package/admin/assets/LogsPage-DmAxUZpu.js +3 -0
- package/admin/assets/MonitorPage-D7ydOeq_.js +2 -0
- package/admin/assets/OptionsPage-DpN3ajRm.js +3 -0
- package/admin/assets/PluginsPage-BYzfwuvg.js +1 -0
- package/admin/assets/icons-CfBG9vvB.js +1 -0
- package/admin/assets/index-CVCnYMoB.js +755 -0
- package/admin/assets/index-mI9kZlpb.css +1 -0
- package/admin/assets/routing-C4l8rnk_.js +1 -0
- package/admin/index.html +2 -2
- package/central.json +100 -1
- package/frontend/assets/{dist-legacy-zQdkj0Z9.js → dist-legacy-CKczq_fx.js} +1 -1
- package/frontend/assets/index-legacy-ZYae30AY.js +1 -0
- package/frontend/assets/{lib-legacy-CRrFEEqm.js → lib-legacy-D2tyILlj.js} +1 -1
- package/frontend/assets/{sha512-legacy-BB7Mdt0y.js → sha512-legacy-CFcDeiiP.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +60 -60
- package/package.json +3 -3
- package/src/acme.js +143 -50
- package/src/acmeConfig.js +43 -0
- package/src/acmeDns.js +285 -0
- package/src/admin-langs/embedded.js +33 -0
- package/src/admin-langs/hfs-admin-lang-ar.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-bg.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-de.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-el.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-en.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-es.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-fi.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-fr.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-hu.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-it.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ja.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ko.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-lt.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ms.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-nl.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-pl.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-pt-br.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ro.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-ru.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-sr-latn.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-sr.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-th.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-tr.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-uk.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-vi.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-zh-tw.json +1141 -0
- package/src/admin-langs/hfs-admin-lang-zh.json +1141 -0
- package/src/adminApis.js +2 -2
- package/src/adminLang.js +50 -0
- package/src/api.lang.js +22 -13
- package/src/api.log.js +5 -3
- package/src/api.net.js +22 -4
- package/src/api.plugins.js +53 -19
- package/src/api.vfs.js +2 -1
- package/src/argv.js +4 -0
- package/src/auth.js +0 -2
- package/src/basicWeb.js +6 -2
- package/src/block.js +1 -1
- package/src/config.js +1 -0
- package/src/connections.js +4 -7
- package/src/const.js +1 -1
- package/src/cross-const.js +7 -5
- package/src/cross.js +16 -3
- package/src/frontEndApis.js +44 -7
- package/src/github.js +6 -37
- package/src/i18n.js +16 -4
- package/src/index.js +4 -2
- package/src/lang.js +15 -8
- package/src/langs/embedded.js +2 -1
- package/src/langs/hfs-lang-ar.json +5 -1
- package/src/langs/hfs-lang-bg.json +5 -1
- package/src/langs/hfs-lang-de.json +5 -1
- package/src/langs/hfs-lang-el.json +5 -1
- package/src/langs/hfs-lang-en.json +188 -0
- package/src/langs/hfs-lang-es.json +7 -3
- package/src/langs/hfs-lang-fi.json +6 -2
- package/src/langs/hfs-lang-fr.json +10 -6
- package/src/langs/hfs-lang-hu.json +5 -1
- package/src/langs/hfs-lang-it.json +9 -4
- package/src/langs/hfs-lang-ja.json +5 -1
- package/src/langs/hfs-lang-ko.json +5 -1
- package/src/langs/hfs-lang-lt.json +5 -1
- package/src/langs/hfs-lang-ms.json +7 -1
- package/src/langs/hfs-lang-nl.json +5 -1
- package/src/langs/hfs-lang-pl.json +5 -1
- package/src/langs/hfs-lang-pt-br.json +7 -3
- package/src/langs/hfs-lang-ro.json +5 -1
- package/src/langs/hfs-lang-ru.json +7 -3
- package/src/langs/hfs-lang-sr-latn.json +5 -1
- package/src/langs/hfs-lang-sr.json +5 -1
- package/src/langs/hfs-lang-th.json +6 -2
- package/src/langs/hfs-lang-tr.json +6 -2
- package/src/langs/hfs-lang-uk.json +14 -10
- package/src/langs/hfs-lang-vi.json +5 -1
- package/src/langs/hfs-lang-zh-tw.json +5 -1
- package/src/langs/hfs-lang-zh.json +5 -1
- package/src/log.js +7 -1
- package/src/middlewares.js +85 -14
- package/src/multipartUpload.js +21 -0
- package/src/nat.js +10 -7
- package/src/outboundProxy.js +1 -1
- package/src/perm.js +38 -20
- package/src/pluginCatalog.js +66 -0
- package/src/pluginUsage.js +64 -0
- package/src/plugins.js +12 -0
- package/src/roots.js +22 -13
- package/src/serveFile.js +26 -5
- package/src/serveGuiFiles.js +5 -2
- package/src/update.js +51 -18
- package/src/upload.js +49 -8
- package/src/util-http.js +31 -5
- package/src/vfs.js +84 -4
- package/src/webdav.js +12 -6
- package/admin/assets/AccountsPage-Cvi9DSc1.js +0 -3
- package/admin/assets/ArrayField-ChS9pNra.js +0 -2
- package/admin/assets/ConfigFilePage-6jCYVkbr.js +0 -4
- package/admin/assets/CustomHtmlPage-DaEXsEcQ.js +0 -2
- package/admin/assets/DataTable-Cf5S5yWr.js +0 -119
- package/admin/assets/DateTimeField--mTsSB1l.js +0 -1
- package/admin/assets/EventManager-BJWba-fL.js +0 -1
- package/admin/assets/FileField-B0WhrAuk.js +0 -117
- package/admin/assets/InternetPage-BICQDG_K.js +0 -8
- package/admin/assets/LangPage-D42UPSxC.js +0 -2
- package/admin/assets/LogsPage-DQTrf5GE.js +0 -4
- package/admin/assets/MenuButton-DDfB3119.js +0 -1
- package/admin/assets/MonitorPage-CH06c8aW.js +0 -2
- package/admin/assets/OptionsPage-BvXLA_R0.js +0 -3
- package/admin/assets/PluginsPage-CyaVJZkM.js +0 -1
- package/admin/assets/Tabs-CGfV3BK0.js +0 -1
- package/admin/assets/TextEditor-BeS-mR2I.css +0 -1
- package/admin/assets/TextEditor-Df9LSzm6.js +0 -34
- package/admin/assets/VfsPathField-6Kyy5vuQ.js +0 -1
- package/admin/assets/agentIcons-DwT9U-lh.js +0 -1
- package/admin/assets/baseUrl-D7S0KsqZ.js +0 -2
- package/admin/assets/icons-DWkdo8hc.js +0 -1
- package/admin/assets/index-11n5z0Ez.css +0 -1
- package/admin/assets/index-BDdPG215.js +0 -477
- package/admin/assets/routing-CxxvyWME.js +0 -1
- package/frontend/assets/index-legacy-B8EvbIOB.js +0 -1
package/src/acme.js
CHANGED
|
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.acmeRenewError = exports.
|
|
6
|
+
exports.acmeRenewError = exports.acmeMiddleware = void 0;
|
|
7
|
+
exports.getAcmeStatus = getAcmeStatus;
|
|
8
|
+
exports.getAcmeStatusEvents = getAcmeStatusEvents;
|
|
9
|
+
exports.makeCert = makeCert;
|
|
7
10
|
const misc_1 = require("./misc");
|
|
8
11
|
const http_1 = require("http");
|
|
9
12
|
const nat_1 = require("./nat");
|
|
@@ -12,7 +15,11 @@ const apiMiddleware_1 = require("./apiMiddleware");
|
|
|
12
15
|
const acme_client_1 = __importDefault(require("acme-client"));
|
|
13
16
|
const debounceAsync_1 = require("./debounceAsync");
|
|
14
17
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
15
|
-
const
|
|
18
|
+
const acmeConfig_1 = require("./acmeConfig");
|
|
19
|
+
const acmeDns_1 = require("./acmeDns");
|
|
20
|
+
const github_1 = require("./github");
|
|
21
|
+
const promises_2 = require("node:dns/promises");
|
|
22
|
+
const node_stream_1 = require("node:stream");
|
|
16
23
|
const events_1 = __importDefault(require("./events"));
|
|
17
24
|
const selfCheck_1 = require("./selfCheck");
|
|
18
25
|
const net_1 = require("net");
|
|
@@ -49,32 +56,52 @@ const TEMP_MAP = (0, nat_1.upnpMappingParam)(80, 80, 'hfs temporary', 5000); //
|
|
|
49
56
|
stop();
|
|
50
57
|
return client.removeMapping(TEMP_MAP);
|
|
51
58
|
});
|
|
52
|
-
async function generateSSLCert(
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
async function generateSSLCert(names, email) {
|
|
60
|
+
const domain = names[0];
|
|
61
|
+
const method = acmeConfig_1.acmeChallenge.get();
|
|
62
|
+
const ipCertificate = names.some(net_1.isIP);
|
|
63
|
+
if (!['http-01', 'dns-01'].includes(method))
|
|
64
|
+
throw Error("Invalid certificate validation method");
|
|
65
|
+
if (method === 'http-01' && names.some(n => n.startsWith('*.')))
|
|
66
|
+
throw Error("Select a DNS provider for wildcard certificates");
|
|
67
|
+
if (method === 'dns-01' && ipCertificate)
|
|
68
|
+
throw Error("DNS validation cannot be used for IP addresses");
|
|
69
|
+
const config = structuredClone(acmeConfig_1.acmeDns.get()[0]);
|
|
70
|
+
const info = method === 'dns-01' ? await (0, github_1.getProjectInfo)() : undefined;
|
|
71
|
+
const provider = info && config && (0, acmeDns_1.getDnsProviders)(info.acmeDns).get(config.provider);
|
|
72
|
+
if (method === 'dns-01') {
|
|
73
|
+
if (!provider?.available || config?.config_version !== provider.config_version)
|
|
74
|
+
throw Error("DNS provider unavailable or incompatible; update HFS or reconfigure the provider");
|
|
75
|
+
if (Object.keys(provider.fields).some(k => !config.credentials?.[k]))
|
|
76
|
+
throw Error("Enter the DNS provider credentials");
|
|
77
|
+
}
|
|
78
|
+
const resolver = new promises_2.Resolver({ timeout: 3000, tries: 2 });
|
|
79
|
+
if (info)
|
|
80
|
+
resolver.setServers(info.dnsServers);
|
|
81
|
+
const cleanups = new Map();
|
|
82
|
+
const cleanupErrors = [];
|
|
83
|
+
let tempSrv;
|
|
66
84
|
let tempMap;
|
|
85
|
+
acmeOngoing = true;
|
|
67
86
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
if (method === 'http-01') {
|
|
88
|
+
progress("Checking port 80");
|
|
89
|
+
const nat = await (0, nat_1.getNatInfo)();
|
|
90
|
+
const { http } = await (0, listen_1.getServerStatus)();
|
|
91
|
+
if (!(nat.externalPort === 80 || http.listening && http.port === 80)) {
|
|
92
|
+
tempSrv = (0, http_1.createServer)((req, res) => acmeListener(req, res) || res.end('HFS'));
|
|
93
|
+
await new Promise(resolve => tempSrv.listen(80, resolve).on('error', () => resolve()));
|
|
94
|
+
}
|
|
95
|
+
const checkUrl = `http://${(0, misc_1.ipForUrl)(domain)}`;
|
|
96
|
+
let check = await (0, selfCheck_1.selfCheck)(checkUrl);
|
|
97
|
+
if (check?.success === false && nat.upnp && !nat.mapped80) {
|
|
98
|
+
tempMap = await (0, misc_1.haveTimeout)(10_000, (0, nat_1.getUpnpClient)().createMapping(TEMP_MAP)).catch(() => undefined);
|
|
99
|
+
check = await (0, selfCheck_1.selfCheck)(checkUrl);
|
|
100
|
+
}
|
|
101
|
+
if (check?.success === false)
|
|
102
|
+
throw new apiMiddleware_1.ApiError(misc_1.HTTP_FAILED_DEPENDENCY, "port 80 is not working on the specified domain");
|
|
74
103
|
}
|
|
75
|
-
|
|
76
|
-
if (check?.success === false)
|
|
77
|
-
throw new apiMiddleware_1.ApiError(misc_1.HTTP_FAILED_DEPENDENCY, "port 80 is not working on the specified domain");
|
|
104
|
+
progress("Requesting certificate");
|
|
78
105
|
const acmeClient = new acme_client_1.default.Client({
|
|
79
106
|
accountKey: await acme_client_1.default.crypto.createPrivateKey(),
|
|
80
107
|
directoryUrl: acme_client_1.default.directory.letsencrypt.production
|
|
@@ -90,19 +117,57 @@ async function generateSSLCert(domain, email, altNames = []) {
|
|
|
90
117
|
acme_client_1.default.setLogger(console.debug);
|
|
91
118
|
const [key, csr] = await acme_client_1.default.crypto.createCsr({
|
|
92
119
|
commonName: (0, net_1.isIP)(domain) ? undefined : domain, // rejected because Boulder doesn't accept IP addresses in the Common Name
|
|
93
|
-
altNames:
|
|
120
|
+
altNames: names,
|
|
94
121
|
});
|
|
95
122
|
const cert = await acmeClient.auto({
|
|
96
123
|
csr,
|
|
97
124
|
email,
|
|
98
|
-
challengePriority: [
|
|
125
|
+
challengePriority: [method],
|
|
99
126
|
skipChallengeVerification: true, // on NAT, trying to connect to your external ip will likely get your modem instead of the challenge server
|
|
100
127
|
termsOfServiceAgreed: true,
|
|
101
|
-
async challengeCreateFn(
|
|
102
|
-
|
|
128
|
+
async challengeCreateFn(auth, challenge, value) {
|
|
129
|
+
if (challenge.type !== method)
|
|
130
|
+
throw Error("Unsupported ACME challenge");
|
|
131
|
+
if (method === 'http-01') {
|
|
132
|
+
acmeTokens[challenge.token] = value;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
progress("Creating DNS TXT record", auth.identifier.value);
|
|
136
|
+
const record = await (0, acmeDns_1.dnsChallengeRecord)(auth.identifier.value, value, resolver);
|
|
137
|
+
cleanups.set(challenge.token, await provider.present(record, config.credentials));
|
|
138
|
+
progress("Waiting for DNS propagation", auth.identifier.value);
|
|
139
|
+
await (0, acmeDns_1.waitForDnsRecord)(record, resolver, provider.propagation_timeout === undefined ? undefined : provider.propagation_timeout * 1000);
|
|
140
|
+
}
|
|
141
|
+
progress("Validating domain", auth.identifier.value);
|
|
142
|
+
},
|
|
143
|
+
async challengeRemoveFn(_, challenge) {
|
|
144
|
+
delete acmeTokens[challenge.token];
|
|
145
|
+
const cleanup = cleanups.get(challenge.token);
|
|
146
|
+
if (cleanup) {
|
|
147
|
+
cleanups.delete(challenge.token);
|
|
148
|
+
// acme-client suppresses cleanup errors, so keep them visible in the final status
|
|
149
|
+
try {
|
|
150
|
+
await cleanup();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
cleanupErrors.push("Could not remove a DNS challenge TXT record");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
103
157
|
});
|
|
104
158
|
console.log("ACME certificate generated");
|
|
105
|
-
return { key, cert };
|
|
159
|
+
return { key, cert, warning: cleanupErrors.join("; ") || undefined };
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (method === 'http-01' && error instanceof Error) {
|
|
163
|
+
if (error.message.includes('Timeout'))
|
|
164
|
+
error = Error("ensure your router is forwarding port 80 correctly");
|
|
165
|
+
else if (error.message.includes('not match this challenge'))
|
|
166
|
+
error = Error("a different server is responding on port 80 of your domain(s)");
|
|
167
|
+
}
|
|
168
|
+
if (cleanupErrors.length)
|
|
169
|
+
throw Error(`${error instanceof Error ? error.message : error}; ${cleanupErrors.join('; ')}`);
|
|
170
|
+
throw error;
|
|
106
171
|
}
|
|
107
172
|
finally {
|
|
108
173
|
if (tempMap && nat_1.upnpEnabled.get()) {
|
|
@@ -110,33 +175,61 @@ async function generateSSLCert(domain, email, altNames = []) {
|
|
|
110
175
|
(0, nat_1.getUpnpClient)().removeMapping(TEMP_MAP).catch(() => { }); // clean after ourselves
|
|
111
176
|
}
|
|
112
177
|
acmeOngoing = false;
|
|
113
|
-
if (tempSrv)
|
|
114
|
-
await new Promise(res => tempSrv.close(res));
|
|
178
|
+
if (tempSrv?.listening)
|
|
179
|
+
await new Promise(res => tempSrv.close(() => res()));
|
|
115
180
|
console.debug('ACME terminated');
|
|
116
181
|
}
|
|
117
182
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
183
|
+
let acmeStatus = { id: 0, state: 'idle', message: '' };
|
|
184
|
+
function getAcmeStatus() { return acmeStatus; }
|
|
185
|
+
function progress(message, domain) {
|
|
186
|
+
acmeStatus = { ...acmeStatus, message, domain };
|
|
187
|
+
events_1.default.emit('acmeStatus', acmeStatus);
|
|
188
|
+
}
|
|
189
|
+
function getAcmeStatusEvents() {
|
|
190
|
+
let off;
|
|
191
|
+
return new node_stream_1.Readable({
|
|
192
|
+
objectMode: true,
|
|
193
|
+
read() {
|
|
194
|
+
if (off)
|
|
195
|
+
return;
|
|
196
|
+
off = events_1.default.on('acmeStatus', status => this.push(status));
|
|
197
|
+
this.once('close', off);
|
|
198
|
+
this.push(acmeStatus);
|
|
199
|
+
},
|
|
123
200
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
201
|
+
}
|
|
202
|
+
async function makeCert(domain, email, altNames = []) {
|
|
203
|
+
if (acmeStatus.state === 'running')
|
|
204
|
+
throw Error("Certificate request already running");
|
|
205
|
+
acmeStatus = { id: acmeStatus.id + 1, state: 'running', message: '' };
|
|
206
|
+
progress("Starting certificate request");
|
|
207
|
+
try {
|
|
208
|
+
const names = (0, acmeDns_1.certificateNames)([domain, ...altNames]);
|
|
209
|
+
const res = await generateSSLCert(names, email);
|
|
210
|
+
progress("Installing certificate");
|
|
211
|
+
const CERT_FILE = 'acme.cer';
|
|
212
|
+
const KEY_FILE = 'acme.key';
|
|
213
|
+
await promises_1.default.writeFile(KEY_FILE, res.key, { mode: 0o600 });
|
|
214
|
+
await promises_1.default.writeFile(CERT_FILE, res.cert);
|
|
215
|
+
listen_1.cert.set(CERT_FILE);
|
|
216
|
+
listen_1.privateKey.set(KEY_FILE);
|
|
217
|
+
exports.acmeRenewError = '';
|
|
218
|
+
acmeStatus = { ...acmeStatus, state: 'done', warning: res.warning };
|
|
219
|
+
progress("Certificate created");
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
acmeStatus = { ...acmeStatus, state: 'error' };
|
|
223
|
+
progress(error instanceof Error ? error.message : String(error));
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
132
227
|
exports.acmeRenewError = '';
|
|
133
|
-
const acmeDomain = (0, config_1.defineConfig)(misc_1.CFG.acme_domain, '');
|
|
134
|
-
const acmeRenew = (0, config_1.defineConfig)(misc_1.CFG.acme_renew, false); // handle config changes
|
|
135
228
|
events_1.default.once('httpsReady', () => (0, misc_1.repeat)(misc_1.HOUR, renewCert));
|
|
136
229
|
// checks if the cert is near expiration date, and if so renews it
|
|
137
230
|
const renewCert = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
138
|
-
const [domain, ...altNames] = acmeDomain.get().split(',');
|
|
139
|
-
if (!acmeRenew.get() || !domain)
|
|
231
|
+
const [domain, ...altNames] = acmeConfig_1.acmeDomain.get().split(',');
|
|
232
|
+
if (!acmeConfig_1.acmeRenew.get() || !domain)
|
|
140
233
|
return;
|
|
141
234
|
const cert = (0, listen_1.getCertObject)();
|
|
142
235
|
if (!cert)
|
|
@@ -148,6 +241,6 @@ const renewCert = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
|
148
241
|
const renewBefore = (validTo.getTime() - validFrom.getTime()) / 3;
|
|
149
242
|
if (now > validFrom && now < validTo && validTo.getTime() - now.getTime() >= renewBefore)
|
|
150
243
|
return console.log("Certificate still good");
|
|
151
|
-
await
|
|
244
|
+
await makeCert(domain, undefined, altNames)
|
|
152
245
|
.catch(e => console.log(exports.acmeRenewError = `Error renewing certificate, expiring ${(0, misc_1.formatDate)(validTo)}: ${String(e.message || e)}`));
|
|
153
246
|
}, { retain: misc_1.HOUR }); // short-lived certificates leave a renew window of just 2 days, and a failed renew is retained as a success here
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.acmeDns = exports.acmeChallenge = exports.acmeRenew = exports.acmeDomain = void 0;
|
|
4
|
+
exports.getAcmeSettings = getAcmeSettings;
|
|
5
|
+
exports.setAcmeSettings = setAcmeSettings;
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const cross_const_1 = require("./cross-const");
|
|
8
|
+
const github_1 = require("./github");
|
|
9
|
+
const acmeDns_1 = require("./acmeDns");
|
|
10
|
+
exports.acmeDomain = (0, config_1.defineConfig)(cross_const_1.CFG.acme_domain, '');
|
|
11
|
+
exports.acmeRenew = (0, config_1.defineConfig)(cross_const_1.CFG.acme_renew, false);
|
|
12
|
+
exports.acmeChallenge = (0, config_1.defineConfig)(cross_const_1.CFG.acme_challenge, 'http-01');
|
|
13
|
+
exports.acmeDns = (0, config_1.defineConfig)(cross_const_1.CFG.acme_dns, []);
|
|
14
|
+
async function getAcmeSettings() {
|
|
15
|
+
return {
|
|
16
|
+
domain: exports.acmeDomain.get(), renew: exports.acmeRenew.get(), challenge: exports.acmeChallenge.get(),
|
|
17
|
+
dns: exports.acmeDns.get(), providers: (0, acmeDns_1.dnsProviderInfo)((await (0, github_1.getProjectInfo)()).acmeDns),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function setAcmeSettings(settings) {
|
|
21
|
+
if (typeof settings.domain !== 'string' || typeof settings.renew !== 'boolean'
|
|
22
|
+
|| !['http-01', 'dns-01'].includes(settings.challenge) || !Array.isArray(settings.dns) || settings.dns.length > 1)
|
|
23
|
+
throw Error("Invalid certificate settings");
|
|
24
|
+
const providers = (0, acmeDns_1.dnsProviderInfo)((await (0, github_1.getProjectInfo)()).acmeDns);
|
|
25
|
+
const configs = settings.dns.map(config => {
|
|
26
|
+
if (!config || typeof config.id !== 'string' || !config.id || !config.credentials || typeof config.credentials !== 'object'
|
|
27
|
+
|| Object.values(config.credentials).some(value => typeof value !== 'string'))
|
|
28
|
+
throw Error("Invalid DNS configuration");
|
|
29
|
+
const previous = exports.acmeDns.get().find(c => c.id === config.id && c.provider === config.provider && c.config_version === config.config_version);
|
|
30
|
+
const provider = providers.find(p => p.id === config.provider && p.config_version === config.config_version);
|
|
31
|
+
// retain unavailable credentials so switching back to HTTP doesn't require discarding them
|
|
32
|
+
if (!provider && previous)
|
|
33
|
+
return config;
|
|
34
|
+
if (!provider || Object.keys(config.credentials).some(key => !(key in provider.fields)))
|
|
35
|
+
throw Error("Invalid DNS configuration; select a supported provider");
|
|
36
|
+
return { id: config.id, provider: config.provider, config_version: config.config_version, credentials: config.credentials };
|
|
37
|
+
});
|
|
38
|
+
await (0, config_1.setConfig)({
|
|
39
|
+
[cross_const_1.CFG.acme_domain]: settings.domain, [cross_const_1.CFG.acme_renew]: settings.renew,
|
|
40
|
+
[cross_const_1.CFG.acme_challenge]: settings.challenge, [cross_const_1.CFG.acme_dns]: configs,
|
|
41
|
+
});
|
|
42
|
+
return getAcmeSettings();
|
|
43
|
+
}
|
package/src/acmeDns.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAcmeDnsProvider = registerAcmeDnsProvider;
|
|
4
|
+
exports.getDnsProviders = getDnsProviders;
|
|
5
|
+
exports.dnsProviderInfo = dnsProviderInfo;
|
|
6
|
+
exports.presentHttpDns = presentHttpDns;
|
|
7
|
+
exports.certificateNames = certificateNames;
|
|
8
|
+
exports.dnsChallengeRecord = dnsChallengeRecord;
|
|
9
|
+
exports.waitForDnsRecord = waitForDnsRecord;
|
|
10
|
+
const node_url_1 = require("node:url");
|
|
11
|
+
const node_net_1 = require("node:net");
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
|
+
const util_http_1 = require("./util-http");
|
|
14
|
+
const promises_1 = require("node:timers/promises");
|
|
15
|
+
const plugins = new Map();
|
|
16
|
+
function registerAcmeDnsProvider(id, provider) {
|
|
17
|
+
if (!id || plugins.has(id))
|
|
18
|
+
throw Error("DNS provider ID already registered");
|
|
19
|
+
if (!Number.isInteger(provider.config_version) || provider.config_version < 1 || typeof provider.present !== 'function')
|
|
20
|
+
throw Error("Invalid DNS provider");
|
|
21
|
+
plugins.set(id, provider);
|
|
22
|
+
return () => { if (plugins.get(id) === provider)
|
|
23
|
+
plugins.delete(id); };
|
|
24
|
+
}
|
|
25
|
+
function getDnsProviders(catalog) {
|
|
26
|
+
const providers = new Map();
|
|
27
|
+
if (!Array.isArray(catalog))
|
|
28
|
+
throw Error("Invalid DNS provider catalog");
|
|
29
|
+
for (const raw of catalog) {
|
|
30
|
+
try {
|
|
31
|
+
if (!raw || typeof raw !== 'object' || !raw.providers || typeof raw.providers !== 'object')
|
|
32
|
+
throw Error("Invalid DNS provider catalog");
|
|
33
|
+
const recipe = raw;
|
|
34
|
+
if (recipe.driver !== 'http-json1')
|
|
35
|
+
continue;
|
|
36
|
+
validateRecipe(recipe);
|
|
37
|
+
for (const [id, entry] of Object.entries(recipe.providers)) {
|
|
38
|
+
try {
|
|
39
|
+
if (!entry || typeof entry.endpoint !== 'string' || new URL(entry.endpoint).protocol !== 'https:')
|
|
40
|
+
throw Error("Unsupported DNS API endpoint");
|
|
41
|
+
if (providers.has(id))
|
|
42
|
+
throw Error("Duplicate DNS provider");
|
|
43
|
+
providers.set(id, {
|
|
44
|
+
label: entry.label, config_version: recipe.config_version, fields: recipe.fields,
|
|
45
|
+
available: entry.enabled !== false, propagation_timeout: recipe.propagation_timeout, help_url: entry.help_url || recipe.help_url,
|
|
46
|
+
present: (record, credentials) => presentHttpDns(recipe, entry.endpoint, record, credentials),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.warn("Skipping DNS provider", id, error instanceof Error ? error.message : String(error));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// a broken recipe must not prevent independent providers from being used
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn("Skipping DNS recipe", error instanceof Error ? error.message : String(error));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const [id, provider] of plugins) {
|
|
60
|
+
if (providers.has(id))
|
|
61
|
+
throw Error("Duplicate DNS provider");
|
|
62
|
+
providers.set(id, { ...provider, available: true });
|
|
63
|
+
}
|
|
64
|
+
return providers;
|
|
65
|
+
}
|
|
66
|
+
function dnsProviderInfo(catalog) {
|
|
67
|
+
return Array.from(getDnsProviders(catalog), ([id, p]) => ({
|
|
68
|
+
id, label: p.label || id, config_version: p.config_version, fields: p.fields, available: p.available, help_url: p.help_url,
|
|
69
|
+
})).sort((a, b) => a.label.localeCompare(b.label));
|
|
70
|
+
}
|
|
71
|
+
function validateRecipe(recipe) {
|
|
72
|
+
if (!Number.isInteger(recipe.config_version) || recipe.config_version < 1 || !recipe.fields || typeof recipe.fields !== 'object' || Array.isArray(recipe.fields)
|
|
73
|
+
|| !recipe.headers || typeof recipe.headers !== 'object' || Array.isArray(recipe.headers)
|
|
74
|
+
|| Object.values(recipe.headers).some(value => typeof value !== 'string')
|
|
75
|
+
|| !Array.isArray(recipe.create) || !recipe.create.length || !Array.isArray(recipe.remove) || !recipe.remove.length
|
|
76
|
+
|| recipe.prepare !== undefined && !Array.isArray(recipe.prepare))
|
|
77
|
+
throw Error("Invalid DNS recipe");
|
|
78
|
+
if (recipe.auth !== undefined && recipe.auth !== 'ovh'
|
|
79
|
+
&& (!recipe.auth || typeof recipe.auth !== 'object' || typeof recipe.auth.basic !== 'string'))
|
|
80
|
+
throw Error("Invalid DNS authentication");
|
|
81
|
+
if (recipe.propagation_timeout !== undefined && (!Number.isInteger(recipe.propagation_timeout) || recipe.propagation_timeout <= 0))
|
|
82
|
+
throw Error("Invalid DNS propagation timeout");
|
|
83
|
+
for (const field of Object.values(recipe.fields))
|
|
84
|
+
if (!field || typeof field !== 'object' || field.label !== undefined && typeof field.label !== 'string'
|
|
85
|
+
|| field.secret !== undefined && typeof field.secret !== 'boolean')
|
|
86
|
+
throw Error("Invalid DNS field");
|
|
87
|
+
for (const step of [...recipe.prepare || [], ...recipe.create, ...recipe.remove])
|
|
88
|
+
if (!step || !['GET', 'POST', 'DELETE'].includes(step.method) || typeof step.path !== 'string'
|
|
89
|
+
|| !step.path.startsWith('/') || step.path.startsWith('//')
|
|
90
|
+
|| step.expect !== undefined && (!Array.isArray(step.expect) || step.expect.length !== 2 || typeof step.expect[0] !== 'string')
|
|
91
|
+
|| step.select !== undefined && (!step.select || typeof step.select !== 'object'
|
|
92
|
+
|| typeof step.select.field !== 'string' || typeof step.select.equals !== 'string')
|
|
93
|
+
|| step.capture !== undefined && (!step.capture || typeof step.capture !== 'object' || Array.isArray(step.capture)
|
|
94
|
+
|| Object.values(step.capture).some(value => typeof value !== 'string')))
|
|
95
|
+
throw Error("Invalid DNS request");
|
|
96
|
+
}
|
|
97
|
+
function property(value, path) {
|
|
98
|
+
for (const key of path ? path.split('.') : []) {
|
|
99
|
+
if (!value || typeof value !== 'object' || !Object.hasOwn(value, key))
|
|
100
|
+
throw Error("Missing DNS API response field");
|
|
101
|
+
value = value[key];
|
|
102
|
+
}
|
|
103
|
+
if (value === undefined)
|
|
104
|
+
throw Error("Missing DNS API response field");
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
function expand(value, variables, url = false) {
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
const whole = /^\{([\w.]+)\}$/.exec(value);
|
|
110
|
+
if (whole && !url)
|
|
111
|
+
return property(variables, whole[1]);
|
|
112
|
+
return value.replace(/\{([\w.]+)\}/g, (_, key) => {
|
|
113
|
+
const v = property(variables, key);
|
|
114
|
+
if (typeof v !== 'string' && typeof v !== 'number')
|
|
115
|
+
throw Error("Invalid DNS request parameter");
|
|
116
|
+
return url ? encodeURIComponent(v) : String(v);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(value))
|
|
120
|
+
return value.map(v => expand(v, variables));
|
|
121
|
+
if (value && typeof value === 'object')
|
|
122
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, expand(v, variables)]));
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
async function presentHttpDns(recipe, endpoint, record, credentials) {
|
|
126
|
+
// retain the recipe and credentials used to create the record until cleanup finishes
|
|
127
|
+
recipe = structuredClone(recipe);
|
|
128
|
+
credentials = { ...credentials };
|
|
129
|
+
const vars = Object.assign(Object.create(null), { credentials, ...record });
|
|
130
|
+
let ovhOffset = 0;
|
|
131
|
+
async function run(steps, authenticate = true) {
|
|
132
|
+
for (const step of steps) {
|
|
133
|
+
const url = new URL(endpoint.replace(/\/$/, '') + expand(step.path, vars, true));
|
|
134
|
+
if (url.origin !== new URL(endpoint).origin || url.username || url.password)
|
|
135
|
+
throw Error("Invalid DNS API URL");
|
|
136
|
+
const body = step.body === undefined ? undefined : JSON.stringify(expand(step.body, vars));
|
|
137
|
+
const headers = { 'content-type': 'application/json', ...expand(recipe.headers, vars) };
|
|
138
|
+
if (authenticate && recipe.auth) {
|
|
139
|
+
if (recipe.auth === 'ovh') {
|
|
140
|
+
// sign the exact bytes sent, using server time to tolerate an incorrect local clock
|
|
141
|
+
const timestamp = Math.floor((Date.now() + ovhOffset) / 1000);
|
|
142
|
+
Object.assign(headers, {
|
|
143
|
+
'X-Ovh-Application': credentials.applicationKey,
|
|
144
|
+
'X-Ovh-Consumer': credentials.consumerKey,
|
|
145
|
+
'X-Ovh-Timestamp': String(timestamp),
|
|
146
|
+
'X-Ovh-Signature': '$1$' + (0, node_crypto_1.createHash)('sha1').update([
|
|
147
|
+
credentials.applicationSecret, credentials.consumerKey, step.method, url.href, body || '', timestamp,
|
|
148
|
+
].join('+')).digest('hex'),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
Object.assign(headers, { Authorization: 'Basic ' + Buffer.from(String(expand(recipe.auth.basic, vars))).toString('base64') });
|
|
153
|
+
}
|
|
154
|
+
// avoid exposing raw response bodies; OVH diagnostic fields are redacted below
|
|
155
|
+
const response = await (0, util_http_1.httpStream)(url.href, {
|
|
156
|
+
method: step.method, headers, body,
|
|
157
|
+
timeout: 20_000, noRedirect: true, httpThrow: false,
|
|
158
|
+
}).catch((error) => {
|
|
159
|
+
throw Error(`DNS API connection failed (${error.code || 'network error'})`);
|
|
160
|
+
});
|
|
161
|
+
let data;
|
|
162
|
+
try {
|
|
163
|
+
const body = await (0, util_http_1.readTextLimited)(response, 1024 * 1024).catch((error) => {
|
|
164
|
+
throw Error(`DNS API response interrupted (${error.code || 'network error'})`);
|
|
165
|
+
});
|
|
166
|
+
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
|
|
167
|
+
let detail = '';
|
|
168
|
+
if (recipe.auth === 'ovh' && !('error' in body)) {
|
|
169
|
+
try {
|
|
170
|
+
const { errorCode, message } = JSON.parse(body.text);
|
|
171
|
+
detail = [errorCode, message].filter(value => typeof value === 'string').join(': ');
|
|
172
|
+
for (const secret of Object.values(credentials))
|
|
173
|
+
if (secret)
|
|
174
|
+
detail = detail.replaceAll(secret, '[redacted]');
|
|
175
|
+
detail = detail.slice(0, 500);
|
|
176
|
+
}
|
|
177
|
+
catch { } // non-JSON errors still report the HTTP status
|
|
178
|
+
}
|
|
179
|
+
throw Error(`DNS API returned HTTP ${response.statusCode || 'unknown'} (${step.method} ${url.hostname} ${step.path})${detail ? ': ' + detail : ''}`);
|
|
180
|
+
}
|
|
181
|
+
if ('error' in body)
|
|
182
|
+
throw Error("DNS API response exceeds 1 MB");
|
|
183
|
+
try {
|
|
184
|
+
data = body.text ? JSON.parse(body.text) : null;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
throw Error("DNS API returned invalid JSON");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
response.destroy();
|
|
192
|
+
}
|
|
193
|
+
if (step.select) {
|
|
194
|
+
if (!Array.isArray(data))
|
|
195
|
+
throw Error("Invalid DNS list response");
|
|
196
|
+
const { field, equals } = step.select;
|
|
197
|
+
const selected = data.filter(item => property(item, field) === expand(equals, vars));
|
|
198
|
+
if (selected.length !== 1)
|
|
199
|
+
throw Error("DNS zone missing or ambiguous");
|
|
200
|
+
data = selected[0];
|
|
201
|
+
}
|
|
202
|
+
if (step.expect && property(data, step.expect[0]) !== step.expect[1])
|
|
203
|
+
throw Error("DNS API rejected the request");
|
|
204
|
+
for (const [key, path] of Object.entries(step.capture || {}))
|
|
205
|
+
vars[key] = property(data, path);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (recipe.auth === 'ovh') {
|
|
209
|
+
await run([{ method: 'GET', path: '/auth/time', capture: { serverTime: '' } }], false);
|
|
210
|
+
if (typeof vars.serverTime !== 'number' || !Number.isFinite(vars.serverTime))
|
|
211
|
+
throw Error("Invalid DNS API server time");
|
|
212
|
+
ovhOffset = vars.serverTime * 1000 - Date.now();
|
|
213
|
+
}
|
|
214
|
+
await run(recipe.prepare || []);
|
|
215
|
+
try {
|
|
216
|
+
await run(recipe.create);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if ('recordId' in vars) {
|
|
220
|
+
try {
|
|
221
|
+
await run(recipe.remove);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
throw Error("DNS creation and cleanup failed; check the challenge TXT record");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
return () => run(recipe.remove);
|
|
230
|
+
}
|
|
231
|
+
function certificateNames(names) {
|
|
232
|
+
const normalized = names.flatMap(name => {
|
|
233
|
+
name = name.trim().replace(/\.$/, '').toLowerCase();
|
|
234
|
+
const wildcard = name.startsWith('*.');
|
|
235
|
+
const host = wildcard ? name.slice(2) : name;
|
|
236
|
+
const ascii = (0, node_net_1.isIP)(host) ? host : (0, node_url_1.domainToASCII)(host);
|
|
237
|
+
if (!ascii || host.includes('*') || !(0, node_net_1.isIP)(host) && (!ascii.includes('.') || ascii.length > 253
|
|
238
|
+
|| ascii.split('.').some(part => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(part)))
|
|
239
|
+
|| wildcard && (0, node_net_1.isIP)(host))
|
|
240
|
+
throw Error("Invalid certificate domain");
|
|
241
|
+
return wildcard ? ['*.' + ascii, ascii] : [ascii];
|
|
242
|
+
});
|
|
243
|
+
if (!normalized.length)
|
|
244
|
+
throw Error("Enter a domain for the certificate");
|
|
245
|
+
return [...new Set(normalized)];
|
|
246
|
+
}
|
|
247
|
+
async function dnsChallengeRecord(domain, value, resolver) {
|
|
248
|
+
let name = '_acme-challenge.' + domain.replace(/^\*\./, '');
|
|
249
|
+
const visited = new Set();
|
|
250
|
+
// follow only the validation name: the website's CNAME is unrelated to DNS-01
|
|
251
|
+
for (;;) {
|
|
252
|
+
if (visited.has(name) || visited.size >= 16)
|
|
253
|
+
throw Error("DNS challenge CNAME loop");
|
|
254
|
+
visited.add(name);
|
|
255
|
+
const aliases = await resolver.resolveCname(name).catch(ignoreMissingDns);
|
|
256
|
+
if (!aliases?.length)
|
|
257
|
+
break;
|
|
258
|
+
name = aliases[0].toLowerCase().replace(/\.$/, '');
|
|
259
|
+
}
|
|
260
|
+
let zone = name;
|
|
261
|
+
for (;;) {
|
|
262
|
+
const soa = await resolver.resolveSoa(zone).catch(ignoreMissingDns);
|
|
263
|
+
if (soa)
|
|
264
|
+
break;
|
|
265
|
+
const dot = zone.indexOf('.');
|
|
266
|
+
if (dot < 0)
|
|
267
|
+
throw Error("Cannot find the authoritative DNS zone");
|
|
268
|
+
zone = zone.slice(dot + 1);
|
|
269
|
+
}
|
|
270
|
+
return { name, zone, relative: name === zone ? '' : name.slice(0, -zone.length - 1), value };
|
|
271
|
+
}
|
|
272
|
+
function ignoreMissingDns(error) {
|
|
273
|
+
if (error.code !== 'ENODATA' && error.code !== 'ENOTFOUND')
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
async function waitForDnsRecord(record, resolver, timeout = 5 * 60_000) {
|
|
277
|
+
const until = Date.now() + timeout;
|
|
278
|
+
do {
|
|
279
|
+
const values = await resolver.resolveTxt(record.name).catch(ignoreMissingDns);
|
|
280
|
+
if (values?.some(parts => parts.join('') === record.value))
|
|
281
|
+
return;
|
|
282
|
+
await (0, promises_1.setTimeout)(Math.min(2000, Math.max(0, until - Date.now())));
|
|
283
|
+
} while (Date.now() < until);
|
|
284
|
+
throw Error("Timed out waiting for the DNS TXT record to propagate");
|
|
285
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const hfs_admin_lang_en_json_1 = __importDefault(require("./hfs-admin-lang-en.json"));
|
|
7
|
+
const hfs_admin_lang_it_json_1 = __importDefault(require("./hfs-admin-lang-it.json"));
|
|
8
|
+
const hfs_admin_lang_zh_json_1 = __importDefault(require("./hfs-admin-lang-zh.json"));
|
|
9
|
+
const hfs_admin_lang_ru_json_1 = __importDefault(require("./hfs-admin-lang-ru.json"));
|
|
10
|
+
const hfs_admin_lang_ro_json_1 = __importDefault(require("./hfs-admin-lang-ro.json"));
|
|
11
|
+
const hfs_admin_lang_sr_json_1 = __importDefault(require("./hfs-admin-lang-sr.json"));
|
|
12
|
+
const hfs_admin_lang_sr_latn_json_1 = __importDefault(require("./hfs-admin-lang-sr-latn.json"));
|
|
13
|
+
const hfs_admin_lang_ko_json_1 = __importDefault(require("./hfs-admin-lang-ko.json"));
|
|
14
|
+
const hfs_admin_lang_ms_json_1 = __importDefault(require("./hfs-admin-lang-ms.json"));
|
|
15
|
+
const hfs_admin_lang_zh_tw_json_1 = __importDefault(require("./hfs-admin-lang-zh-tw.json"));
|
|
16
|
+
const hfs_admin_lang_fr_json_1 = __importDefault(require("./hfs-admin-lang-fr.json"));
|
|
17
|
+
const hfs_admin_lang_pl_json_1 = __importDefault(require("./hfs-admin-lang-pl.json"));
|
|
18
|
+
const hfs_admin_lang_pt_br_json_1 = __importDefault(require("./hfs-admin-lang-pt-br.json"));
|
|
19
|
+
const hfs_admin_lang_vi_json_1 = __importDefault(require("./hfs-admin-lang-vi.json"));
|
|
20
|
+
const hfs_admin_lang_es_json_1 = __importDefault(require("./hfs-admin-lang-es.json"));
|
|
21
|
+
const hfs_admin_lang_nl_json_1 = __importDefault(require("./hfs-admin-lang-nl.json"));
|
|
22
|
+
const hfs_admin_lang_el_json_1 = __importDefault(require("./hfs-admin-lang-el.json"));
|
|
23
|
+
const hfs_admin_lang_de_json_1 = __importDefault(require("./hfs-admin-lang-de.json"));
|
|
24
|
+
const hfs_admin_lang_fi_json_1 = __importDefault(require("./hfs-admin-lang-fi.json"));
|
|
25
|
+
const hfs_admin_lang_hu_json_1 = __importDefault(require("./hfs-admin-lang-hu.json"));
|
|
26
|
+
const hfs_admin_lang_ja_json_1 = __importDefault(require("./hfs-admin-lang-ja.json"));
|
|
27
|
+
const hfs_admin_lang_tr_json_1 = __importDefault(require("./hfs-admin-lang-tr.json"));
|
|
28
|
+
const hfs_admin_lang_th_json_1 = __importDefault(require("./hfs-admin-lang-th.json"));
|
|
29
|
+
const hfs_admin_lang_uk_json_1 = __importDefault(require("./hfs-admin-lang-uk.json"));
|
|
30
|
+
const hfs_admin_lang_ar_json_1 = __importDefault(require("./hfs-admin-lang-ar.json"));
|
|
31
|
+
const hfs_admin_lang_lt_json_1 = __importDefault(require("./hfs-admin-lang-lt.json"));
|
|
32
|
+
const hfs_admin_lang_bg_json_1 = __importDefault(require("./hfs-admin-lang-bg.json"));
|
|
33
|
+
exports.default = { en: hfs_admin_lang_en_json_1.default, it: hfs_admin_lang_it_json_1.default, zh: hfs_admin_lang_zh_json_1.default, ru: hfs_admin_lang_ru_json_1.default, ro: hfs_admin_lang_ro_json_1.default, sr: hfs_admin_lang_sr_json_1.default, 'sr-latn': hfs_admin_lang_sr_latn_json_1.default, ko: hfs_admin_lang_ko_json_1.default, ms: hfs_admin_lang_ms_json_1.default, 'zh-tw': hfs_admin_lang_zh_tw_json_1.default, fr: hfs_admin_lang_fr_json_1.default, pl: hfs_admin_lang_pl_json_1.default, 'pt-br': hfs_admin_lang_pt_br_json_1.default, vi: hfs_admin_lang_vi_json_1.default, es: hfs_admin_lang_es_json_1.default, nl: hfs_admin_lang_nl_json_1.default, el: hfs_admin_lang_el_json_1.default, de: hfs_admin_lang_de_json_1.default, fi: hfs_admin_lang_fi_json_1.default, hu: hfs_admin_lang_hu_json_1.default, ja: hfs_admin_lang_ja_json_1.default, tr: hfs_admin_lang_tr_json_1.default, th: hfs_admin_lang_th_json_1.default, uk: hfs_admin_lang_uk_json_1.default, ar: hfs_admin_lang_ar_json_1.default, lt: hfs_admin_lang_lt_json_1.default, bg: hfs_admin_lang_bg_json_1.default };
|