From ebe2c36ae33c21b6e8eca9c65e041b498b8d9731 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 17:04:44 +0000 Subject: [PATCH] Fix(auth): offer GitHub sign-in in the installed PWA too GitHub OAuth apps now take several redirect URIs, with optional wildcard matching, so the PWA's own auth domain can be registered there like Google's; the menu no longer hides GitHub when the PWA signs in through a different domain (signInProvidersFor and the store getter go). The operator notes name both registrations. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco --- .env.example | 5 +++-- api/AGENTS.md | 6 +++--- frontend/components/UserMenu.vue | 5 +---- frontend/firebase-init.js | 2 +- frontend/store.js | 6 +----- frontend/utils/auth-domain.js | 18 +++--------------- tests/auth-domain.test.js | 23 +---------------------- 7 files changed, 13 insertions(+), 52 deletions(-) diff --git a/.env.example b/.env.example index 8b448972..6c8f9589 100644 --- a/.env.example +++ b/.env.example @@ -44,8 +44,9 @@ VITE_CURL_IPV64_DOMAIN="" # 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 -# Google's OAuth client; GitHub (one callback) is then hidden in the PWA. +# 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). VITE_FIREBASE_API_KEY="" VITE_FIREBASE_AUTH_DOMAIN="" VITE_FIREBASE_PWA_AUTH_DOMAIN="" diff --git a/api/AGENTS.md b/api/AGENTS.md index 1ce37c0a..da25f5dc 100644 --- a/api/AGENTS.md +++ b/api/AGENTS.md @@ -165,9 +165,9 @@ 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` (GitHub's single callback lives there, so the PWA -then offers Google only). Not an `/api` route: no guards, caching or -per-request logs. +`VITE_FIREBASE_AUTH_DOMAIN`. Every OAuth provider must accept +`https:///__/auth/handler` for both domains. Not an `/api` route: no +guards, caching or per-request logs. ## Edge caching diff --git a/frontend/components/UserMenu.vue b/frontend/components/UserMenu.vue index 03651041..e858cd8e 100644 --- a/frontend/components/UserMenu.vue +++ b/frontend/components/UserMenu.vue @@ -82,8 +82,7 @@ {{ t('user.SignInWithGoogle') }} - + {{ t('user.SignInWithGithub') }} @@ -149,8 +148,6 @@ const remoteUserInfo = computed(() => store.remoteUserInfo); const remoteUserInfoFetched = computed(() => store.remoteUserInfoFetched); // Sign-in methods attached to this account. const linkedProviders = computed(() => store.linkedProviders); -// Sign-in methods offered here (no GitHub in a PWA on its own auth domain). -const signInProviders = computed(() => store.signInProviders); // Level Badge Color: mapped to semantic token, keep each level color distinction const levelBadgeClass = computed(() => { diff --git a/frontend/firebase-init.js b/frontend/firebase-init.js index 86813f5c..200c8372 100644 --- a/frontend/firebase-init.js +++ b/frontend/firebase-init.js @@ -48,4 +48,4 @@ const loadFirebaseAuth = () => { return authModulePromise; }; -export { isFireBaseSet, loadFirebaseAuth, authDomainInputs }; +export { isFireBaseSet, loadFirebaseAuth }; diff --git a/frontend/store.js b/frontend/store.js index f4a1d2f3..e1f813e4 100644 --- a/frontend/store.js +++ b/frontend/store.js @@ -1,7 +1,6 @@ // store.js import { defineStore } from 'pinia'; -import { isFireBaseSet, loadFirebaseAuth, authDomainInputs } from './firebase-init.js'; -import { signInProvidersFor } from './utils/auth-domain.js'; +import { isFireBaseSet, loadFirebaseAuth } from './firebase-init.js'; import { writeAuthHint } from './utils/auth-hint.js'; import { markPendingRedirectSignIn, shouldFallBackToRedirect, takePendingRedirectSignIn } from './utils/auth-redirect.js'; import { isRunningAsPwa } from './utils/pwa.js'; @@ -112,9 +111,6 @@ export const useMainStore = defineStore('main', { }; }); }, - // Provider keys the sign-in menu offers: GitHub drops out in an installed - // PWA that signs in through its own auth domain (utils/auth-domain.js). - signInProviders: () => signInProvidersFor(authDomainInputs()), // Per-feature "monthly quota exhausted" booleans, derived from the // /api/getuserinfo quota snapshot in remoteUserInfo. Frontend first line // only — the backend enforces the same limits authoritatively; absent diff --git a/frontend/utils/auth-domain.js b/frontend/utils/auth-domain.js index 3e6b3caa..77191e67 100644 --- a/frontend/utils/auth-domain.js +++ b/frontend/utils/auth-domain.js @@ -1,11 +1,6 @@ -// Which Firebase auth domain this session signs in through, and which -// providers that leaves: browsers keep the shared default, the installed PWA -// may use VITE_FIREBASE_PWA_AUTH_DOMAIN (why: api/AGENTS.md "Firebase Auth -// handler proxy"). GitHub's single callback URL is on the default domain, so a -// PWA on another domain offers only Google. - -const ALL_PROVIDERS = ['google', 'github']; -const DEFAULT_DOMAIN_ONLY = new Set(['github']); +// 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() : ''); @@ -15,10 +10,3 @@ export const resolveAuthDomain = ({ runningAsPwa, authDomain, pwaAuthDomain }) = if (!runningAsPwa) return fallback; return clean(pwaAuthDomain) || fallback; }; - -// Sign-in provider keys (store.js SIGN_IN_PROVIDERS) usable on that domain. -export const signInProvidersFor = (inputs) => { - const domain = resolveAuthDomain(inputs); - if (domain === clean(inputs.authDomain)) return [...ALL_PROVIDERS]; - return ALL_PROVIDERS.filter((key) => !DEFAULT_DOMAIN_ONLY.has(key)); -}; diff --git a/tests/auth-domain.test.js b/tests/auth-domain.test.js index 676ea0eb..0a59905e 100644 --- a/tests/auth-domain.test.js +++ b/tests/auth-domain.test.js @@ -4,7 +4,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { resolveAuthDomain, signInProvidersFor } from '../frontend/utils/auth-domain.js'; +import { resolveAuthDomain } from '../frontend/utils/auth-domain.js'; const DEFAULT = 'auth.example.com'; const SITE = 'example.com'; @@ -34,24 +34,3 @@ describe('resolveAuthDomain', () => { assert.equal(resolveAuthDomain({ runningAsPwa: true }), ''); }); }); - -describe('signInProvidersFor', () => { - it('offers both providers in a browser tab', () => { - assert.deepEqual(signInProvidersFor({ runningAsPwa: false, authDomain: DEFAULT, pwaAuthDomain: SITE }), ['google', 'github']); - }); - - it('offers both providers in the PWA when it shares the default domain', () => { - assert.deepEqual(signInProvidersFor({ runningAsPwa: true, authDomain: DEFAULT }), ['google', 'github']); - assert.deepEqual(signInProvidersFor({ runningAsPwa: true, authDomain: DEFAULT, pwaAuthDomain: ' ' }), ['google', 'github']); - assert.deepEqual(signInProvidersFor({ runningAsPwa: true, authDomain: DEFAULT, pwaAuthDomain: ` ${DEFAULT}` }), ['google', 'github']); - }); - - it('drops GitHub in the PWA when it signs in through a different domain', () => { - assert.deepEqual(signInProvidersFor({ runningAsPwa: true, authDomain: DEFAULT, pwaAuthDomain: SITE }), ['google']); - }); - - it('returns a fresh array each call', () => { - const inputs = { runningAsPwa: false, authDomain: DEFAULT }; - assert.notEqual(signInProvidersFor(inputs), signInProvidersFor(inputs)); - }); -});