# Digt POC Hub (portal)

The front door + access gateway for the `proof-of-concepts` host. It is a login wall
for the whole domain, lists the POC apps as cards (filtered to what each user may
access), and gives admins a dashboard to manage the app directory **and** which
emails can reach which apps. It centralises the path/domain registry and the access
control — it does **not** provision or deploy apps (those are created with the
`proof-of-concept` skill's scaffolder and shipped via GitLab).

## How it works

- **App registry** — single source of truth at `data/apps.json`
  (`{ slug, title, description, path, status, roles }`). Seeded from code on first
  run, runtime-editable, and under `data/` (kept out of the CI's `rsync --delete`, so
  edits survive deploys).
- **User directory** — `data/users.json` (`{ email, name, allowedApps[], admin,
  passwordHash? }`). Holds password hashes → never committed.
- **`/`** — cards, filtered to the signed-in user's allowed apps (admins see all).
- **`/admin`** — admin-only: manage users (email, name, allowed apps, admin flag,
  optional local password) and the app directory.

## Authentication & access (the login wall)

- **Login** at `/login` via **local username/password** (directory users + the
  bootstrap admin) or **Google OAuth** (the Google email must be in the directory).
- **Bootstrap admin** — `HUB_ADMIN_EMAIL` + `HUB_ADMIN_PASSWORD` (env only, not in the
  directory). The escape hatch so a fresh environment isn't locked out.
- **Session** — a signed (`AUTH_SECRET`, HS256) cookie scoped to the whole host.
- **Per-app access** — each user has an `allowedApps` list; admins (or `*`) get all.
  Resolved **fresh** on every check, so changes take effect without re-login.
- **Domain-wide enforcement** — the hub exposes `/api/authz` for nginx
  `auth_request`: nginx calls it before proxying **any** request (hub or a POC), so
  the wall + per-app ACL cover the whole domain and the POC apps need no auth code.
  See `deploy/nginx-auth.example.conf`.

## Local dev

```bash
cd portal
npm install
cp .env.example .env.local     # set AUTH_SECRET + HUB_ADMIN_EMAIL/PASSWORD (+ Google, optional)
npm run dev                    # http://localhost:3000 → redirects to /login
```

## Serving model (subpath-per-POC on one domain)

The hub serves the **domain root** (`poc.digt.ch`); each POC serves under its registry
`path` (e.g. `poc.digt.ch/product-ai-automation`), built with `BASE_PATH=/<slug>`
(see the `proof-of-concept` skill's base-path recipe). nginx routes `/` → the hub and
`/<slug>` → each POC, with the `auth_request` gate on every location.

## Deploy — follow-up (not wired in this branch on purpose)

The app is complete; standing up the live topology is a coordinated infra step:

1. CI deploy jobs for `portal/` → the **root** web roots
   (`/srv/www/digt/{dev.,stage.,}poc.digt.ch/`), `changes:`-scoped to `portal/**`
   (mirror the skill's `_ci_jobs.sh`).
2. Apply the nginx `auth_request` config (`deploy/nginx-auth.example.conf`) + a
   `location` block per slug.
3. Move `product-ai-automation` to a subpath-mapped root, built with
   `BASE_PATH=/product-ai-automation` (support already on `feat/configurable-base-path`).

Kept out of this branch so it doesn't collide with the base-path branch's CI change
or guess at server config.
