From 65ec65213512519a1104a628c6e74b4a1fcb4ff0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 01:38:29 +0000 Subject: [PATCH] Refactor(auth): one auth domain per deployment VITE_FIREBASE_AUTH_DOMAIN is each environment's own host, served by the /__/auth proxy; the PWA-only VITE_FIREBASE_PWA_AUTH_DOMAIN and utils/auth-domain.js go. Every OAuth provider lists the handler URL per environment (Google and GitHub both take several redirect URIs). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco --- .env.example | 16 ++++++---------- api/AGENTS.md | 11 +++++------ frontend/AGENTS.md | 3 +-- frontend/firebase-init.js | 12 +----------- frontend/utils/auth-domain.js | 12 ------------ tests/auth-domain.test.js | 36 ----------------------------------- 6 files changed, 13 insertions(+), 77 deletions(-) delete mode 100644 frontend/utils/auth-domain.js delete mode 100644 tests/auth-domain.test.js diff --git a/.env.example b/.env.example index 6c8f9589..451e3d8b 100644 --- a/.env.example +++ b/.env.example @@ -38,18 +38,14 @@ VITE_PULSE_BEACON_URL="" VITE_CURL_IPV4_DOMAIN="" VITE_CURL_IPV6_DOMAIN="" VITE_CURL_IPV64_DOMAIN="" -# Firebase Auth — optional; sign-in is offered when API key, auth domain and -# project id are all set. VITE_FIREBASE_AUTH_DOMAIN is the browsers' login -# domain (e.g. "auth.ipcheck.ing"); each OAuth provider's callback URL is -# https:///__/auth/handler. -# VITE_FIREBASE_PWA_AUTH_DOMAIN — optional, installed PWA only: the site's own -# host (e.g. "ipcheck.ing"), served by the /__/auth proxy. Add it to Firebase -# Authentication → Authorized domains, and https:///__/auth/handler to -# every OAuth provider (Google: an extra redirect URI; GitHub: an extra -# redirect URI, or wildcard matching on the apex callback). +# Firebase Auth — optional; sign-in is offered when all three are set. +# VITE_FIREBASE_AUTH_DOMAIN is this deployment's own host (e.g. "ipcheck.ing"): +# the frontend server and Vite proxy /__/auth/* to Firebase for it, which the +# installed PWA needs. Add the host to Firebase Authentication → Authorized +# domains and https:///__/auth/handler to every OAuth provider's +# redirect URIs (one per environment, or GitHub's wildcard matching). VITE_FIREBASE_API_KEY="" VITE_FIREBASE_AUTH_DOMAIN="" -VITE_FIREBASE_PWA_AUTH_DOMAIN="" VITE_FIREBASE_PROJECT_ID="" # Google Analytics VITE_GOOGLE_ANALYTICS_ID="" diff --git a/api/AGENTS.md b/api/AGENTS.md index da25f5dc..3dbe0b9f 100644 --- a/api/AGENTS.md +++ b/api/AGENTS.md @@ -161,12 +161,11 @@ upstreams; those get only what's explicitly needed. `frontend-server.js` (production) and `vite.config.js` (dev) reverse-proxy `/__/auth/*` to `.firebaseapp.com` (`common/firebase-auth-proxy.js`) -when `VITE_FIREBASE_PROJECT_ID` is set. `VITE_FIREBASE_PWA_AUTH_DOMAIN` points -the installed PWA at the site's own host, so its sign-in handler runs -first-party — inside the PWA's webview and Safari's storage partition — and -`getRedirectResult` finds the result; browsers keep the shared -`VITE_FIREBASE_AUTH_DOMAIN`. Every OAuth provider must accept -`https:///__/auth/handler` for both domains. Not an `/api` route: no +when `VITE_FIREBASE_PROJECT_ID` is set, so `VITE_FIREBASE_AUTH_DOMAIN` can be +the deployment's own host and the sign-in handler runs first-party — inside +the installed PWA's webview and Safari's storage partition, where +`getRedirectResult` finds the result. Every OAuth provider must accept +`https:///__/auth/handler` per environment. Not an `/api` route: no guards, caching or per-request logs. ## Edge caching diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index ec8a1dc3..0fc03ffb 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -14,8 +14,7 @@ over copied-in shadcn-vue primitives. No TypeScript, no `dark:` dual pairs. frontend/ ├── App.vue / main.js / store.js / router/ / locales/ / style/style.css ├── firebase-init.js ← env-gated lazy Firebase Auth (boot path: utils/auth-hint.js; -│ PWA / blocked-popup redirect sign-in: utils/auth-redirect.js; -│ the PWA's own auth domain: utils/auth-domain.js) +│ PWA / blocked-popup redirect sign-in: utils/auth-redirect.js) ├── sentry-init.js ← env-gated Sentry (see "Error monitoring") ├── data/ ← static config (tools registry drives pages+cards+nav) ├── lib/ · utils/ · composables/ ← see "Helper placement" diff --git a/frontend/firebase-init.js b/frontend/firebase-init.js index 200c8372..a7469b37 100644 --- a/frontend/firebase-init.js +++ b/frontend/firebase-init.js @@ -1,20 +1,10 @@ // Firebase Auth bootstrap — env-gated AND lazy: the SDK chunk only loads on // the first loadFirebaseAuth() call (signed-in boot, sign-in click, or the // background auth probe), never on the visitor-critical path. -import { resolveAuthDomain } from './utils/auth-domain.js'; -import { isRunningAsPwa } from './utils/pwa.js'; - const env = import.meta.env ?? {}; const isFireBaseSet = !!env.VITE_FIREBASE_API_KEY && !!env.VITE_FIREBASE_AUTH_DOMAIN && !!env.VITE_FIREBASE_PROJECT_ID; -// Inputs for utils/auth-domain.js, read on demand (display mode needs `window`). -const authDomainInputs = () => ({ - runningAsPwa: isRunningAsPwa(), - authDomain: env.VITE_FIREBASE_AUTH_DOMAIN, - pwaAuthDomain: env.VITE_FIREBASE_PWA_AUTH_DOMAIN, -}); - let authModulePromise = null; // Resolves to the firebase/auth namespace plus the initialized `auth` @@ -35,7 +25,7 @@ const loadFirebaseAuth = () => { ...authModule, auth: authModule.getAuth(appModule.initializeApp({ apiKey: env.VITE_FIREBASE_API_KEY, - authDomain: resolveAuthDomain(authDomainInputs()), + authDomain: env.VITE_FIREBASE_AUTH_DOMAIN, projectId: env.VITE_FIREBASE_PROJECT_ID, })), }; diff --git a/frontend/utils/auth-domain.js b/frontend/utils/auth-domain.js deleted file mode 100644 index 77191e67..00000000 --- a/frontend/utils/auth-domain.js +++ /dev/null @@ -1,12 +0,0 @@ -// Which Firebase auth domain this session signs in through: browsers keep -// the shared default, the installed PWA may use VITE_FIREBASE_PWA_AUTH_DOMAIN -// (why: api/AGENTS.md "Firebase Auth handler proxy"). - -const clean = (value) => (typeof value === 'string' ? value.trim() : ''); - -// The domain to initialise Firebase with; a blank PWA value means unset. -export const resolveAuthDomain = ({ runningAsPwa, authDomain, pwaAuthDomain }) => { - const fallback = clean(authDomain); - if (!runningAsPwa) return fallback; - return clean(pwaAuthDomain) || fallback; -}; diff --git a/tests/auth-domain.test.js b/tests/auth-domain.test.js deleted file mode 100644 index 0a59905e..00000000 --- a/tests/auth-domain.test.js +++ /dev/null @@ -1,36 +0,0 @@ -// Tests for the auth-domain choice (frontend/utils/auth-domain.js): browsers -// keep the shared default domain, the installed PWA may use its own, and -// GitHub (one callback, on the default domain) drops out when it does. - -import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; -import { resolveAuthDomain } from '../frontend/utils/auth-domain.js'; - -const DEFAULT = 'auth.example.com'; -const SITE = 'example.com'; - -describe('resolveAuthDomain', () => { - it('uses the default domain in a browser tab, even with a PWA domain set', () => { - assert.equal(resolveAuthDomain({ runningAsPwa: false, authDomain: DEFAULT, pwaAuthDomain: SITE }), DEFAULT); - }); - - it('uses the PWA domain inside the installed PWA', () => { - assert.equal(resolveAuthDomain({ runningAsPwa: true, authDomain: DEFAULT, pwaAuthDomain: SITE }), SITE); - }); - - it('falls back to the default in the PWA when the PWA domain is unset or blank', () => { - assert.equal(resolveAuthDomain({ runningAsPwa: true, authDomain: DEFAULT }), DEFAULT); - assert.equal(resolveAuthDomain({ runningAsPwa: true, authDomain: DEFAULT, pwaAuthDomain: '' }), DEFAULT); - assert.equal(resolveAuthDomain({ runningAsPwa: true, authDomain: DEFAULT, pwaAuthDomain: ' ' }), DEFAULT); - }); - - it('trims both values', () => { - assert.equal(resolveAuthDomain({ runningAsPwa: false, authDomain: ` ${DEFAULT} ` }), DEFAULT); - assert.equal(resolveAuthDomain({ runningAsPwa: true, authDomain: DEFAULT, pwaAuthDomain: ` ${SITE}\n` }), SITE); - }); - - it('returns an empty string when nothing is configured', () => { - assert.equal(resolveAuthDomain({ runningAsPwa: false }), ''); - assert.equal(resolveAuthDomain({ runningAsPwa: true }), ''); - }); -});