diff --git a/BACKLOG.md b/BACKLOG.md index 704d140..c4ce43f 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -289,11 +289,12 @@ Sami explicitly stated he wants email auth as an OPTION alongside TOTP, not a re - **prerequisite:** DC-047 (needs email auth working first). ### DC-049: Update login UI to show multiple providers -- **status:** todo -- **owner:** unclaimed +- **status:** done +- **owner:** hermes - **details:** Currently the login page is TOTP-only. Once DC-046/047/048 ship, login needs to render ALL enabled providers as a list of buttons, each routing to its provider-specific initiate flow (`/api/v1/auth/login/totp`, `/api/v1/auth/login/email`). Frontend work — `status/js/core/login.js` and the login modal markup. Add a small "Choose how to sign in" header. Effort: ~1 hr. Risk: low (pure UI, no backend changes). - **impact:** Makes the pluggable auth provider pattern visible to users. Without this, providers other than TOTP are unreachable. - **prerequisite:** DC-046 + DC-047 (needs at least two providers to be meaningful). +- **result:** Shipped. New module `status/js/auth-gate.js` (~290 LOC) owns the `?auth=required` flow: queries `GET /api/v1/auth/login/methods`, renders one of three UIs — provider selector (2+ enabled), TOTP overlay + email fallback link (only TOTP enabled, email available), or pure legacy TOTP (truly single-provider). `email` provider renders inline: text input + "Send sign-in link" button that POSTs to `/api/v1/auth/login/email/initiate`; on success shows the masked recipient + deliveredVia ('dev-console' vs 'inbox'). Coordination with `totp-auth.js`: `auth-gate.js` sets `window.__dc_049_handled = true` at IIFE entry so the legacy TOTP module skips its own UI when auth-gate is in charge, eliminating flicker on multi-provider installs. Bundle order in `build.js`: auth-gate BEFORE totp-auth (flag must be set first). Verified live: `https://status.sami/dist/core.js` contains all 4 expected markers (`_showAuthGate`, `provider-btn`, `auth-gate-email-input`, `__dc_049_handled`). SW cache hash `dashcaddy-shell-c550d0b371` (was `dashcaddy-shell-310b97d25a` before this work). User instruction: hard-refresh `status.sami` to pick up the new bundle. ### DC-050: Harden platform-paths.dataDir — structural guard against image-layer data loss - **status:** done diff --git a/CHANGELOG.md b/CHANGELOG.md index f236d82..008ac49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Pluggable auth UI — DC-049.** `status/js/auth-gate.js` discovers enabled providers via `GET /api/v1/auth/login/methods` and renders either a provider selector (2+ enabled), the TOTP overlay with an "Or sign in with email instead →" alt-link, or the pure legacy TOTP overlay. Email provider renders inline: input + "Send sign-in link" button → POST `/api/v1/auth/login/email/initiate`. Coordination flag `window.__dc_049_handled` eliminates flicker on multi-provider installs. New SW cache hash `dashcaddy-shell-c550d0b371`. - **Pluggable `AuthProvider` framework + TOTP + EmailMagicLink providers (DC-046 + DC-047).** New `src/auth/providers/` directory contains the `AuthProvider` base class contract, the TOTP provider (refactored from existing `routes/auth/totp.js`), and a new `EmailMagicLinkProvider` that issues single-use base64url tokens (stored as SHA-256 hashes in `data/email-tokens.json`), sends via the existing nodemailer config (or logs to console + `log.info('email magic link issued')` in dev fallback). `createAuthProviderRegistry()` composes all providers and surfaces them via `/api/v1/auth/login/methods` (`GET`), `/api/v1/auth/login/:provider/{initiate,verify}` (`POST`), `/api/v1/auth/login/recovery-info` (`GET`), `/api/v1/auth/disable/:provider` (`POST`). Future auth methods (OIDC, SAML, passkeys) plug into the registry without auth-path refactors. - **`platform-paths.assertSafe()` — DC-046 hardening.** Production startup refuses to boot if `dataDir` resolves into a Docker image-layer forbidden zone (`/app/src`, `/app/routes`, `/app/utils`, `/app/managers`, `/app/security`, `/etc/*`, `/var/lib/caddy`, etc.). Catches the silent failure mode where `SERVICES_FILE` isn't set as an env var and the resolver falls back to a path that would lose runtime state on every container recreate. Bypassed with `SKIP_DATA_DIR_GUARD=1` for emergency legacy setups. - **`platform-paths.isMountedCheck(dir)`.** Heuristic predicate for detecting whether a directory is reachable + writable + on a separate filesystem from `/app`. Used by `start.sh` migration step to no-op safely on fresh installs.