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:
parent
2d1d5c80ac
commit
d7b7acb637
9 changed files with 106 additions and 24 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"access": "Zugang",
|
||||
"email": "E-Mail / SMTP",
|
||||
"youtube": "YouTube-API",
|
||||
"google": "Google-Anmeldung",
|
||||
"quota": "Kontingent",
|
||||
"backfill": "Nachladen (Backfill)",
|
||||
"shorts": "Shorts-Prüfung",
|
||||
|
|
@ -25,6 +26,8 @@
|
|||
"enrich_batch_size": { "label": "Anreicherungs-Stapelgröße", "hint": "videos.list-IDs pro Aufruf (YouTube begrenzt auf 50)." },
|
||||
"autotag_title_sample": { "label": "Auto-Tag-Titelstichprobe", "hint": "Pro Kanal abgetastete aktuelle Videotitel zur Spracherkennung." },
|
||||
"youtube_api_key": { "label": "YouTube-API-Schlüssel", "hint": "Optional. Für öffentliche Lesezugriffe (Kanäle/Videos), damit gemeinsames Nachladen nicht vom OAuth eines Nutzers abhängt. Verschlüsselt gespeichert; nur schreibbar." },
|
||||
"google_client_id": { "label": "Google-Client-ID", "hint": "OAuth-2.0-Client-ID für die Google-Anmeldung. Verschlüsselt gespeichert; nur schreibbar. Beide Google-Felder leer lassen, um die Google-Anmeldung zu deaktivieren (E-Mail+Passwort funktioniert weiter)." },
|
||||
"google_client_secret": { "label": "Google-Client-Secret", "hint": "OAuth-2.0-Client-Secret. Verschlüsselt gespeichert; nur schreibbar." },
|
||||
"allow_registration": { "label": "Registrierung erlauben", "hint": "Wenn aktiviert, kann jeder eine E-Mail+Passwort-Registrierung einreichen. Für die Anmeldung sind weiterhin E-Mail-Bestätigung und Admin-Freigabe nötig." }
|
||||
},
|
||||
"save": "Speichern",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"access": "Access",
|
||||
"email": "Email / SMTP",
|
||||
"youtube": "YouTube API",
|
||||
"google": "Google sign-in",
|
||||
"quota": "Quota",
|
||||
"backfill": "Backfill",
|
||||
"shorts": "Shorts probe",
|
||||
|
|
@ -25,6 +26,8 @@
|
|||
"enrich_batch_size": { "label": "Enrichment batch size", "hint": "videos.list ids per call (YouTube caps this at 50)." },
|
||||
"autotag_title_sample": { "label": "Auto-tag title sample", "hint": "Recent video titles sampled per channel for language detection." },
|
||||
"youtube_api_key": { "label": "YouTube API key", "hint": "Optional. Used for public reads (channels/videos) so shared backfill doesn't depend on a user's OAuth. Stored encrypted; write-only." },
|
||||
"google_client_id": { "label": "Google client ID", "hint": "OAuth 2.0 client ID for Sign in with Google. Stored encrypted; write-only. Leave both Google fields empty to disable Google sign-in (email+password still works)." },
|
||||
"google_client_secret": { "label": "Google client secret", "hint": "OAuth 2.0 client secret. Stored encrypted; write-only." },
|
||||
"allow_registration": { "label": "Allow registration", "hint": "When on, anyone can submit an email+password registration. They still need to verify their email and be approved by an admin before they can sign in." }
|
||||
},
|
||||
"save": "Save",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"access": "Hozzáférés",
|
||||
"email": "E-mail / SMTP",
|
||||
"youtube": "YouTube API",
|
||||
"google": "Google bejelentkezés",
|
||||
"quota": "Kvóta",
|
||||
"backfill": "Letöltés (backfill)",
|
||||
"shorts": "Shorts-vizsgálat",
|
||||
|
|
@ -25,6 +26,8 @@
|
|||
"enrich_batch_size": { "label": "Gazdagítási kötegméret", "hint": "videos.list azonosítók hívásonként (a YouTube 50-ben maximálja)." },
|
||||
"autotag_title_sample": { "label": "Auto-címke címmintavétel", "hint": "Csatornánként ennyi friss videócímet mintázunk a nyelvfelismeréshez." },
|
||||
"youtube_api_key": { "label": "YouTube API-kulcs", "hint": "Opcionális. Publikus olvasásokhoz (csatornák/videók), hogy a megosztott letöltés ne függjön egy felhasználó OAuth-jától. Titkosítva tárolva; csak írható." },
|
||||
"google_client_id": { "label": "Google kliens-azonosító", "hint": "OAuth 2.0 kliens-azonosító a Google-bejelentkezéshez. Titkosítva tárolva; csak írható. Hagyd üresen mindkét Google-mezőt a Google-bejelentkezés kikapcsolásához (az e-mail+jelszó továbbra is működik)." },
|
||||
"google_client_secret": { "label": "Google kliens-titok", "hint": "OAuth 2.0 kliens-titok. Titkosítva tárolva; csak írható." },
|
||||
"allow_registration": { "label": "Regisztráció engedélyezése", "hint": "Bekapcsolva bárki beküldhet email+jelszó regisztrációt. Belépéshez továbbra is email-igazolás és admin-jóváhagyás szükséges." }
|
||||
},
|
||||
"save": "Mentés",
|
||||
|
|
|
|||
|
|
@ -635,6 +635,9 @@ export const api = {
|
|||
body: JSON.stringify({ revalidate_batch: revalidateBatch }),
|
||||
}),
|
||||
|
||||
// Public: which sign-in options this instance offers (e.g. hide Google when not configured).
|
||||
authConfig: (): Promise<{ google_enabled: boolean; allow_registration: boolean }> =>
|
||||
req("/auth/config"),
|
||||
// --- auth: email + password (errors handled inline via quiet) ---
|
||||
register: (email: string, password: string): Promise<{ status: string }> =>
|
||||
req("/auth/register", { method: "POST", body: JSON.stringify({ email, password }) }, { quiet: true }),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue