@tapflowio/relay 0.19.0 → 0.20.1
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 +37 -1
- package/dist/RelayServer.d.ts.map +1 -1
- package/dist/RelayServer.js +121 -7
- package/dist/RelayServer.js.map +1 -1
- package/dist/SessionManager.d.ts.map +1 -1
- package/dist/SessionManager.js +4 -0
- package/dist/SessionManager.js.map +1 -1
- package/dist/api/auth.d.ts +2 -2
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +22 -33
- package/dist/api/auth.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/adminAccount.d.ts +24 -0
- package/dist/lib/adminAccount.d.ts.map +1 -0
- package/dist/lib/adminAccount.js +61 -0
- package/dist/lib/adminAccount.js.map +1 -0
- package/dist/lib/adminBootstrap.d.ts +48 -0
- package/dist/lib/adminBootstrap.d.ts.map +1 -0
- package/dist/lib/adminBootstrap.js +68 -0
- package/dist/lib/adminBootstrap.js.map +1 -0
- package/dist/lib/cert/index.d.ts +1 -1
- package/dist/lib/cert/index.d.ts.map +1 -1
- package/dist/lib/cert/index.js +1 -1
- package/dist/lib/cert/index.js.map +1 -1
- package/dist/lib/cert/parseCert.d.ts +4 -0
- package/dist/lib/cert/parseCert.d.ts.map +1 -1
- package/dist/lib/cert/parseCert.js +68 -0
- package/dist/lib/cert/parseCert.js.map +1 -1
- package/dist/lib/config.d.ts +4 -4
- package/dist/lib/loadEnvFile.d.ts +1 -1
- package/dist/lib/loadEnvFile.d.ts.map +1 -1
- package/dist/lib/loadEnvFile.js +11 -1
- package/dist/lib/loadEnvFile.js.map +1 -1
- package/dist/lib/trailingRequester.d.ts +23 -0
- package/dist/lib/trailingRequester.d.ts.map +1 -0
- package/dist/lib/trailingRequester.js +56 -0
- package/dist/lib/trailingRequester.js.map +1 -0
- package/dist/server.js +26 -2
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/public/assets/{AppCenter-DyP0aqfF.js → AppCenter-DtF_g4F7.js} +5 -5
- package/public/assets/AppCenter-DtF_g4F7.js.br +0 -0
- package/public/assets/{Default-D7ScPE30.js → Default-C_p-ZWS6.js} +1 -1
- package/public/assets/Default-C_p-ZWS6.js.br +0 -0
- package/public/assets/{Invite-UmhV5rAf.js → Invite-DKT4qT6M.js} +1 -1
- package/public/assets/Invite-DKT4qT6M.js.br +0 -0
- package/public/assets/MacResources-D8T_KpPV.js +1 -0
- package/public/assets/MacResources-D8T_KpPV.js.br +0 -0
- package/public/assets/QASession-Daup7RGP.js +140 -0
- package/public/assets/QASession-Daup7RGP.js.br +0 -0
- package/public/assets/{ResetPassword-CAlB6CfP.js → ResetPassword-BnmE9MfY.js} +1 -1
- package/public/assets/ResetPassword-BnmE9MfY.js.br +0 -0
- package/public/assets/{Setup-CPm2-hPJ.js → Setup-I6nFcwNZ.js} +1 -1
- package/public/assets/Setup-I6nFcwNZ.js.br +0 -0
- package/public/assets/{Team-GEIzrM1D.js → Team-JhXhGt1H.js} +2 -2
- package/public/assets/Team-JhXhGt1H.js.br +0 -0
- package/public/assets/{Tokens-DSeBz_CJ.js → Tokens-KAKIDZ0V.js} +1 -1
- package/public/assets/Tokens-KAKIDZ0V.js.br +0 -0
- package/public/assets/{alert-dialog-BsXI9SVV.js → alert-dialog-CLhPOSdB.js} +1 -1
- package/public/assets/alert-dialog-CLhPOSdB.js.br +0 -0
- package/public/assets/{badge-BkdYQFsh.js → badge-CP-xbjo3.js} +1 -1
- package/public/assets/badge-CP-xbjo3.js.br +0 -0
- package/public/assets/{build-format-CXuQt1X2.js → build-format-DO1zN4vW.js} +3 -3
- package/public/assets/build-format-DO1zN4vW.js.br +0 -0
- package/public/assets/{dialog-DuXZpbiF.js → dialog-BGH9MPcH.js} +3 -3
- package/public/assets/dialog-BGH9MPcH.js.br +0 -0
- package/public/assets/index-ClHUQm-F.css +1 -0
- package/public/assets/index-ClHUQm-F.css.br +0 -0
- package/public/assets/index-O-Temo_p.js +198 -0
- package/public/assets/index-O-Temo_p.js.br +0 -0
- package/public/assets/{pencil-iRR275s8.js → pencil-0UYCdq7e.js} +2 -2
- package/public/assets/pencil-0UYCdq7e.js.br +0 -0
- package/public/assets/{table-CHmhGpHG.js → table-4DnBixRm.js} +1 -1
- package/public/assets/table-4DnBixRm.js.br +0 -0
- package/public/assets/{tabs-BnbeuAOe.js → tabs-DwiCx3dV.js} +1 -1
- package/public/assets/tabs-DwiCx3dV.js.br +0 -0
- package/public/assets/{trash-2-QI162w2K.js → trash-2-CAhMuWgg.js} +3 -3
- package/public/assets/trash-2-CAhMuWgg.js.br +0 -0
- package/public/assets/vendor-router-BFibuab1.js +12 -0
- package/public/assets/vendor-router-BFibuab1.js.br +0 -0
- package/public/index.html +3 -3
- package/public/index.html.br +0 -0
- package/public/assets/AppCenter-DyP0aqfF.js.br +0 -0
- package/public/assets/Default-D7ScPE30.js.br +0 -0
- package/public/assets/Invite-UmhV5rAf.js.br +0 -0
- package/public/assets/MacResources-MS4E00w3.js +0 -1
- package/public/assets/MacResources-MS4E00w3.js.br +0 -0
- package/public/assets/QASession-CjgJHDZ3.js +0 -125
- package/public/assets/QASession-CjgJHDZ3.js.br +0 -0
- package/public/assets/ResetPassword-CAlB6CfP.js.br +0 -0
- package/public/assets/Setup-CPm2-hPJ.js.br +0 -0
- package/public/assets/Team-GEIzrM1D.js.br +0 -0
- package/public/assets/Tokens-DSeBz_CJ.js.br +0 -0
- package/public/assets/alert-dialog-BsXI9SVV.js.br +0 -0
- package/public/assets/badge-BkdYQFsh.js.br +0 -0
- package/public/assets/build-format-CXuQt1X2.js.br +0 -0
- package/public/assets/dialog-DuXZpbiF.js.br +0 -0
- package/public/assets/index-Bk2hqaHZ.css +0 -1
- package/public/assets/index-Bk2hqaHZ.css.br +0 -0
- package/public/assets/index-bitr6DIC.js +0 -215
- package/public/assets/index-bitr6DIC.js.br +0 -0
- package/public/assets/pencil-iRR275s8.js.br +0 -0
- package/public/assets/table-CHmhGpHG.js.br +0 -0
- package/public/assets/tabs-BnbeuAOe.js.br +0 -0
- package/public/assets/trash-2-QI162w2K.js.br +0 -0
- package/public/assets/vendor-router-ChFy8CZ1.js +0 -12
- package/public/assets/vendor-router-ChFy8CZ1.js.br +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminAccount.d.ts","sourceRoot":"","sources":["../../src/lib/adminAccount.ts"],"names":[],"mappings":"AAgBA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAQxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAGvC;AAED,0GAA0G;AAC1G,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,gBAAgB,GAAG,oBAAoB,CAAA;AAE9E;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAOrF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { getDb } from '../db.js';
|
|
3
|
+
// The first-owner rules, and the password hashing they need.
|
|
4
|
+
//
|
|
5
|
+
// **Why this is in `lib/` and not beside the HTTP handlers.** Two callers need it: `api/auth.ts`
|
|
6
|
+
// serves `POST /api/v1/auth/init`, and `lib/adminBootstrap.ts` creates the first account at boot for
|
|
7
|
+
// a container, which cannot reach that endpoint at all — it requires a local client and a container
|
|
8
|
+
// is always behind its bridge gateway. A `lib/` module importing `api/` is backwards here, and doing
|
|
9
|
+
// it made `isInitialized` read as `undefined` depending on which module the test runner loaded
|
|
10
|
+
// first: green on its own file, nine failures in the full suite.
|
|
11
|
+
function hashPassword(password, salt) {
|
|
12
|
+
return crypto.pbkdf2Sync(password, salt, 100_000, 64, 'sha512').toString('hex');
|
|
13
|
+
}
|
|
14
|
+
export function makePasswordHash(password) {
|
|
15
|
+
const salt = crypto.randomBytes(16).toString('hex');
|
|
16
|
+
const hash = hashPassword(password, salt);
|
|
17
|
+
return `${salt}:${hash}`;
|
|
18
|
+
}
|
|
19
|
+
export function verifyPassword(password, stored) {
|
|
20
|
+
const [salt, hash] = stored.split(':');
|
|
21
|
+
if (!salt || !hash)
|
|
22
|
+
return false;
|
|
23
|
+
const computed = Buffer.from(hashPassword(password, salt), 'hex');
|
|
24
|
+
const expected = Buffer.from(hash, 'hex');
|
|
25
|
+
// 저장 해시 포맷 손상 시 길이 불일치로 timingSafeEqual이 RangeError → 안전 실패로 처리.
|
|
26
|
+
if (computed.length !== expected.length)
|
|
27
|
+
return false;
|
|
28
|
+
return crypto.timingSafeEqual(computed, expected);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Does this install have an owner yet?
|
|
32
|
+
*
|
|
33
|
+
* Shared so the two bootstrap paths cannot disagree about what "already initialized" means.
|
|
34
|
+
*
|
|
35
|
+
* They are not identical in every respect: the boot path trims `TAPFLOW_ADMIN_EMAIL` and the HTTP
|
|
36
|
+
* one stores `body.email` as sent, so the same address with surrounding whitespace produces two
|
|
37
|
+
* different rows and the HTTP one never matches `handleLogin`'s `WHERE email = ?`. Normalising
|
|
38
|
+
* inside this function would change what a shipped endpoint stores, so it is a decision rather than
|
|
39
|
+
* a fix here — tracked separately.
|
|
40
|
+
*/
|
|
41
|
+
export function isInitialized() {
|
|
42
|
+
const { n } = getDb().prepare('SELECT COUNT(*) as n FROM users').get();
|
|
43
|
+
return n > 0;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create the first Admin.
|
|
47
|
+
*
|
|
48
|
+
* Does **not** check whether an owner already exists — callers do, because they answer that case
|
|
49
|
+
* differently: 403 over HTTP, a silent no-op on every restart at boot.
|
|
50
|
+
*/
|
|
51
|
+
export function createAdminAccount(email, password) {
|
|
52
|
+
if (!email || !password)
|
|
53
|
+
return 'missing-fields';
|
|
54
|
+
if (password.length < 8)
|
|
55
|
+
return 'password-too-short';
|
|
56
|
+
getDb()
|
|
57
|
+
.prepare('INSERT INTO users (email, display_name, role, password_hash) VALUES (?, ?, ?, ?)')
|
|
58
|
+
.run(email, 'Admin', 'Admin', makePasswordHash(password));
|
|
59
|
+
return 'ok';
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=adminAccount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminAccount.js","sourceRoot":"","sources":["../../src/lib/adminAccount.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAEhC,6DAA6D;AAC7D,EAAE;AACF,iGAAiG;AACjG,qGAAqG;AACrG,oGAAoG;AACpG,qGAAqG;AACrG,+FAA+F;AAC/F,iEAAiE;AAEjE,SAAS,YAAY,CAAC,QAAgB,EAAE,IAAY;IAClD,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AACjF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnD,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACzC,OAAO,GAAG,IAAI,IAAI,IAAI,EAAE,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,MAAc;IAC7D,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACtC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;IACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzC,iEAAiE;IACjE,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACrD,OAAO,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;AACnD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAAmB,CAAA;IACvF,OAAO,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAKD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,QAAgB;IAChE,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAO,gBAAgB,CAAA;IAChD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,oBAAoB,CAAA;IACpD,KAAK,EAAE;SACJ,OAAO,CAAC,kFAAkF,CAAC;SAC3F,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC3D,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create the first Admin from the environment, once, at boot.
|
|
3
|
+
*
|
|
4
|
+
* **Why this exists at all.** `POST /api/v1/auth/init` requires `resolveClient(req).isLocal`, which
|
|
5
|
+
* stops a public instance being claimed by a stranger between first boot and the owner setting a
|
|
6
|
+
* password. A container is always behind its bridge gateway, so that check can never pass there —
|
|
7
|
+
* measured against the published image: the call answers 403 from the host's own browser and from
|
|
8
|
+
* the LAN alike. The error text points at `tapflow admin init`, but the image is relay-only by
|
|
9
|
+
* design (`pnpm deploy --filter @tapflowio/relay`) and carries no CLI. A Docker install therefore
|
|
10
|
+
* had no way to make its first account at all.
|
|
11
|
+
*
|
|
12
|
+
* This path does not go through HTTP, so the `isLocal` gate is not weakened — it is simply not on
|
|
13
|
+
* this road. Whoever can set the container's environment already controls the install.
|
|
14
|
+
*
|
|
15
|
+
* **Idempotent and silent when there is nothing to do.** It runs on every boot, and an install that
|
|
16
|
+
* already has an owner must not accumulate a log line per restart, nor have its account touched.
|
|
17
|
+
*/
|
|
18
|
+
export interface BootstrapLogger {
|
|
19
|
+
info(message: string): void;
|
|
20
|
+
warn(message: string): void;
|
|
21
|
+
error(message: string): void;
|
|
22
|
+
}
|
|
23
|
+
export interface BootstrapEnv {
|
|
24
|
+
TAPFLOW_ADMIN_EMAIL?: string;
|
|
25
|
+
TAPFLOW_ADMIN_PASSWORD?: string;
|
|
26
|
+
}
|
|
27
|
+
/** Thrown when the environment asks for a first account and cannot get one. Fatal at boot. */
|
|
28
|
+
export declare class AdminBootstrapError extends Error {
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* **Misconfiguration refuses to boot, and the first draft had this backwards.**
|
|
32
|
+
*
|
|
33
|
+
* That draft warned and carried on, reasoning that these variables are first-run-only and a typo in
|
|
34
|
+
* them must not keep a running relay down. Review pointed out that the reasoning describes a state
|
|
35
|
+
* this function cannot reach: `isInitialized()` returns above every one of these branches, so an
|
|
36
|
+
* install that already has an owner never gets here whatever its environment says. The only state
|
|
37
|
+
* that does reach them is *the operator asked for a first account, there is none, and the settings
|
|
38
|
+
* are wrong* — and serving anyway leaves the install ownerless and claimable by anything that can
|
|
39
|
+
* reach loopback, indefinitely, behind one line of boot spam.
|
|
40
|
+
*
|
|
41
|
+
* Refusing costs nothing, which is what makes the trade one-sided: an ownerless relay cannot be
|
|
42
|
+
* logged into by anyone legitimate either, so there is no working service to lose, and a container
|
|
43
|
+
* that will not stay up is far louder than a warning.
|
|
44
|
+
*
|
|
45
|
+
* Neither the password nor its length is ever written to the log.
|
|
46
|
+
*/
|
|
47
|
+
export declare function bootstrapAdminFromEnv(env: BootstrapEnv, logger: BootstrapLogger): void;
|
|
48
|
+
//# sourceMappingURL=adminBootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminBootstrap.d.ts","sourceRoot":"","sources":["../../src/lib/adminBootstrap.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,8FAA8F;AAC9F,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAoDtF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isInitialized, createAdminAccount } from './adminAccount.js';
|
|
2
|
+
/** Thrown when the environment asks for a first account and cannot get one. Fatal at boot. */
|
|
3
|
+
export class AdminBootstrapError extends Error {
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* **Misconfiguration refuses to boot, and the first draft had this backwards.**
|
|
7
|
+
*
|
|
8
|
+
* That draft warned and carried on, reasoning that these variables are first-run-only and a typo in
|
|
9
|
+
* them must not keep a running relay down. Review pointed out that the reasoning describes a state
|
|
10
|
+
* this function cannot reach: `isInitialized()` returns above every one of these branches, so an
|
|
11
|
+
* install that already has an owner never gets here whatever its environment says. The only state
|
|
12
|
+
* that does reach them is *the operator asked for a first account, there is none, and the settings
|
|
13
|
+
* are wrong* — and serving anyway leaves the install ownerless and claimable by anything that can
|
|
14
|
+
* reach loopback, indefinitely, behind one line of boot spam.
|
|
15
|
+
*
|
|
16
|
+
* Refusing costs nothing, which is what makes the trade one-sided: an ownerless relay cannot be
|
|
17
|
+
* logged into by anyone legitimate either, so there is no working service to lose, and a container
|
|
18
|
+
* that will not stay up is far louder than a warning.
|
|
19
|
+
*
|
|
20
|
+
* Neither the password nor its length is ever written to the log.
|
|
21
|
+
*/
|
|
22
|
+
export function bootstrapAdminFromEnv(env, logger) {
|
|
23
|
+
const email = env.TAPFLOW_ADMIN_EMAIL?.trim();
|
|
24
|
+
const password = env.TAPFLOW_ADMIN_PASSWORD;
|
|
25
|
+
// Neither set is the ordinary case — every install that is not a fresh container.
|
|
26
|
+
if (!email && !password)
|
|
27
|
+
return;
|
|
28
|
+
if (isInitialized())
|
|
29
|
+
return;
|
|
30
|
+
const fail = (message) => {
|
|
31
|
+
logger.error(message);
|
|
32
|
+
throw new AdminBootstrapError(message);
|
|
33
|
+
};
|
|
34
|
+
if (!email || !password) {
|
|
35
|
+
const missing = email ? 'TAPFLOW_ADMIN_PASSWORD' : 'TAPFLOW_ADMIN_EMAIL';
|
|
36
|
+
// `return` so the compiler narrows both to `string` below — `fail` returning `never` is not
|
|
37
|
+
// enough on its own when it is called as a statement.
|
|
38
|
+
return fail(`${missing} is not set, so the first admin account could not be created. ` +
|
|
39
|
+
'Set both TAPFLOW_ADMIN_EMAIL and TAPFLOW_ADMIN_PASSWORD, or neither.');
|
|
40
|
+
}
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = createAdminAccount(email, password);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
// **A second container on the same volume is a race, not a misconfiguration.** `users.email` is
|
|
47
|
+
// `UNIQUE`, so two replicas booting together have one of them lose the INSERT. The install ends
|
|
48
|
+
// up with an owner either way, which is the outcome that was asked for — carry on rather than
|
|
49
|
+
// crash-looping whichever replica arrived second.
|
|
50
|
+
if (isInitialized()) {
|
|
51
|
+
logger.warn('Another process created the first admin account while this one was starting.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
switch (result) {
|
|
57
|
+
case 'password-too-short':
|
|
58
|
+
return fail('TAPFLOW_ADMIN_PASSWORD is shorter than 8 characters, so the first admin account could not ' +
|
|
59
|
+
'be created. Set a longer one.');
|
|
60
|
+
case 'missing-fields':
|
|
61
|
+
// Unreachable: both are non-empty by the checks above. Handled so a later change to
|
|
62
|
+
// `createAdminAccount` cannot silently fall through to "created".
|
|
63
|
+
return fail('The first admin account could not be created: the configured email or password was empty.');
|
|
64
|
+
case 'ok':
|
|
65
|
+
logger.info(`Created the first admin account for ${email} from the environment.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=adminBootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminBootstrap.js","sourceRoot":"","sources":["../../src/lib/adminBootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AA8BrE,8FAA8F;AAC9F,MAAM,OAAO,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAiB,EAAE,MAAuB;IAC9E,MAAM,KAAK,GAAG,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,CAAA;IAC7C,MAAM,QAAQ,GAAG,GAAG,CAAC,sBAAsB,CAAA;IAE3C,kFAAkF;IAClF,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ;QAAE,OAAM;IAE/B,IAAI,aAAa,EAAE;QAAE,OAAM;IAE3B,MAAM,IAAI,GAAG,CAAC,OAAe,EAAS,EAAE;QACtC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrB,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAA;IACxC,CAAC,CAAA;IAED,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,qBAAqB,CAAA;QACxE,4FAA4F;QAC5F,sDAAsD;QACtD,OAAO,IAAI,CACT,GAAG,OAAO,gEAAgE;YAC1E,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,IAAI,MAAM,CAAA;IACV,IAAI,CAAC;QACH,MAAM,GAAG,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gGAAgG;QAChG,gGAAgG;QAChG,8FAA8F;QAC9F,kDAAkD;QAClD,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAA;YAC3F,OAAM;QACR,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,oBAAoB;YACvB,OAAO,IAAI,CACT,4FAA4F;gBAC5F,+BAA+B,CAChC,CAAA;QACH,KAAK,gBAAgB;YACnB,oFAAoF;YACpF,kEAAkE;YAClE,OAAO,IAAI,CAAC,2FAA2F,CAAC,CAAA;QAC1G,KAAK,IAAI;YACP,MAAM,CAAC,IAAI,CAAC,uCAAuC,KAAK,wBAAwB,CAAC,CAAA;IACrF,CAAC;AACH,CAAC"}
|
package/dist/lib/cert/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { AcmeClientIssuer } from './AcmeClientIssuer.js';
|
|
|
10
10
|
export type { AcmeClientIssuerOptions } from './AcmeClientIssuer.js';
|
|
11
11
|
export { ImportCertProvider } from './ImportCertProvider.js';
|
|
12
12
|
export type { ImportCertProviderOptions } from './ImportCertProvider.js';
|
|
13
|
-
export { parseCertNotAfter } from './parseCert.js';
|
|
13
|
+
export { parseCertNotAfter, resolveRelayDisplayHost } from './parseCert.js';
|
|
14
14
|
export { DiskCertStore } from './DiskCertStore.js';
|
|
15
15
|
export { startCertRenewal, renewalTick } from './renewal.js';
|
|
16
16
|
export type { CertRenewalOptions } from './renewal.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACjF,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACxF,YAAY,EAAE,4BAA4B,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACzF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC5E,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACjF,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACxF,YAAY,EAAE,4BAA4B,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACzF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC5E,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC5E,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA"}
|
package/dist/lib/cert/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { VercelDnsProvider, vercelDnsFromEnv } from './VercelDnsProvider.js';
|
|
|
3
3
|
export { AcmeCertProvider, InMemoryCertStore } from './AcmeCertProvider.js';
|
|
4
4
|
export { AcmeClientIssuer } from './AcmeClientIssuer.js';
|
|
5
5
|
export { ImportCertProvider } from './ImportCertProvider.js';
|
|
6
|
-
export { parseCertNotAfter } from './parseCert.js';
|
|
6
|
+
export { parseCertNotAfter, resolveRelayDisplayHost } from './parseCert.js';
|
|
7
7
|
export { DiskCertStore } from './DiskCertStore.js';
|
|
8
8
|
export { startCertRenewal, renewalTick } from './renewal.js';
|
|
9
9
|
export { startAddressPublisher, detectLanIPv4 } from './addressPublisher.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/cert/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAExF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAE5E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/cert/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAExF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAE5E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAC3E,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE5D,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE5E,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { TlsConfig } from './createCertProvider.js';
|
|
1
2
|
/** PEM 인증서에서 notAfter(만료 시각)를 파싱한다. 잘못된 PEM이면 throw. */
|
|
2
3
|
export declare function parseCertNotAfter(certPem: string): Date;
|
|
4
|
+
/** Return one concrete DNS host the certificate can identify, or localhost when it has none. */
|
|
5
|
+
export declare function parseCertDisplayHost(certPem: string): string;
|
|
6
|
+
export declare function resolveRelayDisplayHost(tls: TlsConfig | null, certPem?: string, warn?: (message: string) => void): string;
|
|
3
7
|
//# sourceMappingURL=parseCert.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseCert.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/parseCert.ts"],"names":[],"mappings":"AAEA,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEvD"}
|
|
1
|
+
{"version":3,"file":"parseCert.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/parseCert.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AA0BxD,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEvD;AAiCD,gGAAgG;AAChG,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,SAAS,GAAG,IAAI,EACrB,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC/B,MAAM,CAQR"}
|
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
import { X509Certificate } from 'crypto';
|
|
2
|
+
import { isIP } from 'net';
|
|
3
|
+
const FALLBACK_HOST = 'localhost';
|
|
4
|
+
const IMPORT_CERT_FALLBACK_WARNING = 'Imported certificate has DNS SANs, but none can be advertised to teammates; using localhost. ' +
|
|
5
|
+
'Add a concrete non-localhost DNS SAN to advertise its URL.';
|
|
6
|
+
function concreteDnsHost(value) {
|
|
7
|
+
if (!value || value !== value.trim() || isIP(value) !== 0 || value.length > 253)
|
|
8
|
+
return null;
|
|
9
|
+
const labels = value.split('.');
|
|
10
|
+
if (labels.some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label)))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
// WHATWG URLs accept legacy IPv4 spellings such as 127.1 and 0x7f000001.
|
|
14
|
+
// Do not advertise them as DNS names: browsers normalize them to an IP address.
|
|
15
|
+
if (isIP(new URL(`https://${value}`).hostname) !== 0)
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
2
23
|
/** PEM 인증서에서 notAfter(만료 시각)를 파싱한다. 잘못된 PEM이면 throw. */
|
|
3
24
|
export function parseCertNotAfter(certPem) {
|
|
4
25
|
return new Date(new X509Certificate(certPem).validTo);
|
|
5
26
|
}
|
|
27
|
+
function parseCertDisplayHostResult(certPem) {
|
|
28
|
+
try {
|
|
29
|
+
const cert = new X509Certificate(certPem);
|
|
30
|
+
if (cert.subjectAltName !== undefined) {
|
|
31
|
+
let hasDnsSan = false;
|
|
32
|
+
// Node JSON-quotes and escapes ambiguous SAN values, including commas. A real DNS host uses
|
|
33
|
+
// none of those characters, so strict label validation can safely reject the whole value.
|
|
34
|
+
for (const entry of cert.subjectAltName.split(', ')) {
|
|
35
|
+
if (!entry.startsWith('DNS:'))
|
|
36
|
+
continue;
|
|
37
|
+
hasDnsSan = true;
|
|
38
|
+
const host = concreteDnsHost(entry.slice(4));
|
|
39
|
+
if (host && host.toLowerCase() !== FALLBACK_HOST) {
|
|
40
|
+
return { host, warnAboutFallback: false };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { host: FALLBACK_HOST, warnAboutFallback: hasDnsSan };
|
|
44
|
+
}
|
|
45
|
+
const commonName = cert.subject
|
|
46
|
+
.split('\n')
|
|
47
|
+
.find((entry) => entry.startsWith('CN='))
|
|
48
|
+
?.slice(3);
|
|
49
|
+
return {
|
|
50
|
+
host: commonName ? concreteDnsHost(commonName) ?? FALLBACK_HOST : FALLBACK_HOST,
|
|
51
|
+
warnAboutFallback: false,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { host: FALLBACK_HOST, warnAboutFallback: false };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Return one concrete DNS host the certificate can identify, or localhost when it has none. */
|
|
59
|
+
export function parseCertDisplayHost(certPem) {
|
|
60
|
+
return parseCertDisplayHostResult(certPem).host;
|
|
61
|
+
}
|
|
62
|
+
export function resolveRelayDisplayHost(tls, certPem, warn) {
|
|
63
|
+
if (!tls)
|
|
64
|
+
return FALLBACK_HOST;
|
|
65
|
+
if (tls.mode === 'byo-api-token')
|
|
66
|
+
return tls.domain;
|
|
67
|
+
if (!certPem)
|
|
68
|
+
return FALLBACK_HOST;
|
|
69
|
+
const result = parseCertDisplayHostResult(certPem);
|
|
70
|
+
if (result.warnAboutFallback)
|
|
71
|
+
warn?.(IMPORT_CERT_FALLBACK_WARNING);
|
|
72
|
+
return result.host;
|
|
73
|
+
}
|
|
6
74
|
//# sourceMappingURL=parseCert.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseCert.js","sourceRoot":"","sources":["../../../src/lib/cert/parseCert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"parseCert.js","sourceRoot":"","sources":["../../../src/lib/cert/parseCert.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -75,15 +75,15 @@ export declare const config: {
|
|
|
75
75
|
publicUrl?: string | undefined;
|
|
76
76
|
} | null;
|
|
77
77
|
tls: {
|
|
78
|
+
mode: "import-cert";
|
|
79
|
+
certPath: string;
|
|
80
|
+
keyPath: string;
|
|
81
|
+
} | {
|
|
78
82
|
mode: "byo-api-token";
|
|
79
83
|
domain: string;
|
|
80
84
|
dnsProvider: string;
|
|
81
85
|
publishAddress?: boolean | undefined;
|
|
82
86
|
address?: string | undefined;
|
|
83
|
-
} | {
|
|
84
|
-
mode: "import-cert";
|
|
85
|
-
certPath: string;
|
|
86
|
-
keyPath: string;
|
|
87
87
|
} | null;
|
|
88
88
|
smtp: {
|
|
89
89
|
host: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function loadDataDirEnv(dataDir: string): string | null;
|
|
1
|
+
export declare function loadDataDirEnv(dataDir: string, warn?: (m: string) => void): string | null;
|
|
2
2
|
//# sourceMappingURL=loadEnvFile.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadEnvFile.d.ts","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"loadEnvFile.d.ts","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAA6B,GAAG,MAAM,GAAG,IAAI,CAoBjH"}
|
package/dist/lib/loadEnvFile.js
CHANGED
|
@@ -3,11 +3,21 @@ import path from 'path';
|
|
|
3
3
|
// gitignore된 <dataDir>/.env 에서 자격 증명(JWT_SECRET·SMTP·DNS/ACME 토큰 등)을 로드한다.
|
|
4
4
|
// config.load()가 secret을 읽기 전에 호출되어 .env가 모든 비밀의 기본 경로가 된다(#287에서 시작).
|
|
5
5
|
// process.loadEnvFile 은 기존 process.env 값을 안 덮으므로 ambient(셸) 가 항상 우선.
|
|
6
|
-
export function loadDataDirEnv(dataDir) {
|
|
6
|
+
export function loadDataDirEnv(dataDir, warn = (m) => console.warn(m)) {
|
|
7
7
|
const envPath = path.join(dataDir, '.env');
|
|
8
8
|
if (!fs.existsSync(envPath))
|
|
9
9
|
return null;
|
|
10
10
|
try {
|
|
11
|
+
// **Everything else that writes a secret into dataDir sets 0600 and this file trusted its
|
|
12
|
+
// caller.** `config.ts` writes `jwt-secret` with `{ mode: 0o600 }` and chmods it; `tapflow init`
|
|
13
|
+
// does the same for this file. The relay-only image has no CLI, so a container operator writes
|
|
14
|
+
// it by hand under their own umask — 0644 — and the admin bootstrap now tells them to put a
|
|
15
|
+
// plaintext password in it. Warn rather than refuse: an unreadable-by-others file is the goal,
|
|
16
|
+
// but a relay that will not start because of a permission bit helps nobody.
|
|
17
|
+
const mode = fs.statSync(envPath).mode & 0o777;
|
|
18
|
+
if (mode & 0o077) {
|
|
19
|
+
warn(`${envPath} is readable by other users (mode ${mode.toString(8)}). Run: chmod 600 ${envPath}`);
|
|
20
|
+
}
|
|
11
21
|
process.loadEnvFile(envPath);
|
|
12
22
|
return envPath;
|
|
13
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadEnvFile.js","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,2EAA2E;AAC3E,uEAAuE;AACvE,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,OAAe;
|
|
1
|
+
{"version":3,"file":"loadEnvFile.js","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,2EAA2E;AAC3E,uEAAuE;AACvE,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,OAA4B,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAChG,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAA;IACxC,IAAI,CAAC;QACH,0FAA0F;QAC1F,iGAAiG;QACjG,+FAA+F;QAC/F,4FAA4F;QAC5F,+FAA+F;QAC/F,4EAA4E;QAC5E,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,KAAK,CAAA;QAC9C,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,OAAO,qCAAqC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAA;QACrG,CAAC;QACD,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;QAC5B,OAAO,OAAO,CAAA;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;QAChD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** A clock and timer boundary for a trailing-edge requester. */
|
|
2
|
+
export interface TimerScheduler<Timer> {
|
|
3
|
+
now(): number;
|
|
4
|
+
schedule(callback: () => void, delayMs: number): Timer;
|
|
5
|
+
cancel(timer: Timer): void;
|
|
6
|
+
}
|
|
7
|
+
/** A callable requester whose delayed edge can be cancelled when its owner goes away. */
|
|
8
|
+
export type TrailingRequester = (() => void) & {
|
|
9
|
+
dispose(): void;
|
|
10
|
+
};
|
|
11
|
+
/** The production scheduler. `unref` keeps a pending request from keeping the relay process alive. */
|
|
12
|
+
export declare const systemTimerScheduler: TimerScheduler<ReturnType<typeof setTimeout>>;
|
|
13
|
+
/**
|
|
14
|
+
* Calls `fire` immediately at the leading edge, then coalesces calls inside `windowMs` onto one
|
|
15
|
+
* trailing edge. The scheduler is injected so the timing policy can be tested without faking the
|
|
16
|
+
* relay's WebSocket or heartbeat timers.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createTrailingRequester<Timer>(options: {
|
|
19
|
+
scheduler: TimerScheduler<Timer>;
|
|
20
|
+
windowMs: number;
|
|
21
|
+
fire: () => void;
|
|
22
|
+
}): TrailingRequester;
|
|
23
|
+
//# sourceMappingURL=trailingRequester.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trailingRequester.d.ts","sourceRoot":"","sources":["../../src/lib/trailingRequester.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC,GAAG,IAAI,MAAM,CAAA;IACb,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAAA;IACtD,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CAC3B;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,CAAA;AAElE,sGAAsG;AACtG,eAAO,MAAM,oBAAoB,EAAE,cAAc,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAQ9E,CAAA;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE;IACtD,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB,GAAG,iBAAiB,CA4CpB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** The production scheduler. `unref` keeps a pending request from keeping the relay process alive. */
|
|
2
|
+
export const systemTimerScheduler = {
|
|
3
|
+
now: () => Date.now(),
|
|
4
|
+
schedule(callback, delayMs) {
|
|
5
|
+
const timer = setTimeout(callback, delayMs);
|
|
6
|
+
timer.unref();
|
|
7
|
+
return timer;
|
|
8
|
+
},
|
|
9
|
+
cancel(timer) { clearTimeout(timer); },
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Calls `fire` immediately at the leading edge, then coalesces calls inside `windowMs` onto one
|
|
13
|
+
* trailing edge. The scheduler is injected so the timing policy can be tested without faking the
|
|
14
|
+
* relay's WebSocket or heartbeat timers.
|
|
15
|
+
*/
|
|
16
|
+
export function createTrailingRequester(options) {
|
|
17
|
+
const { scheduler, windowMs, fire } = options;
|
|
18
|
+
let lastAt = null;
|
|
19
|
+
let pending = null;
|
|
20
|
+
const fireNow = () => {
|
|
21
|
+
lastAt = scheduler.now();
|
|
22
|
+
fire();
|
|
23
|
+
};
|
|
24
|
+
const requester = (() => {
|
|
25
|
+
if (lastAt === null) {
|
|
26
|
+
fireNow();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const wait = windowMs - (scheduler.now() - lastAt);
|
|
30
|
+
if (wait <= 0) {
|
|
31
|
+
// A timer that became due while another callback was running must not fire after this leading
|
|
32
|
+
// edge as well. Node normally runs due timers first, but that ordering is not a second budget.
|
|
33
|
+
if (pending !== null) {
|
|
34
|
+
scheduler.cancel(pending);
|
|
35
|
+
pending = null;
|
|
36
|
+
}
|
|
37
|
+
fireNow();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Already coalesced. A second timer here would be the double send the window is for.
|
|
41
|
+
if (pending !== null)
|
|
42
|
+
return;
|
|
43
|
+
pending = scheduler.schedule(() => {
|
|
44
|
+
pending = null;
|
|
45
|
+
fireNow();
|
|
46
|
+
}, wait);
|
|
47
|
+
});
|
|
48
|
+
requester.dispose = () => {
|
|
49
|
+
if (pending !== null) {
|
|
50
|
+
scheduler.cancel(pending);
|
|
51
|
+
pending = null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return requester;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=trailingRequester.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trailingRequester.js","sourceRoot":"","sources":["../../src/lib/trailingRequester.ts"],"names":[],"mappings":"AAUA,sGAAsG;AACtG,MAAM,CAAC,MAAM,oBAAoB,GAAkD;IACjF,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO;QACxB,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC3C,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,OAAO,KAAK,CAAA;IACd,CAAC;IACD,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC;CACtC,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAQ,OAI9C;IACC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IAC7C,IAAI,MAAM,GAAkB,IAAI,CAAA;IAChC,IAAI,OAAO,GAAiB,IAAI,CAAA;IAEhC,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAA;QACxB,IAAI,EAAE,CAAA;IACR,CAAC,CAAA;IAED,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE;QACtB,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,EAAE,CAAA;YACT,OAAM;QACR,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAA;QAClD,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACd,8FAA8F;YAC9F,+FAA+F;YAC/F,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;gBACzB,OAAO,GAAG,IAAI,CAAA;YAChB,CAAC;YACD,OAAO,EAAE,CAAA;YACT,OAAM;QACR,CAAC;QAED,qFAAqF;QACrF,IAAI,OAAO,KAAK,IAAI;YAAE,OAAM;QAC5B,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE;YAChC,OAAO,GAAG,IAAI,CAAA;YACd,OAAO,EAAE,CAAA;QACX,CAAC,EAAE,IAAI,CAAC,CAAA;IACV,CAAC,CAAsB,CAAA;IAEvB,SAAS,CAAC,OAAO,GAAG,GAAG,EAAE;QACvB,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACzB,OAAO,GAAG,IAAI,CAAA;QAChB,CAAC;IACH,CAAC,CAAA;IAED,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,8 @@ import { initDb } from './db.js';
|
|
|
3
3
|
import { RelayServer } from './RelayServer.js';
|
|
4
4
|
import { config, loadedEnvPath } from './lib/config.js';
|
|
5
5
|
import { buildCorsOrigins, proxyWithoutPublicUrlWarning } from './lib/proxyConfig.js';
|
|
6
|
-
import {
|
|
6
|
+
import { bootstrapAdminFromEnv, AdminBootstrapError } from './lib/adminBootstrap.js';
|
|
7
|
+
import { createCertProvider, resolveRelayDisplayHost } from './lib/cert/index.js';
|
|
7
8
|
import { startTlsBackgroundTasks } from './lib/tlsTasks.js';
|
|
8
9
|
import { createLogger } from '@tapflowio/agent-core';
|
|
9
10
|
const logger = createLogger('relay');
|
|
@@ -14,6 +15,27 @@ if (loadedEnvPath)
|
|
|
14
15
|
const dbPath = path.join(dataDir, 'tapflow.db');
|
|
15
16
|
const uploadsDir = path.join(dataDir, 'uploads');
|
|
16
17
|
initDb(dbPath);
|
|
18
|
+
// After the schema exists and before anything is served: a container cannot reach the HTTP
|
|
19
|
+
// bootstrap, because that one requires a local client and a container is always behind its bridge.
|
|
20
|
+
//
|
|
21
|
+
// **Fatal on failure.** The only way to get here with a problem is "an admin was asked for, there is
|
|
22
|
+
// none, and the settings are wrong" — serving that would leave the install ownerless and claimable
|
|
23
|
+
// from loopback. See `lib/adminBootstrap.ts`.
|
|
24
|
+
try {
|
|
25
|
+
bootstrapAdminFromEnv(process.env, logger);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
// The reason is already logged where it was decided; re-logging it here printed every refusal
|
|
29
|
+
// twice. Anything that is not a refusal has no message of its own yet, so that one is logged.
|
|
30
|
+
if (!(err instanceof AdminBootstrapError))
|
|
31
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
// **The password does not outlive its use.** It is read once here, and every `spawnSync` in
|
|
35
|
+
// `api/builds.ts` — twelve of them, over user-uploaded archives — inherits `process.env` without
|
|
36
|
+
// passing one explicitly. Nothing reads these names again; `<dataDir>/.env` is re-read each boot.
|
|
37
|
+
delete process.env.TAPFLOW_ADMIN_PASSWORD;
|
|
38
|
+
delete process.env.TAPFLOW_ADMIN_EMAIL;
|
|
17
39
|
const corsOrigins = buildCorsOrigins(config, port);
|
|
18
40
|
const proxyWarning = proxyWithoutPublicUrlWarning(config);
|
|
19
41
|
if (proxyWarning)
|
|
@@ -21,10 +43,12 @@ if (proxyWarning)
|
|
|
21
43
|
async function main() {
|
|
22
44
|
let tls;
|
|
23
45
|
let provider = null;
|
|
46
|
+
let displayHost = 'localhost';
|
|
24
47
|
if (config.tls) {
|
|
25
48
|
provider = createCertProvider(config.tls, { dataDir });
|
|
26
49
|
const material = await provider.ensureCert();
|
|
27
50
|
tls = { cert: material.cert, key: material.key };
|
|
51
|
+
displayHost = resolveRelayDisplayHost(config.tls, material.cert, (message) => logger.warn(message));
|
|
28
52
|
}
|
|
29
53
|
else {
|
|
30
54
|
logger.info('TLS disabled — serving HTTP. Secure-context features (e.g. WebCodecs hardware decode) require HTTPS; ' +
|
|
@@ -32,7 +56,7 @@ async function main() {
|
|
|
32
56
|
}
|
|
33
57
|
const server = new RelayServer({ port, uploadsDir, wsBackpressureBytes: config.local.wsBackpressureBytes, trustedProxies: config.local.trustedProxies, corsOrigins, tls });
|
|
34
58
|
await server.start();
|
|
35
|
-
logger.info(`tapflow relay running
|
|
59
|
+
logger.info(`tapflow relay running at ${tls ? 'https' : 'http'}://${displayHost}:${port}`);
|
|
36
60
|
const stopTls = provider ? startTlsBackgroundTasks(provider, server, config.tls) : null;
|
|
37
61
|
const shutdown = () => {
|
|
38
62
|
stopTls?.();
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tapflowio/relay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "WebSocket relay server for tapflow — NAT traversal, session routing, JWT auth, bundled dashboard",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tapflow",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"better-sqlite3": "^12.11.1",
|
|
50
50
|
"busboy": "^1.6.0",
|
|
51
51
|
"jsonwebtoken": "^9.0.3",
|
|
52
|
-
"nodemailer": "^9.0.
|
|
52
|
+
"nodemailer": "^9.0.6",
|
|
53
53
|
"ws": "^8.21.3",
|
|
54
|
-
"zod": "^4.4
|
|
55
|
-
"@tapflowio/agent-core": "0.
|
|
56
|
-
"@tapflowio/protocol": "0.
|
|
54
|
+
"zod": "^4.5.4",
|
|
55
|
+
"@tapflowio/agent-core": "0.20.1",
|
|
56
|
+
"@tapflowio/protocol": "0.20.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@types/nodemailer": "^8.0.1",
|
|
64
64
|
"@types/ws": "^8.0.0",
|
|
65
65
|
"typescript": "^5.0.0",
|
|
66
|
-
"vitest": "^4.1.
|
|
66
|
+
"vitest": "^4.1.11",
|
|
67
67
|
"@tapflowio/test-utils": "0.0.0"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import{i as r,j as e}from"./vendor-react-AJpf4YZ0.js";import{W as C,B as y,L as b,a4 as k,J,I as M,S as Q,f as W}from"./index-
|
|
2
|
-
* @license lucide-react
|
|
1
|
+
import{i as r,j as e}from"./vendor-react-AJpf4YZ0.js";import{W as C,B as y,L as b,a4 as k,J,I as M,S as Q,f as W}from"./index-O-Temo_p.js";import{e as Y,S as K,P as X,g as ee,h as se,i as ae,b as te,d as le,s as ne,u as re}from"./build-format-DO1zN4vW.js";import{D as O,f as L,a as U,d as E,e as V,S as A,i as _,j as B,g as z,h as i,C as ce}from"./dialog-BGH9MPcH.js";import{P as ie,T as oe}from"./trash-2-CAhMuWgg.js";import{B as T}from"./badge-CP-xbjo3.js";import{A as de,c as ue,f as he,g as xe,d as pe,e as me,b as fe,a as ge}from"./alert-dialog-CLhPOSdB.js";import{b as je,c as ve}from"./vendor-router-BFibuab1.js";/**
|
|
2
|
+
* @license lucide-react v1.37.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
5
5
|
* See the LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/const Ne=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]],Se=C("layers",Ne);/**
|
|
7
|
-
* @license lucide-react
|
|
7
|
+
* @license lucide-react v1.37.0 - ISC
|
|
8
8
|
*
|
|
9
9
|
* This source code is licensed under the ISC license.
|
|
10
10
|
* See the LICENSE file in the root directory of this source tree.
|
|
11
11
|
*/const ye=[["path",{d:"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z",key:"1a0edw"}],["path",{d:"M12 22V12",key:"d0xqtd"}],["polyline",{points:"3.29 7 12 12 20.71 7",key:"ousv84"}],["path",{d:"m7.5 4.27 9 5.15",key:"1c824w"}]],we=C("package",ye);/**
|
|
12
|
-
* @license lucide-react
|
|
12
|
+
* @license lucide-react v1.37.0 - ISC
|
|
13
13
|
*
|
|
14
14
|
* This source code is licensed under the ISC license.
|
|
15
15
|
* See the LICENSE file in the root directory of this source tree.
|
|
16
16
|
*/const be=[["path",{d:"M10 2h4",key:"n1abiw"}],["path",{d:"M4.6 11a8 8 0 0 0 1.7 8.7 8 8 0 0 0 8.7 1.7",key:"10he05"}],["path",{d:"M7.4 7.4a8 8 0 0 1 10.3 1 8 8 0 0 1 .9 10.2",key:"15f7sh"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M12 12v-2",key:"fwoke6"}]],ke=C("timer-off",be);/**
|
|
17
|
-
* @license lucide-react
|
|
17
|
+
* @license lucide-react v1.37.0 - ISC
|
|
18
18
|
*
|
|
19
19
|
* This source code is licensed under the ISC license.
|
|
20
20
|
* See the LICENSE file in the root directory of this source tree.
|