Changeset 2248cd2 in Klonkt


Ignore:
Timestamp:
06/16/2026 10:28:54 PM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
e2a8e31
Parents:
221a209
Message:

Google login configurable in-app: Admin → Settings card (no .env required)

config/google.js now reads Client ID/Secret from app_settings (env = fallback),
dynamically (no restart needed). Redirect URI derived from PUBLIC_BASE_URL.
New "Google login (listeners)" card shows the redirect URI to register + fields
for Client ID/Secret + a disconnect option. Self-hosters no longer need to
edit .env.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

Location:
src
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/config/google.js

    r221a209 r2248cd2  
    11// Google OAuth2 voor LUISTERAARS (reageren). Per-instance: de self-hoster zet
    2 // z'n EIGEN Google-client in .env. Zo hangt elke site aan z'n eigen Google Cloud
    3 // project — geen centrale afhankelijkheid, geen gedeelde aansprakelijkheid.
     2// z'n EIGEN Google-client. Zo hangt elke site aan z'n eigen Google Cloud project
     3// — geen centrale afhankelijkheid, geen gedeelde aansprakelijkheid.
    44//
    5 // Optioneel: staat dit niet ingesteld, dan is er simpelweg geen "Login met
    6 // Google"-knop en blijft de rest van de site werken. Google-login geeft NOOIT
    7 // beheerrechten — beheer gaat via het wachtwoord-account.
     5// Config-bron (in deze volgorde): app_settings (ingesteld via Beheer → Instellingen),
     6// anders de env-vars. Niet ingesteld → geen "Login met Google"-knop; de rest van
     7// de site werkt door. Google-login geeft NOOIT beheerrechten.
    88//
    9 // Config via env:
    10 //   GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
    11 //   GOOGLE_REDIRECT_URI = https://<dit-domein>/auth/google/callback
     9// De redirect-URI wordt afgeleid van PUBLIC_BASE_URL (<base>/auth/google/callback),
     10// of expliciet via GOOGLE_REDIRECT_URI. Die exacte URL moet in Google Cloud staan.
    1211
    13 const CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
    14 const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
    15 const REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || '';
     12import { getSetting } from '../services/SettingsService.js';
    1613
    1714const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
     
    1916const USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
    2017
     18// Dynamisch lezen (UI-wijziging werkt zonder herstart). app_settings wint, env = fallback.
     19function clientId() {
     20  return getSetting('google_client_id', '') || process.env.GOOGLE_CLIENT_ID || '';
     21}
     22function clientSecret() {
     23  return getSetting('google_client_secret', '') || process.env.GOOGLE_CLIENT_SECRET || '';
     24}
     25export function redirectUri() {
     26  if (process.env.GOOGLE_REDIRECT_URI) return process.env.GOOGLE_REDIRECT_URI;
     27  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     28  return base ? `${base}/auth/google/callback` : '';
     29}
     30
     31export function currentClientId() { return clientId(); } // niet-geheim, voor het formulier
     32export function clientSecretSet() { return !!clientSecret(); }
    2133export function googleConfigured() {
    22   return !!(CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
     34  return !!(clientId() && clientSecret() && redirectUri());
    2335}
    2436
    2537export function authorizeUrl(state) {
    2638  const p = new URLSearchParams({
    27     client_id: CLIENT_ID,
    28     redirect_uri: REDIRECT_URI,
     39    client_id: clientId(),
     40    redirect_uri: redirectUri(),
    2941    response_type: 'code',
    3042    scope: 'openid email profile',
     
    3951  const body = new URLSearchParams({
    4052    code,
    41     client_id: CLIENT_ID,
    42     client_secret: CLIENT_SECRET,
    43     redirect_uri: REDIRECT_URI,
     53    client_id: clientId(),
     54    client_secret: clientSecret(),
     55    redirect_uri: redirectUri(),
    4456    grant_type: 'authorization_code',
    4557  });
  • src/routes/admin-settings.js

    r221a209 r2248cd2  
    2323import { getTenancy, setTenancy, getSetting, setSetting } from '../services/SettingsService.js';
    2424import { entitlementStatus, premiumUnlocked } from '../services/PatreonService.js';
     25import { googleConfigured, redirectUri, currentClientId, clientSecretSet } from '../config/google.js';
    2526
    2627const router = express.Router();
     
    7677    hubHeroOverlay: clampOverlay(getSetting('hub_hero_overlay')),
    7778    premium: entitlementStatus(),
     79    google: {
     80      configured: googleConfigured(),
     81      redirectUri: redirectUri(),
     82      clientId: currentClientId(),
     83      secretSet: clientSecretSet(),
     84    },
    7885    success: req.query.success || null,
    7986    error: req.query.error || null,
     
    129136});
    130137
     138// Google-login (luisteraars) configureren — Client ID + Secret in app_settings.
     139// De redirect-URI leiden we af van PUBLIC_BASE_URL (zie config/google.js).
     140router.post('/google', requireGod, (req, res) => {
     141  if (req.body.clear === '1') {
     142    setSetting('google_client_id', '');
     143    setSetting('google_client_secret', '');
     144    return res.redirect('/admin/settings?success=' + encodeURIComponent('Google-login losgekoppeld'));
     145  }
     146  setSetting('google_client_id', (req.body.google_client_id || '').toString().trim());
     147  // Secret alleen overschrijven als er een nieuwe waarde is ingevoerd (leeg = laat staan).
     148  const secret = (req.body.google_client_secret || '').toString().trim();
     149  if (secret) setSetting('google_client_secret', secret);
     150  res.redirect('/admin/settings?success=' + encodeURIComponent('Google-login opgeslagen'));
     151});
     152
    131153export default router;
  • src/views/pages/admin-settings.ejs

    r221a209 r2248cd2  
    6363  </section>
    6464  <% } %>
     65
     66  <%# Google-login voor luisteraars (reageren) — per-site eigen Google-client. %>
     67  <section class="set-card" style="margin-top:1rem">
     68    <h2>Google-login (luisteraars)</h2>
     69    <p class="set-help">Laat bezoekers met Google inloggen om te reageren — met je <strong>eigen</strong>
     70      Google Cloud-client. Maak er een aan op
     71      <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener">console.cloud.google.com</a>
     72      (OAuth client-ID, type "Web application").
     73      <% if (google && google.configured) { %><strong>Status: ingesteld&nbsp;✓</strong><% } else { %>Nog niet ingesteld.<% } %></p>
     74
     75    <% if (google && google.redirectUri) { %>
     76      <p class="set-help" style="margin-bottom:.7rem">Zet deze <strong>exacte</strong> redirect-URI in je Google-client
     77        (Authorized redirect URIs):<br><code style="user-select:all"><%= google.redirectUri %></code></p>
     78    <% } else { %>
     79      <p class="set-help" style="color:var(--accent)">Stel eerst <code>PUBLIC_BASE_URL</code> in — daar wordt de redirect-URI van afgeleid.</p>
     80    <% } %>
     81
     82    <form method="post" action="/admin/settings/google" class="set-form">
     83      <label class="set-field">
     84        <span>Client ID</span>
     85        <input type="text" name="google_client_id" value="<%= google ? google.clientId : '' %>" placeholder="123-abc.apps.googleusercontent.com" autocomplete="off">
     86      </label>
     87      <label class="set-field">
     88        <span>Client Secret <% if (google && google.secretSet) { %><small style="font-weight:400">— ingesteld; leeg laten = ongewijzigd</small><% } %></span>
     89        <input type="password" name="google_client_secret" placeholder="<%= (google && google.secretSet) ? '•••••••• (ingesteld)' : 'GOCSPX-…' %>" autocomplete="off">
     90      </label>
     91      <div style="display:flex; gap:.5rem; flex-wrap:wrap">
     92        <button type="submit" class="btn btn-primary">Opslaan</button>
     93        <% if (google && (google.clientId || google.secretSet)) { %>
     94          <button type="submit" name="clear" value="1" class="btn">Loskoppelen</button>
     95        <% } %>
     96      </div>
     97    </form>
     98    <% if (typeof premiumEnabled !== 'undefined' && premiumEnabled && !(premium && premium.premium)) { %>
     99      <p class="set-help" style="margin-top:.7rem; color:var(--accent)">Premium-laag staat aan zonder Patreon — de Google-knop blijft verborgen tot premium ontgrendeld is.</p>
     100    <% } %>
     101  </section>
    65102
    66103  <% if (tenancy === 'hub') { %>
Note: See TracChangeset for help on using the changeset viewer.