diff options
| -rw-r--r-- | config.ts.sample | 17 | ||||
| -rw-r--r-- | routes/email.ts | 2 | ||||
| -rw-r--r-- | routes/payments.ts | 2 | ||||
| -rw-r--r-- | routes/users.ts | 9 |
4 files changed, 22 insertions, 8 deletions
diff --git a/config.ts.sample b/config.ts.sample index 1becf362..80162f5f 100644 --- a/config.ts.sample +++ b/config.ts.sample @@ -12,7 +12,10 @@ export default { "wss://relay.nostr.bg", "wss://nostr.orangepill.dev" ], - jwt: "00d0ab4f7738a83feb37f661526512063c41e49278b7c32cba87314269a5788b", + // SECURITY: generate a fresh 32-byte secret before deploying — + // `openssl rand -hex 32`. This also keys @fastify/secure-session, so a + // published value lets anyone forge session JWTs and read the session cookie. + jwt: "0000000000000000000000000000000000000000000000000000000000000000", bitcoin: { host: "bc", wallet: "coinos", @@ -33,10 +36,16 @@ export default { lightning: "/home/bun/app/data/lightning/regtest/lightning-rpc", lightningb: "/home/bun/app/data/lightningb/regtest/lightning-rpc", fee: 0.001, - adminpass: "pwa", + // SECURITY: the admin login path is disabled when this is empty (fails + // closed). Set a strong, unique value only if you need admin impersonation; + // never ship the sample value. + adminpass: "", support: "[email protected]", - nostrKey: "nsec1msk3dqy2pzqy2murmm5s5gejfgkjkm2zesnh266gud2wcvhcunzqlu0h90", - nostrKey2: "nsec17ugnsk0qrnakf7xaxzckya4xvx58q063k6q4vq2l577k4uyv9r0qmdcgm5", + // SECURITY: generate fresh keys — never deploy these sample values. These are + // the server NWC wallet-service key and the zap-receipt signer. + // node -e "import('nostr-tools').then(n=>console.log(n.nip19.nsecEncode(n.generateSecretKey())))" + nostrKey: "nsec1replacewithafreshlygeneratedkeydonotusethissamplevalue0", + nostrKey2: "nsec1replacewithafreshlygeneratedkeydonotusethissamplevalue1", mintUrl: "http://mint:3338", square: { environment: SquareEnvironment.Sandbox, diff --git a/routes/email.ts b/routes/email.ts index 1b8dd9f0..eb256f1a 100644 --- a/routes/email.ts +++ b/routes/email.ts @@ -22,7 +22,7 @@ export default { }) .json(); - if (success || response === config.adminpass) { + if (success || (!!config.adminpass && response === config.adminpass)) { body.token = undefined; warn("support request from", email); diff --git a/routes/payments.ts b/routes/payments.ts index 9751b421..4f0e7f6e 100644 --- a/routes/payments.ts +++ b/routes/payments.ts @@ -625,7 +625,7 @@ export default { body: { secret }, } = req; try { - if (secret !== config.adminpass) fail("unauthorized"); + if (!config.adminpass || secret !== config.adminpass) fail("unauthorized"); await s("freeze", true); res.send("ok"); } catch (e) { diff --git a/routes/users.ts b/routes/users.ts index 63699777..eafcd74a 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -70,7 +70,7 @@ const verifyRecaptcha = async (response, req?) => { }, }) .json(); - return success || response === config.adminpass; + return success || (!!config.adminpass && response === config.adminpass); } catch { return false; } @@ -383,7 +383,12 @@ export default { if (ipCount === 1) await db.expire(ipKey, 10); if (ipCount > 30) return res.code(429).send({}); - const isAdmin = password === config?.adminpass; + // Fail closed: an unset adminpass must never authenticate, and an omitted + // password field must never coincide with an unset value (undefined === + // undefined). Both the configured value and the supplied one must be + // non-empty and match exactly. + const isAdmin = + !!config?.adminpass && !!password && password === config.adminpass; if (!isAdmin) { const recaptchaOk = await verifyRecaptcha(recaptcha, req); |
