fix(auth): front CouchDB with nginx to mark AuthSession cookie Secure
CouchDB 3.5 derives the Set-Cookie Secure flag from the raw mochiweb socket scheme and ignores X-Forwarded-Proto/Ssl for cookie attribution. Behind a TLS-terminating proxy the cookie was emitted without Secure, so browsers dropped the SameSite=None AuthSession cookie on cross-origin requests — POST /_session returned 200 but the next GET /_session showed userCtx.name: null and PouchDB stayed unauthenticated. Add an nginx sidecar that adds Secure via proxy_cookie_flags, route the public domain through it, and make couchdb internal. Also persist same_site=none in couchdb-init so the setting survives container recreation (local.d/ is not in the data volume).
This commit is contained in:
29
README.md
29
README.md
@@ -1,6 +1,14 @@
|
||||
# coolcouch
|
||||
|
||||
CouchDB 3 on Coolify. The `couchdb-init` sidecar waits for the server to come up, creates the three system databases, and enables CORS via the config API — no file mounts required.
|
||||
CouchDB 3 on Coolify, fronted by an nginx sidecar. The `couchdb-init` sidecar waits for the server to come up, creates the three system databases, and applies CORS + cookie config via the config API — no file mounts on CouchDB itself.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
client ──https──> Traefik ──http──> nginx ──http──> couchdb (internal)
|
||||
```
|
||||
|
||||
nginx exists for one reason: to add `Secure` to the `AuthSession` cookie that CouchDB emits. CouchDB only marks cookies `Secure` when its embedded mochiweb sees a true HTTPS socket; behind a TLS-terminating proxy it never does, and it ignores `X-Forwarded-Proto`/`X-Forwarded-Ssl` for cookie attribution. Without `Secure`, browsers reject `SameSite=None` cookies, and cross-origin PouchDB logins silently fail (login returns 200, the next `_session` shows `userCtx.name: null`). The fix is `proxy_cookie_flags AuthSession secure;` in `nginx/default.conf`.
|
||||
|
||||
## Deploy
|
||||
|
||||
@@ -8,7 +16,8 @@ CouchDB 3 on Coolify. The `couchdb-init` sidecar waits for the server to come up
|
||||
2. In the resource's **Environment Variables** tab, set:
|
||||
- `COUCHDB_PASSWORD` — required
|
||||
- `COUCHDB_USER` — optional, defaults to `admin`
|
||||
3. In the resource's **Domains** tab, assign a domain to the `couchdb` service on port `5984`. Coolify fills `SERVICE_FQDN_COUCHDB_5984` from this.
|
||||
- `INVITE_CODE` — required for the signup service
|
||||
3. In the resource's **Domains** tab, assign your CouchDB domain to the **`nginx`** service on port `5984` (not `couchdb` directly — that's now internal). Coolify fills `SERVICE_FQDN_NGINX_5984` from this. Assign the signup domain to the `signup` service on port `8080`.
|
||||
4. **Deploy.** `couchdb-init` runs once, exits, and won't restart.
|
||||
|
||||
## Verify
|
||||
@@ -26,6 +35,21 @@ curl -fsS -I -X OPTIONS https://$DOMAIN/ \
|
||||
|
||||
Expected: `_up` returns `{"status":"ok",...}`; `/` returns the CouchDB welcome JSON; `_all_dbs` returns `["_global_changes","_replicator","_users"]`; the preflight returns 2xx with an `Access-Control-Allow-Origin` header.
|
||||
|
||||
Verify the cookie has both `SameSite=None` and `Secure`:
|
||||
|
||||
```bash
|
||||
curl -i -X POST https://$DOMAIN/_session \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d "name=admin&password=$PASS" | grep -i set-cookie
|
||||
```
|
||||
|
||||
Expected:
|
||||
```
|
||||
set-cookie: AuthSession=...; Version=1; Expires=...; Max-Age=600; Path=/; HttpOnly; SameSite=None; Secure
|
||||
```
|
||||
|
||||
If `Secure` is missing, the nginx sidecar isn't fronting the request — check that the Coolify domain is assigned to `nginx:5984` and not to `couchdb` directly.
|
||||
|
||||
If the init logs show CORS errors, you can re-trigger only that step by redeploying — the PUTs against `/_node/_local/_config/...` are idempotent.
|
||||
|
||||
## CORS
|
||||
@@ -87,4 +111,5 @@ The frontend should then `POST https://couch.apoena.dev/_session` with the same
|
||||
## Files
|
||||
|
||||
- `docker-compose.yml` — services, volume, Traefik labels, init sidecar
|
||||
- `nginx/default.conf` — reverse-proxy config that adds `Secure` to AuthSession cookies
|
||||
- `signup/` — Gleam signup service (Dockerfile, gleam.toml, src/)
|
||||
|
||||
@@ -3,7 +3,6 @@ services:
|
||||
image: couchdb:3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
SERVICE_FQDN_COUCHDB_5984:
|
||||
COUCHDB_USER: ${COUCHDB_USER:-admin}
|
||||
COUCHDB_PASSWORD: ${COUCHDB_PASSWORD}
|
||||
expose:
|
||||
@@ -12,6 +11,24 @@ services:
|
||||
- couchdb_data:/opt/couchdb/data
|
||||
networks:
|
||||
- default
|
||||
|
||||
# nginx fronts CouchDB to add `Secure` to the AuthSession cookie. CouchDB only
|
||||
# marks the cookie Secure when mochiweb sees a true HTTPS socket, which never
|
||||
# happens behind a TLS-terminating proxy — so SameSite=None cookies would be
|
||||
# dropped by browsers on cross-origin requests. See nginx/default.conf.
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- couchdb
|
||||
environment:
|
||||
SERVICE_FQDN_NGINX_5984:
|
||||
expose:
|
||||
- "5984"
|
||||
volumes:
|
||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- default
|
||||
- coolify
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
@@ -59,11 +76,15 @@ services:
|
||||
"$$base/_node/_local/_config/$$1/$$2" -d "\"$$3\"" >/dev/null
|
||||
echo "cfg $$1/$$2 = $$3"
|
||||
}
|
||||
put_config chttpd enable_cors true
|
||||
put_config cors origins 'http://localhost:8080,http://localhost:5173,http://localhost:3000,http://localhost:4173,https://vaquant.at'
|
||||
put_config cors credentials true
|
||||
put_config cors methods 'GET, PUT, POST, HEAD, DELETE'
|
||||
put_config cors headers 'accept, authorization, content-type, origin, referer, x-csrf-token'
|
||||
put_config chttpd enable_cors true
|
||||
put_config cors origins 'http://localhost:8080,http://localhost:5173,http://localhost:3000,http://localhost:4173,https://vaquant.at'
|
||||
put_config cors credentials true
|
||||
put_config cors methods 'GET, PUT, POST, HEAD, DELETE'
|
||||
put_config cors headers 'accept, authorization, content-type, origin, referer, x-csrf-token'
|
||||
# SameSite=None is required for PouchDB cross-origin cookie sessions.
|
||||
# Browsers also require Secure on SameSite=None cookies — that flag is
|
||||
# added by the nginx sidecar via proxy_cookie_flags.
|
||||
put_config couch_httpd_auth same_site none
|
||||
|
||||
signup:
|
||||
build:
|
||||
|
||||
29
nginx/default.conf
Normal file
29
nginx/default.conf
Normal file
@@ -0,0 +1,29 @@
|
||||
server {
|
||||
listen 5984;
|
||||
server_name _;
|
||||
|
||||
# PouchDB attachments and _bulk_docs can be large.
|
||||
client_max_body_size 256m;
|
||||
|
||||
# CouchDB's _changes?feed=continuous needs HTTP/1.1, no buffering, long timeouts.
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 1h;
|
||||
|
||||
location / {
|
||||
proxy_pass http://couchdb:5984;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Host $http_x_forwarded_host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
# CouchDB never marks AuthSession cookies Secure behind a TLS-terminating
|
||||
# proxy — mochiweb only checks the raw socket scheme, and ignores
|
||||
# x_forwarded_proto/x_forwarded_ssl for cookie attribution. Add Secure
|
||||
# here so SameSite=None cookies survive cross-origin requests.
|
||||
proxy_cookie_flags AuthSession secure;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user