diff --git a/.gitignore b/.gitignore index 9176e4a..920be65 100644 --- a/.gitignore +++ b/.gitignore @@ -82,4 +82,4 @@ RATE_LIMIT.md # Unpublished paper paper/ -paper.zip \ No newline at end of file +paper.zip.gstack/ diff --git a/CHANGELOG.md b/CHANGELOG.md index bb1822e..e89789a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.0.1.0] - 2026-03-22 + +### Fixed +- Social auth errors now display inside the `SocialAuth` component and call `clearError()` to prevent double-display in parent forms (`SignInForm`, `SignUpFlow`) +- Fixed regression where social auth failure on `RegisterPage` Step 1 was silently invisible — error now shown in the correct step context +- `auth/popup-closed-by-user` error message updated to mention env var and Firebase Authorized Domains as common causes on fresh deployments +- Error color in `SocialAuth` unified to use design system token `text-verdict-fail` instead of hardcoded `text-red-500` +- `firebaseConfigured` export added to `firebase.js` — `SocialAuth` renders "Social sign-in is not configured." when Firebase env vars are absent + +### Added +- Meta tag fallback for Firebase config (`fb-api-key`, `fb-auth-domain`, `fb-project-id`) enables Docker runtime injection path as alternative to build-time env vars +- Named error handling for `auth/account-exists-with-different-credential`, `auth/popup-blocked`, `auth/popup-closed-by-user`, `auth/unauthorized-domain` with readable user-facing messages +- CLAUDE.md: Docker base image rules, npm install vs npm ci rules, Firebase auth deployment checklist and error code reference +- TODOS.md: created with P2 items for Firebase health endpoint and Vitest auth unit tests + ## [Unreleased] ### Added diff --git a/CLAUDE.md b/CLAUDE.md index feaf739..4bef144 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -133,3 +133,56 @@ After implementing UI changes, verify the running app serves the latest code. St When instructed to review a plan or enter "Plan Mode", you **MUST** read and strictly adhere to the workflow, review stages, and engineering preferences defined in `PLAN_MODE.md`. +--- + +## Docker & Deployment Rules + +**Base images — NEVER use `node:XX-alpine` official images:** +- `node:XX.X-alpine` bundles npm outside Alpine's APK database; Docker Scout reports CVEs per-layer even after `npm install -g npm@newer` (the old layer remains visible to the scanner) +- ALWAYS use `FROM alpine:X.XX` + `RUN apk upgrade --no-cache && apk add --no-cache nodejs npm` for all Node build/runtime stages +- On Alpine 3.23, the Node.js package name is `nodejs` — NOT `nodejs-20` (does not exist), NOT `node` + +**Lock files — tracked in git, use npm install in Docker:** +- `frontend/package-lock.json`, `backend/package-lock.json`, `demo/rag-chatbot/package-lock.json` are committed — `.gitignore` has explicit `!` exceptions for them; never remove these files from git +- NEVER use `npm ci` in Dockerfiles — lock files generated on Windows lack Linux platform-specific optional packages (`@esbuild/linux-*`, `@rollup/rollup-linux-*`); always use `npm install` (or `npm install --omit=dev`) instead +- IF "Missing: @esbuild/linux-* from lock file" → lock file generated on non-Linux; switch `npm ci` to `npm install` +- IF "npm ci can only install with an existing package-lock.json" → lock file not committed; check `.gitignore` exceptions + +**Layer cache invalidation:** +- Changing any `FROM` line invalidates ALL subsequent layer caches on Render; any previously-hidden issue (missing build-arg env vars, missing files in git) will surface on the next fresh build + +## Firebase Auth Rules + +**Architecture (fixed — do not change):** +- ALWAYS use `signInWithPopup` — NEVER `signInWithRedirect` (Chrome 115+ storage partitioning permanently broke redirect auth) +- COOP header must be `same-origin-allow-popups` — set in `backend/src/index.js` via Helmet; do not change it + +**Environment variables (Vite bakes these at build time):** +- `VITE_FIREBASE_API_KEY`, `VITE_FIREBASE_AUTH_DOMAIN`, `VITE_FIREBASE_PROJECT_ID` must exist in Render's environment variables panel BEFORE triggering a deploy +- If these vars are absent at build time, Firebase initializes with empty strings and `signInWithPopup` silently fails with `auth/popup-closed-by-user` (popup opens to malformed URL, closes immediately) +- IF `auth/popup-closed-by-user` on a fresh deploy → first check: are the three `VITE_FIREBASE_*` vars set in Render? + +**New deployment checklist (every new domain/service):** +1. Set `VITE_FIREBASE_API_KEY`, `VITE_FIREBASE_AUTH_DOMAIN`, `VITE_FIREBASE_PROJECT_ID` in Render environment +2. Add the deployment URL to Firebase Console → Authentication → Settings → Authorized domains + +**Error code reference:** +- `auth/popup-closed-by-user` = popup failed internally (check env vars + authorized domains) OR user closed it +- `auth/unauthorized-domain` = deployment URL missing from Firebase authorized domains → add it to Firebase Console +- `auth/popup-blocked` = browser blocked popup → user must allow popups for the site +- `auth/cancelled-popup-request` = two popups opened simultaneously → silently ignore (already handled) + +**Code contract (do not regress):** +- `AuthContext.loginWithProvider` must `throw new Error(cleanMessage)` — NOT `throw err` — so callers receive readable text, not raw Firebase SDK strings +- `SocialAuth.jsx` checks `firebaseConfigured` (from `frontend/src/config/firebase.js`) and shows "Social sign-in is not configured." when Firebase vars are absent + +## gstack + +Use `/browse` from gstack for all web browsing. Never use `mcp__claude-in-chrome__*` tools. +If gstack skills aren't working, run `cd ~/.claude/skills/gstack && ./setup` to build the binary and register skills. + +Available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, +/autoplan, /design-consultation, /review, /ship, /land-and-deploy, /canary, /benchmark, +/browse, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /retro, +/investigate, /document-release, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade. + diff --git a/TODOS.md b/TODOS.md new file mode 100644 index 0000000..3d7b1df --- /dev/null +++ b/TODOS.md @@ -0,0 +1,41 @@ +# TODOS + +## P2 — Auth + +### Firebase auth health check endpoint + +**What:** Add `GET /api/auth/firebase-health` that tests Firebase Admin SDK connectivity and returns a JSON status. + +**Why:** A misconfigured Firebase setup (bad project ID, expired service account, wrong VITE_FIREBASE_* vars) is only discovered when a real user attempts OAuth. A health endpoint lets Render's health check or uptime monitoring catch this before users do. + +**Pros:** Catches deploy-day auth failures early. Plugs directly into Render's health check URL. + +**Cons:** Requires Firebase Admin SDK initialization check logic. Moderate effort. + +**Context:** Emerged from the 2026-03-21 Firebase auth hardening session. The new `auth/popup-closed-by-user` error message now tells devs to check env vars — but a health endpoint would make that check automatic. Start in `backend/src/routes/auth.js`, add a `GET /health` handler that calls `getFirebaseAuth().tenantManager()` or a lightweight Admin SDK ping. + +**Effort:** M (human ~4h) → S with CC+gstack (~15 min) + +**Priority:** P2 + +**Depends on:** Nothing. Standalone addition. + +--- + +### Vitest + React Testing Library — auth unit tests + +**What:** Set up Vitest + RTL and write unit tests for: `firebaseConfigured=false` renders "not configured", each named Firebase error code (`popup-blocked`, `popup-closed-by-user`, `account-exists`, `unauthorized-domain`) produces the correct user-facing message, and `SocialAuth` buttons disable during loading. + +**Why:** Zero test coverage on auth flows. Given the auth instability history (signInWithPopup ↔ signInWithRedirect back-and-forth, multiple CVE/config fixes), a regression test suite would have caught breakage earlier. The `auth/popup-closed-by-user` message was misleading until this session — a test would have locked in the correct behavior. + +**Pros:** Locks in current behavior. Fast to write with CC+gstack once framework is in place. + +**Cons:** Adds dev dependencies (vitest, @testing-library/react). Requires initial setup. + +**Context:** No test framework configured as of 2026-03-21. Start with `frontend/src/components/auth/SocialAuth.test.jsx` and `frontend/src/context/AuthContext.test.jsx`. Mock Firebase SDK calls (`vi.mock('firebase/auth')`). + +**Effort:** M (human ~1 day) → S with CC+gstack (~20 min) + +**Priority:** P2 + +**Depends on:** Nothing. Can be added independently. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0839c1f --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.1.0 diff --git a/frontend/src/components/auth/SocialAuth.jsx b/frontend/src/components/auth/SocialAuth.jsx index 475de11..079290d 100644 --- a/frontend/src/components/auth/SocialAuth.jsx +++ b/frontend/src/components/auth/SocialAuth.jsx @@ -18,16 +18,17 @@ const GITHUB_ICON = ( ); export default function SocialAuth() { - const { loginWithProvider } = useAuth(); + const { loginWithProvider, clearError } = useAuth(); const [loadingProvider, setLoadingProvider] = useState(null); const [error, setError] = useState(null); async function handleProviderLogin(providerName) { - setError(null); setLoadingProvider(providerName); + setError(null); try { await loginWithProvider(providerName); } catch (err) { + clearError(); // prevent parent form from double-displaying this error setError(err.message); } finally { setLoadingProvider(null); @@ -47,7 +48,7 @@ export default function SocialAuth() { ) : ( <> {error && ( -
{error}
+{error}
)}