fix(auth): Google Sign-In audience mismatch + remove per-user accent color
CI and Deploy / test (pull_request) Successful in 2m8s
CI and Deploy / deploy (pull_request) Has been skipped

Root cause of "Google authentication failed": appsettings.Development.json
had Auth:GoogleClientId set to the literal placeholder
"CHANGE_ME_GOOGLE_CLIENT_ID" while the frontend's .env.development had a
real (already-public, already-committed) client ID -- every Google ID
token's audience check failed against the backend's placeholder. Fixed
by setting the same real client ID on both sides (a client ID is a
public identifier, not a secret, safe to commit -- unlike a client
secret). Also enabled Auth:AllowRegistration in dev so the existing
Google-first self-serve-signup path (auto-create on unmatched verified
email, auto-link on matching verified email -- built during Wave 7) is
actually exercisable locally.

Wired the previously-missing Auth__MicrosoftClientId /
NEXT_PUBLIC_MICROSOFT_CLIENT_ID into docker-compose.yml/.env.example
(distinct from the existing MICROSOFT_CLIENT_ID used for Outlook mail
linking) -- Microsoft sign-in was never deployable, a leftover gap from
when it was built. Fixed a stale env-var name in the Microsoft setup
hint copy (still said REACT_APP_*, predates the Next.js migration).

Removed the per-user accent color picker entirely: it was purely
client-side (localStorage + theme.ts), never touched the backend/DB.
theme.ts now hardcodes a single ACCENT constant; themePrefs.ts drops
get/set/clearAccentColor; App.tsx and SettingsView.tsx drop the
accentColor prop threading. Dead accent-related i18n keys removed from
both locales.

Consolidated Settings' "Account" tab (duplicated GoogleAuthCard, which
already lives on the Profile page) into Profile: moved AuthStatusCard
and EmailProviderConnections there alongside the existing Google/
Microsoft auth cards, so identity/account-linking lives in one place.
Settings drops from 5 tabs to 4 and its General tab uses a consistent
SectionCard layout instead of ad-hoc per-card styling.

