feat: swap Redis for Dragonfly, fix remote installs, self-hosted email-only auth

- Replace redis:7-alpine with Dragonfly (Redis-protocol compatible; go-redis
  client unchanged) in root, infra, and backend-dev compose files. Bounded
  defaults --proactor_threads/--maxmemory so it boots on small VPSs.
- Frontend now defaults to same-origin API calls; nginx proxies /api/* to the
  backend (WebSocket-aware, docker DNS resolver for upstream re-resolution).
  Removes the baked localhost:8082 that broke installs reached by IP/domain.
- /auth/bootstrap returns configured OAuth providers; the UI hides
  GitHub/Google buttons unless real credentials exist. Self-hosted stays
  email/password only - OAuth reserved for a future managed offering.
- Sidecar ignores PLACEHOLDER_* OAuth credentials and trusts same-host
  origins dynamically (Origin host == request Host) so remote installs pass
  origin checks without config; cross-origin requests still rejected.
- install.sh writes ENVIRONMENT=production; COOKIE_SECURE=false in
  production is now a startup warning instead of fatal (plain-HTTP installs).
- Bootstrap failure surfaces an error notice instead of silently rendering
  the sign-in page.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-21 17:24:43 +02:00
1 parent 9ba2bd06c5
commit 060038cb6f
21 files changed
+207 -83

No files matched your search

+32 -6
View File
@@ -46,10 +46,15 @@ const database = new PostgresDialect({
pool,
});
const githubClientId = env.GITHUB_CLIENT_ID?.trim() || '';
const githubClientSecret = env.GITHUB_CLIENT_SECRET?.trim() || '';
const googleClientId = env.GOOGLE_CLIENT_ID?.trim() || '';
const googleClientSecret = env.GOOGLE_CLIENT_SECRET?.trim() || '';
const realCredential = (value) => {
const trimmed = (value || '').trim();
return trimmed && !trimmed.startsWith('PLACEHOLDER_') ? trimmed : '';
};
const githubClientId = realCredential(env.GITHUB_CLIENT_ID);
const githubClientSecret = realCredential(env.GITHUB_CLIENT_SECRET);
const googleClientId = realCredential(env.GOOGLE_CLIENT_ID);
const googleClientSecret = realCredential(env.GOOGLE_CLIENT_SECRET);
const socialProviders = {};
if (githubClientId && githubClientSecret) {
@@ -67,7 +72,7 @@ if (googleClientId && googleClientSecret) {
};
}
const trustedOrigins = [
const staticTrustedOrigins = [
...splitCsv(env.BETTER_AUTH_TRUSTED_ORIGINS),
env.FRONTEND_URL,
env.BACKEND_URL,
@@ -86,12 +91,33 @@ const trustedOrigins = [
.map((origin) => (origin || '').trim())
.filter(Boolean);
// Self-hosted installs are reached over arbitrary hosts/IPs, so the static
// list above cannot cover them. Trust an Origin only when it matches the
// request's own Host header - that is genuine same-origin traffic (browsers
// cannot forge either header cross-origin). Do NOT consult x-forwarded-host
// here: clients can set it freely.
const resolveTrustedOrigins = (request) => {
const origins = new Set(staticTrustedOrigins);
const host = request?.headers?.get('host');
const origin = request?.headers?.get('origin');
if (host && origin) {
try {
if (new URL(origin).host === host) {
origins.add(origin);
}
} catch {
// malformed Origin header - leave untrusted
}
}
return [...origins];
};
export const auth = betterAuth({
appName: env.BETTER_AUTH_APP_NAME || 'Containr',
baseURL: env.BETTER_AUTH_URL || defaultBackendURL,
basePath: '/api/auth',
secret: env.BETTER_AUTH_SECRET || 'PLACEHOLDER_BETTER_AUTH_SECRET_CHANGE_ME_32CHARS_MIN',
trustedOrigins,
trustedOrigins: resolveTrustedOrigins,
database,
user: {
modelName: 'auth_users',