+ {/* Surface 2 — Brand Slot (above the login card, in the flow) */} + + + {/* Surface 3 — Login Card */} +
+

+ Sign in +

+ + {/* Surface 4 — Username field */} +
+ + setUsername(e.target.value)} + onKeyDown={handleUsernameKeyDown} + aria-describedby={loginError ? 'login-error' : undefined} + style={inputStyle(inputHasError)} + /> +
+ + {/* Surface 5 — Password field with show/hide toggle */} +
+ +
+ setPassword(e.target.value)} + onKeyDown={handlePasswordKeyDown} + onBlur={() => setShowPassword(false)} + aria-describedby={loginError ? 'login-error' : undefined} + style={{ ...inputStyle(inputHasError), paddingRight: '44px' }} + /> + {/* Show/hide toggle button — 44px tap target (UI-SPEC Surface 5) */} + +
+
+ + {/* Surface 6 — Error / lockout banner */} + {loginError && ( +
+ {loginError === 'invalid' && ( +
+
+ )} + + {loginError === 'rate-limit' && ( +
+
+ )} + + {loginError === 'locked' && ( +
+
+ )} + + {loginError === 'server' && ( +
+
+ )} +
+ )} + + {/* Surface 7 — Primary submit button */} + + + {/* Surface 10 — Forgot password helper (informational only, not interactive) */} +

+ Forgot your password? Ask your admin. +

+
+ + {/* Surfaces 8 & 9 — Method divider + OIDC button (only when oidcEnabled) */} + {oidcEnabled && ( + <> + {/* Surface 8 — Method divider */} + + ); +} diff --git a/apps/pwa/src/styles/tokens.css b/apps/pwa/src/styles/tokens.css index 578ce00..ea5d94e 100644 --- a/apps/pwa/src/styles/tokens.css +++ b/apps/pwa/src/styles/tokens.css @@ -88,6 +88,18 @@ --text-display-weight: 600; --text-display-line-height: 1.2; + /* ───────────────────────────────────────────────────────────────────────── + * BRAND SLOT — Phase 17 seam tokens + * Phase 19 sets placeholder defaults; Phase 17 overrides these values only — + * never the BrandSlot component structure (see 19-UI-SPEC.md §Brand Slot). + * ───────────────────────────────────────────────────────────────────────── */ + + --brand-logo-bg: var(--color-member-0); /* placeholder circle background */ + --brand-logo-text: #ffffff; /* placeholder initials color */ + --brand-logo-size: 48px; /* reserved slot height; keep 1:1 aspect */ + --brand-logo-border-radius: 50%; /* circle for initials; Phase 17 may change */ + --brand-app-name: 'FamilySync'; /* drives doc only — not used as CSS content */ + /* ───────────────────────────────────────────────────────────────────────── * BREAKPOINTS (reference; use in @media queries) * ───────────────────────────────────────────────────────────────────────── */ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 08201be..34df070 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -13,6 +13,12 @@ services: # NODE_ENV !== 'production' (and the production image bakes NODE_ENV=production), # so this can never activate in a shipped image. Required by the e2e harness. DEV_AUTH_BYPASS: 'true' + # Phase 19 (AUTH-LOCAL-16, D-14/D-15): required for devSessionCookieMiddleware to + # issue real local-session cookies under bypass AND for the real-login round-trip + # (POST /api/auth/local/login) to sign a session — without it that path 503s. + # Fixed dev-only value, mirrors the CI harness job (.gitea/workflows/ci.yml) — + # NEVER a production secret; this override file is dev-only (target: dev). + LOCAL_SESSION_SECRET: 'dev-secret-change-me-0000000000000000' mariadb: ports: diff --git a/scripts/generate-secrets.mjs b/scripts/generate-secrets.mjs index 181bb9e..f3d577e 100644 --- a/scripts/generate-secrets.mjs +++ b/scripts/generate-secrets.mjs @@ -27,6 +27,9 @@ import { randomBytes, createECDH } from 'node:crypto'; const sessionSecret = randomBytes(32).toString('hex'); const encKey = randomBytes(32).toString('hex'); +// Phase 19 (D-05): LOCAL_SESSION_SECRET signs the local-auth JWT session cookie. +// Must be >= 32 chars. 32 random bytes encoded as base64 = 44 chars (safe, distinct from hex keys). +const localSessionSecret = randomBytes(32).toString('base64'); // VAPID key generation (P-256 / prime256v1 — same curve as web-push) const ecdhCurve = createECDH('prime256v1'); @@ -55,4 +58,6 @@ SESSION_SECRET=${sessionSecret} APP_PASSWORD_ENCRYPTION_KEY=${encKey} VAPID_PUBLIC_KEY=${vapid.publicKey} VAPID_PRIVATE_KEY=${vapid.privateKey} +# Phase 19 (D-05): Signs local-auth JWT session cookies. Required when not using DEV_AUTH_BYPASS. +LOCAL_SESSION_SECRET=${localSessionSecret} `);