Verified: dotnet build/test (177/177) and npm build/test (57/57) both
green; confirmed live against a running dev server that /auth/config
now reports googleEnabled with the corrected client ID, Settings has
no accent controls, and Profile shows the consolidated auth section.
This commit is contained in:
cesnimda
2026-07-12 02:43:10 +02:00
parent 86cdafb3ef
commit 33d899c243
11 changed files with 115 additions and 255 deletions
+8 -11
View File
@@ -36,7 +36,7 @@ import { api } from "./api";
import { resolveCaptureUrl } from "./captureUrl";
import { clearAuthClientState, setAuthUserKey } from "./auth";
import AppShell, { NavItem } from "./layout/AppShell";
import { clearAccentColor, getAccentColor, getThemeModePref, setAccentColor, setThemeModePref, ThemeModePref } from "./themePrefs";
import { getThemeModePref, setThemeModePref, ThemeModePref } from "./themePrefs";
const AddJobModal = lazy(() => import("./components/AddJobModal"));
const KanbanBoard = lazy(() => import("./components/KanbanBoard"));
@@ -104,7 +104,7 @@ function PageLoader() {
return <Box sx={{ p: 4 }}><Typography variant="h6">Loading...</Typography></Box>;
}
function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMode, onThemeModeChange, accentColor, onAccentColorChange, onResetAccentColor }: { jobPageSize: 15 | 20 | 25; setJobPageSize: (n: 15 | 20 | 25) => void; jobColumns: JobTableColumns; setJobColumns: (c: JobTableColumns) => void; themeMode: ThemeModePref; onThemeModeChange: (v: ThemeModePref) => void; accentColor: string; onAccentColorChange: (v: string) => void; onResetAccentColor: () => void; }) {
function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMode, onThemeModeChange }: { jobPageSize: 15 | 20 | 25; setJobPageSize: (n: 15 | 20 | 25) => void; jobColumns: JobTableColumns; setJobColumns: (c: JobTableColumns) => void; themeMode: ThemeModePref; onThemeModeChange: (v: ThemeModePref) => void; }) {
const location = useLocation();
const navigate = useNavigate();
const { t } = useI18n();
@@ -297,7 +297,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
<Route path="/admin/users" element={<AdminUsersPage />} />
<Route path="/admin/system" element={<AdminSystemPage />} />
<Route path="/trash" element={<JobTable refreshToken={refreshToken} pageSize={jobPageSize} onPageSizeChange={setAndPersistPageSize} columns={jobColumns} onColumnsChange={setAndPersistColumns} mode="trash" />} />
<Route path="/settings" element={<SettingsView pageSize={jobPageSize} onPageSizeChange={setAndPersistPageSize} columns={jobColumns} onColumnsChange={setAndPersistColumns} themeMode={themeMode} onThemeModeChange={onThemeModeChange} accentColor={accentColor} onAccentColorChange={onAccentColorChange} onResetAccentColor={onResetAccentColor} />} />
<Route path="/settings" element={<SettingsView pageSize={jobPageSize} onPageSizeChange={setAndPersistPageSize} columns={jobColumns} onColumnsChange={setAndPersistColumns} themeMode={themeMode} onThemeModeChange={onThemeModeChange} />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Suspense>
@@ -314,19 +314,16 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
export default function App() {
const systemPrefersDark = useMediaQuery("(prefers-color-scheme: dark)", { defaultMatches: true, noSsr: true });
const [themeMode, setThemeMode] = useState<ThemeModePref>(() => getThemeModePref());
const [accentColor, setAccentColorState] = useState<string>(() => getAccentColor());
const effectiveMode: "light" | "dark" = themeMode === "light" ? "light" : themeMode === "dark" ? "dark" : systemPrefersDark ? "dark" : "light";
const theme = useMemo(() => getTheme(effectiveMode, accentColor), [effectiveMode, accentColor]);
const theme = useMemo(() => getTheme(effectiveMode), [effectiveMode]);
useEffect(() => {
const sync = () => { setThemeMode(getThemeModePref()); setAccentColorState(getAccentColor()); };
const sync = () => { setThemeMode(getThemeModePref()); };
window.addEventListener("auth-changed", sync);
return () => window.removeEventListener("auth-changed", sync);
}, []);
const onThemeModeChange = (v: ThemeModePref) => { setThemeModePref(v); setThemeMode(v); };
const onAccentColorChange = (v: string) => { setAccentColor(v); setAccentColorState(getAccentColor()); };
const onResetAccentColor = () => { clearAccentColor(); setAccentColorState(getAccentColor()); };
const [jobPageSize, setJobPageSize] = useState<15 | 20 | 25>(() => {
const raw = window.localStorage.getItem("jobPageSize");
@@ -349,14 +346,14 @@ export default function App() {
{ path: "/login", element: <LoginPage />, errorElement: <RouteErrorPage /> },
{ path: "/forgot-password", element: <ForgotPasswordPage />, errorElement: <RouteErrorPage /> },
{ path: "/reset-password", element: <ResetPasswordPage />, errorElement: <RouteErrorPage /> },
{ path: "/*", element: <Shell jobPageSize={jobPageSize} setJobPageSize={setJobPageSize} jobColumns={jobColumns} setJobColumns={setJobColumns} themeMode={themeMode} onThemeModeChange={onThemeModeChange} accentColor={accentColor} onAccentColorChange={onAccentColorChange} onResetAccentColor={onResetAccentColor} />, errorElement: <RouteErrorPage /> },
], { future: { v7_relativeSplatPath: true } }), [jobColumns, jobPageSize, themeMode, accentColor]);
{ path: "/*", element: <Shell jobPageSize={jobPageSize} setJobPageSize={setJobPageSize} jobColumns={jobColumns} setJobColumns={setJobColumns} themeMode={themeMode} onThemeModeChange={onThemeModeChange} />, errorElement: <RouteErrorPage /> },
], { future: { v7_relativeSplatPath: true } }), [jobColumns, jobPageSize, themeMode]);
return (
<ToastProvider>
<ConfirmProvider>
<PromptProvider>
<CssVarsProvider key={`${effectiveMode}:${accentColor}`} theme={theme as any} defaultMode={effectiveMode} disableTransitionOnChange>
<CssVarsProvider key={effectiveMode} theme={theme as any} defaultMode={effectiveMode} disableTransitionOnChange>
<CssBaseline enableColorScheme />
<I18nProvider>
<RouterProvider router={router} future={{ v7_startTransition: true }} />