Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ RATE_LIMIT.md

# Unpublished paper
paper/
paper.zip
paper.zip.gstack/
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

41 changes: 41 additions & 0 deletions TODOS.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.1.0
7 changes: 4 additions & 3 deletions frontend/src/components/auth/SocialAuth.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -47,7 +48,7 @@ export default function SocialAuth() {
) : (
<>
{error && (
<p className="text-sm text-red-500 mb-3 text-center">{error}</p>
<p className="text-sm text-verdict-fail mb-3 text-center">{error}</p>
)}

<div className="space-y-3">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/context/AuthContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function AuthProvider({ children }) {
} else if (err.code === 'auth/popup-blocked') {
message = 'Sign-in popup was blocked by your browser. Please allow popups for this site and try again.';
} else if (err.code === 'auth/popup-closed-by-user') {
message = 'Sign-in window was closed. If this keeps happening, check that your browser allows popups for this site.';
message = 'Sign-in window was closed. On a new deployment, verify VITE_FIREBASE_* env vars are set and the domain is in Firebase Authorized Domains. Otherwise, allow popups for this site.';
} else if (err.code === 'auth/unauthorized-domain') {
message = 'This domain is not authorized for sign-in. Add the deployment URL to Firebase Console → Authentication → Authorized domains.';
}
Expand Down
Loading