Files
picpeak/backend/.env.example
T
Paul Nothaft 5c7de96b7f fix(auth): default COOKIE_SECURE to 'auto' in production + first-install UX (#427)
Two intertwined bugs reported in #427 by @iSchumi6210:

1. Login silently fails over HTTP. Backend defaulted COOKIE_SECURE to true
   when NODE_ENV=production. Over plain HTTP the browser drops the Secure
   cookie → next /auth/session request returns 401 → redirect back to
   /admin/login → no error shown. picpeak-setup.sh writes
   NODE_ENV=production but never writes COOKIE_SECURE, so every first-time
   install without a reverse proxy hits this.

2. Admin password is generated but admins can't find it. The 001_init.js
   migration writes the generated password to data/ADMIN_CREDENTIALS.txt
   inside the backend container, but picpeak-setup.sh only copies it out
   when --reset-admin-password is passed. Default-path users never see it
   and resort to manual bcrypt updates in psql.

Changes:

- tokenUtils.js: production default goes from `true` to `'auto'`. On real
  HTTPS req.secure is true → Secure flag is still emitted (no security
  regression for reverse-proxy deployments). On plain HTTP req.secure is
  false → Secure flag omitted → login works. Users who explicitly want
  the strict HTTPS-only behaviour can still set COOKIE_SECURE=true.

- .env.example: rewrite the COOKIE_SECURE block to make the new default
  obvious and explain when to override (set =true for strict, =false to
  skip the per-request check, leave unset for the auto behaviour).

- picpeak-setup.sh (both Docker and native paths):
  - Write COOKIE_SECURE=auto explicitly to the generated .env (defense in
    depth so the right behaviour is preserved even if the backend default
    flips again later)
  - After migrations, ALWAYS copy ADMIN_CREDENTIALS.txt out of the
    backend container/data dir to the host data dir, chmod 600, and print
    the email + password to the install output. The credentials file
    remains as a backup record that the operator should delete after
    noting the password.

Verified locally with all 4 permutations of NODE_ENV × COOKIE_SECURE:

  production, unset      → HTTPS: secure=true ✓  HTTP: secure=false ✓ (was both true)
  production, =true      → both: secure=true (strict opt-in preserved)
  production, =auto      → HTTPS: secure=true   HTTP: secure=false (already-correct)
  development, unset     → both: secure=false (dev unchanged)
2026-05-09 15:55:09 +02:00

106 lines
3.9 KiB
Bash

# Backend Environment Variables Example
# Copy this file to .env and update with your values
# Application
NODE_ENV=production
PORT=3001
# Security
# Generate with: openssl rand -base64 32
JWT_SECRET=your-very-secure-jwt-secret-at-least-32-characters-long-example123456
# Auth cookie Secure flag
# unset - default: 'auto' in production, false in dev (#427)
# true - always set Secure (HTTPS-only cookies; breaks plain-HTTP access —
# login appears to succeed but the browser silently drops the
# cookie, leaving you in a redirect loop. Only set this if you
# ALWAYS reach the site via HTTPS)
# false - never set Secure (allows HTTP; cookies not protected on HTTPS)
# auto - decide per request: Secure on HTTPS, not on HTTP. Reads
# req.secure from Express which respects X-Forwarded-Proto from a
# trusted reverse proxy. This is the default and is the right
# choice for most deployments.
#
# Why 'auto' is the default in production:
# - On real HTTPS (reverse proxy with X-Forwarded-Proto), req.secure is
# true → Secure flag is still emitted. No security regression vs. true.
# - On plain HTTP (LAN access, first-time install before reverse proxy is
# wired up), req.secure is false → Secure flag is omitted → login works
# instead of silently looping back to /admin/login.
#
# When you'd set this explicitly:
# - COOKIE_SECURE=true → strict HTTPS-only deployments where you want
# defense in depth against accidentally serving over HTTP.
# - COOKIE_SECURE=false → you intentionally only ever serve over HTTP and
# don't want the per-request check (rare).
#
# Requirements for 'auto' mode to detect HTTPS correctly:
# 1. Your reverse proxy MUST send X-Forwarded-Proto: https on HTTPS
# requests. Standard configs for NPM/Traefik/Caddy do this by default.
# 2. The proxy must be on a trusted IP range. By default PicPeak trusts
# loopback and private networks (127.0.0.1, 10.x, 172.16-31.x,
# 192.168.x, link-local). Proxies outside those ranges need custom
# trust proxy configuration.
# COOKIE_SECURE=auto
# Cookie SameSite attribute (Lax | Strict | None). Default: Lax
# COOKIE_SAMESITE=Lax
# Cookie Domain — set this if serving auth cookies across subdomains.
# Leave unset for same-origin setups.
# COOKIE_DOMAIN=.example.com
# URLs (adjust for your domain)
ADMIN_URL=https://photos.example.com
FRONTEND_URL=https://photos.example.com
BACKEND_URL=https://photos.example.com # Or https://api.photos.example.com if separate
# API URL for email assets (logos, images in emails)
# This must be the publicly accessible URL where recipients can load images
# If not set, defaults to http://localhost:3001 which will break images in production emails
API_URL=https://photos.example.com/api
# Database Configuration
DATABASE_CLIENT=pg
DB_HOST=localhost
DB_PORT=5432
DB_USER=picpeak
DB_PASSWORD=your-secure-database-password-change-this
DB_NAME=picpeak
# Email Configuration (Examples for common providers)
# Gmail example:
# SMTP_HOST=smtp.gmail.com
# SMTP_PORT=587
# SMTP_SECURE=false
# SMTP_USER=your-email@gmail.com
# SMTP_PASS=your-app-specific-password
# SendGrid example:
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=apikey
SMTP_PASS=your-sendgrid-api-key
EMAIL_FROM=noreply@example.com
# Storage Paths
# IMPORTANT: STORAGE_PATH must be set to avoid file path resolution issues
# Docker deployment:
STORAGE_PATH=/app/storage
EVENTS_PATH=/app/storage/events
ARCHIVE_PATH=/app/storage/events/archived
# Local development:
# STORAGE_PATH=./storage
# EVENTS_PATH=./storage/events
# ARCHIVE_PATH=./storage/events/archived
# Analytics Backend Configuration (OPTIONAL)
# Used for server-side tracking only
# Primary configuration should be done through Admin UI > Settings > Analytics
# UMAMI_URL=https://analytics.example.com
# UMAMI_WEBSITE_ID=b4d3c2a1-5678-90ab-cdef-1234567890ab
# Logging
LOG_LEVEL=info