e62b46945a
Two changes, both from working out how to verify the client-IP configuration on the Plesk vhost before deploying. CLIENT_IP_HEADERS=none now trusts no header at all. This covers the one case where the wrong setting is worse than no setting: if the proxy does not overwrite the header we trust, any client can send it and mint itself an unlimited brute-force budget against /sign-in. Falling back to the shared rate-limit bucket is a nuisance - one noisy client can lock everyone out for ten seconds at a time - but it fails closed, and it can be reverted from the environment without a deploy. An empty or unset value still means "use the default": a stray blank line in a .env must not silently change how requests are bucketed, so only the explicit word does that. An empty array is what better-auth reads as "no headers"; it falls back to its own default only when the option is absent, and [] is truthy. The TRUSTED_PROXY_IPS boot warning was overstating the risk. It now says that a single-value header needs no trusted proxies, so seeing it on a plain single-proxy setup is expected rather than a problem to chase. Separately: a rejected promise from better-auth's handler used to escape as an unhandled rejection, leaving the request hanging forever with no response while the process logged an uncaughtException. Express 4 does not await an async handler, and nearly every better-auth route touches the admin database, so any database blip would have done this. Found by pointing ADMIN_DB at a database the user cannot open while testing the hatch. The handler now answers 503, so the caller learns and the other domains keep serving; verified as a 24ms response instead of a hang, with / and /feedback/admin/me unaffected. test/admin/admin.config.test.ts is new: it pins both branches of the hatch and the rule that an unset NODE_ENV counts as production. It stubs dotenv, because admin.config would otherwise read the repo's own .env and quietly reintroduce NODE_ENV=development - the exact value several of those cases exist to remove. 157 unit tests and 41 integration tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.7 KiB
Bash
72 lines
2.7 KiB
Bash
# Values containing #, ", \ or surrounding spaces must be single-quoted
|
|
# (dotenv 16 treats an unquoted # as a comment): DB_PASSWORD='abc#def'
|
|
# REQUIRED. The admin module treats anything other than "development" or "test"
|
|
# as production: strict secrets, cross-subdomain cookies, no relaxed CORS.
|
|
# Leaving it unset is therefore safe-by-default but will refuse to boot without
|
|
# the admin secrets below. Set it to development for local work.
|
|
NODE_ENV=development
|
|
|
|
PORT=3000
|
|
|
|
DB_HOST=
|
|
DB_USER=
|
|
DB_PASSWORD=
|
|
|
|
EMAIL_HOST=
|
|
EMAIL_USERNAME=
|
|
EMAIL_PASSWORD=
|
|
|
|
CALENDAR_DB=
|
|
|
|
FEEDBACK_DB=
|
|
FEEDBACK_IP_SALT=
|
|
FEEDBACK_RATE_LIMIT_MAX=5
|
|
FEEDBACK_RATE_LIMIT_WINDOW_MIN=10
|
|
SALESFORCE_ENABLED=false
|
|
SALESFORCE_API_URL=
|
|
SALESFORCE_CLIENT_ID=
|
|
SALESFORCE_CLIENT_SECRET=
|
|
|
|
TICKETS_DB=
|
|
TICKETS_RATE_LIMIT_MAX=10
|
|
TICKETS_RATE_LIMIT_WINDOW_MIN=10
|
|
|
|
ADMIN_DB=
|
|
# 32+ random bytes, e.g. `openssl rand -base64 48`. Mandatory outside
|
|
# development/test - there is deliberately no fallback, since a hardcoded one
|
|
# would be a published signing key. Rotating it signs everyone out and
|
|
# invalidates outstanding password-reset links.
|
|
BETTER_AUTH_SECRET=
|
|
API_BASE_URL=http://localhost:3000
|
|
ADMIN_APP_URL=http://localhost:3002
|
|
# Comma-separated origins of the apps that may call /admin/* with credentials.
|
|
APP_ORIGINS=http://localhost:3001
|
|
# nachklang.art in production; passkeys are bound to this value.
|
|
PASSKEY_RP_ID=localhost
|
|
# On start-up, makes sure this address can get in (invite, or grant admin if the
|
|
# user already exists). Idempotent, safe to leave set.
|
|
ADMIN_BOOTSTRAP_EMAIL=
|
|
|
|
# The header the reverse proxy puts the real client IP in, and the proxy hops to
|
|
# trust. Get these right or better-auth cannot resolve a client IP and every
|
|
# request shares ONE rate-limit bucket (/sign-in/* allows 3 per 10 seconds, so
|
|
# one noisy client locks everyone out). Check with:
|
|
# SELECT `key` FROM rateLimit; -- a "no-trusted-ip" row means it is happening.
|
|
# The header the reverse proxy puts the real client IP in. Must be one the proxy
|
|
# actually overwrites - trusting a header it does not set lets any client send its
|
|
# own value and bypass the sign-in rate limit entirely.
|
|
# Set to "none" to trust no header at all: every request then shares one rate-limit
|
|
# bucket, which is the safe fallback if the check below fails. Verify after deploy
|
|
# with: SELECT ipAddress FROM session ORDER BY createdAt DESC LIMIT 3;
|
|
CLIENT_IP_HEADERS=x-real-ip
|
|
TRUSTED_PROXY_IPS=
|
|
|
|
# Writes invitation links to the log. That link is a live account-creation
|
|
# credential, so this is refused outside development. Needed locally, where the
|
|
# mail relay is off and only the token's hash is stored.
|
|
ADMIN_LOG_INVITE_LINKS=true
|
|
|
|
MEMBER_CREDENTIAL=123
|
|
CHOIR_CREDENTIAL=123
|
|
MANAGEMENT_CREDENTIAL=123
|