From 7b871f4cbf19ff253f7d4ad67f36121e8cb951c0 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Fri, 26 Jun 2026 16:32:42 +0100 Subject: [PATCH] feat(auth): optional HTTP Basic Auth for the whole site nginx includes auth_enabled.conf, (re)written at container start from BASIC_AUTH_USER/PASSWORD (htpasswd via apache2-utils). Both set => the site + /api require a shared login; unset => open (local dev not locked out). Set both on the Coolify web service to protect the deployment. --- Dockerfile | 5 +++++ README.md | 26 ++++++++++++++++---------- docker-compose.yml | 4 ++++ docker-entrypoint.d/40-basic-auth.sh | 16 ++++++++++++++++ nginx.conf | 4 ++++ 5 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 docker-entrypoint.d/40-basic-auth.sh diff --git a/Dockerfile b/Dockerfile index af20aa9..eb6093a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,11 @@ COPY . . RUN pnpm build FROM nginx:alpine +# apache2-utils provides htpasswd; auth_enabled.conf is included by nginx.conf +# and (re)written at start by the entrypoint hook — default empty = no auth. +RUN apk add --no-cache apache2-utils && touch /etc/nginx/auth_enabled.conf COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY docker-entrypoint.d/40-basic-auth.sh /docker-entrypoint.d/40-basic-auth.sh +RUN chmod +x /docker-entrypoint.d/40-basic-auth.sh EXPOSE 80 diff --git a/README.md b/README.md index 60874cb..1b6acf6 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,14 @@ Deployed at https://photofetch.apoena.dev -Pick a **Napta project** and get a grid of the people staffed on it — each with -their **Slack profile picture**, first name, last name, and role. People are -matched between Napta and Slack by **email**. +Pick one or more **Napta projects** and get a grid of the people staffed on each — +every person shown with their **Slack profile picture**, first name, last name, and +role. People are matched between Napta and Slack by **email**. Selected projects are +shown as separate groups, and you can **export** everyone's photos as a zip with one +folder per project (people without a Slack photo get a generated initials image). + +See [`CONTEXT.md`](./CONTEXT.md) for the vocabulary and [`DESIGN.md`](./DESIGN.md) +for the design decisions. ## Architecture @@ -40,14 +45,15 @@ cd backend && pnpm typecheck Set on the Coolify app (or in `backend/.env` locally — see `backend/.env.example`): -| Var | Purpose | -| ----------------- | --------------------------------------------------------- | -| `NAPTA_API_TOKEN` | Napta API token (lists projects + their people) | -| `NAPTA_BASE_URL` | Napta API base (default `https://app.napta.io/api/v1`) | -| `SLACK_BOT_TOKEN` | Slack bot token, scopes `users:read` + `users:read.email` | +| Var | Purpose | +| ------------------------------------- | ------------------------------------------------------------------------------------ | +| `NAPTA_CLIENT_ID` / `NAPTA_CLIENT_SECRET` | Napta Auth0 M2M credentials (lists projects + staffing). Without **both** → demo fixtures. | +| `NAPTA_BASE_URL` | Napta API base (default `https://app.napta.io/api/v1`) | +| `SLACK_BOT_TOKEN` | Slack bot token, scopes `users:read` + `users:read.email`. Optional — without it, Members show initials. | +| `BASIC_AUTH_USER` / `BASIC_AUTH_PASSWORD` | Set **both** (on the `web` service) to require a shared login for the whole site. Unset → open. | -Without **both** tokens the API serves demo fixtures and the UI shows a -"demo data" banner. +Without Napta credentials the API serves demo fixtures and the UI shows a +"demo data" banner. `BASIC_AUTH_*` go on the **web** service; the rest on **api**. ## Deploy diff --git a/docker-compose.yml b/docker-compose.yml index b02956f..abd9ee5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,10 @@ services: # would collide with Coolify's own :80 / other apps on the host. expose: - "80" + environment: + # Set both on the Coolify app to require a shared login for the whole site. + - BASIC_AUTH_USER=${BASIC_AUTH_USER:-} + - BASIC_AUTH_PASSWORD=${BASIC_AUTH_PASSWORD:-} depends_on: - api diff --git a/docker-entrypoint.d/40-basic-auth.sh b/docker-entrypoint.d/40-basic-auth.sh new file mode 100644 index 0000000..25ca608 --- /dev/null +++ b/docker-entrypoint.d/40-basic-auth.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Toggle HTTP Basic Auth for the whole site from env at container start. +# Runs via nginx:alpine's /docker-entrypoint.d/ hook, before nginx starts. +set -e + +if [ -n "$BASIC_AUTH_USER" ] && [ -n "$BASIC_AUTH_PASSWORD" ]; then + htpasswd -bc /etc/nginx/.htpasswd "$BASIC_AUTH_USER" "$BASIC_AUTH_PASSWORD" >/dev/null 2>&1 + cat > /etc/nginx/auth_enabled.conf < /etc/nginx/auth_enabled.conf + echo "[entrypoint] basic auth disabled (set BASIC_AUTH_USER + BASIC_AUTH_PASSWORD to enable)" +fi diff --git a/nginx.conf b/nginx.conf index 9734b8d..c79f517 100644 --- a/nginx.conf +++ b/nginx.conf @@ -4,6 +4,10 @@ server { root /usr/share/nginx/html; index index.html; + # Optional HTTP Basic Auth for the whole site (incl. /api). Written at + # container start from BASIC_AUTH_USER/PASSWORD; empty file => no auth. + include /etc/nginx/auth_enabled.conf; + # Proxy API calls to the backend service (docker-compose service name "api"). location /api/ { proxy_pass http://api:8000;