feat(auth): manage Google OAuth credentials from the database (epic 6b)

- Build the Google OAuth client lazily from sysconfig (DB override, env fallback) and re-register
  when the credentials change, so the install wizard / admin can set them without a restart.
- New 'google' config group (google_client_id/secret, encrypted) on the Configuration page; the
  token-refresh reads the creds the same way.
- Public GET /auth/config exposes google_enabled; the login page hides 'Continue with Google'
  when Google OAuth isn't configured (email+password still works).
This commit is contained in:
npeter83 2026-06-20 20:04:23 +02:00
parent 2d1d5c80ac
commit d7b7acb637
9 changed files with 106 additions and 24 deletions

View file

@ -12,7 +12,7 @@ import Tabs, { usePersistedTab } from "./Tabs";
// applied together via one Save/Discard bar (consistent with the Settings page); nothing hits
// the server until Save. Group order is fixed where known so logically related settings (e.g.
// Email/SMTP) stay together.
const GROUP_ORDER = ["access", "email", "youtube", "quota", "backfill", "shorts", "batch"];
const GROUP_ORDER = ["access", "email", "youtube", "google", "quota", "backfill", "shorts", "batch"];
type SaveState = "idle" | "saving" | "saved" | "error";
export default function ConfigPanel() {

View file

@ -243,6 +243,13 @@ function AuthCard() {
const [password, setPassword] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
// Whether to offer "Continue with Google" — hidden when the instance has no Google OAuth
// configured. Optimistic (most instances have it); the rare Google-less instance just sees it
// disappear once the config loads.
const [googleEnabled, setGoogleEnabled] = useState(true);
useEffect(() => {
api.authConfig().then((c) => setGoogleEnabled(c.google_enabled)).catch(() => {});
}, []);
const [done, setDone] = useState<string>(""); // success/info message replacing the form
// Explicit demo entry (replaces the old hidden probe): a labelled email field, still gated
@ -423,12 +430,14 @@ function AuthCard() {
{t("welcome.auth.or")}
<span className="h-px flex-1 bg-border" />
</div>
<a
href="/auth/login"
className="block text-center px-4 py-2.5 rounded-xl text-sm font-semibold bg-card border border-border hover:border-accent transition"
>
{t("welcome.auth.google")}
</a>
{googleEnabled && (
<a
href="/auth/login"
className="block text-center px-4 py-2.5 rounded-xl text-sm font-semibold bg-card border border-border hover:border-accent transition"
>
{t("welcome.auth.google")}
</a>
)}
{!showDemo ? (
<button
onClick={() => setShowDemo(true)}