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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
This commit is contained in:
Claude committed 2026-10-05 17:04:44 +00:00
1 parent 435d9f6413
commit ebe2c36ae3
7 files changed
+13 -52

No files matched your search

+3 -2
View File
@@ -44,8 +44,9 @@ VITE_CURL_IPV64_DOMAIN=""
# https://<that domain>/__/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://<it>/__/auth/handler to
# Google's OAuth client; GitHub (one callback) is then hidden in the PWA.
# Authentication → Authorized domains, and https://<it>/__/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=""
+3 -3
View File
@@ -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://<host>/__/auth/handler` for both domains. Not an `/api` route: no
guards, caching or per-request logs.
## Edge caching
+1 -4
View File
@@ -82,8 +82,7 @@
<Icon icon="ri:google-line" />
<span>{{ t('user.SignInWithGoogle') }}</span>
</DropdownMenuItem>
<DropdownMenuItem v-if="signInProviders.includes('github')" class="cursor-pointer"
@select="store.signInWithGithub">
<DropdownMenuItem class="cursor-pointer" @select="store.signInWithGithub">
<Icon icon="ri:github-line" />
<span>{{ t('user.SignInWithGithub') }}</span>
</DropdownMenuItem>
@@ -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(() => {
+1 -1
View File
@@ -48,4 +48,4 @@ const loadFirebaseAuth = () => {
return authModulePromise;
};
export { isFireBaseSet, loadFirebaseAuth, authDomainInputs };
export { isFireBaseSet, loadFirebaseAuth };
+1 -5
View File
@@ -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
+3 -15
View File
@@ -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));
};
+1 -22
View File
@@ -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));
});
});