| [9e27d64] | 1 | // 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.
|
|---|
| [32cc601] | 4 | //
|
|---|
| [9e27d64] | 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.
|
|---|
| [32cc601] | 8 | //
|
|---|
| 9 | // Config via env:
|
|---|
| [9e27d64] | 10 | // GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
|
|---|
| 11 | // GOOGLE_REDIRECT_URI = https://<dit-domein>/auth/google/callback
|
|---|
| [32cc601] | 12 |
|
|---|
| [9e27d64] | 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 || '';
|
|---|
| [32cc601] | 16 |
|
|---|
| [9e27d64] | 17 | const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|---|
| 18 | const TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|---|
| 19 | const USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
|
|---|
| [32cc601] | 20 |
|
|---|
| [9e27d64] | 21 | export function googleConfigured() {
|
|---|
| 22 | return !!(CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
|
|---|
| [c80e78b] | 23 | }
|
|---|
| 24 |
|
|---|
| [9e27d64] | 25 | export function authorizeUrl(state) {
|
|---|
| 26 | const p = new URLSearchParams({
|
|---|
| 27 | client_id: CLIENT_ID,
|
|---|
| 28 | redirect_uri: REDIRECT_URI,
|
|---|
| 29 | response_type: 'code',
|
|---|
| 30 | scope: 'openid email profile',
|
|---|
| 31 | state,
|
|---|
| 32 | access_type: 'online',
|
|---|
| 33 | prompt: 'select_account',
|
|---|
| 34 | });
|
|---|
| 35 | return `${AUTH_URL}?${p.toString()}`;
|
|---|
| [32cc601] | 36 | }
|
|---|
| 37 |
|
|---|
| [9e27d64] | 38 | export async function exchangeCode(code) {
|
|---|
| 39 | const body = new URLSearchParams({
|
|---|
| 40 | code,
|
|---|
| 41 | client_id: CLIENT_ID,
|
|---|
| 42 | client_secret: CLIENT_SECRET,
|
|---|
| 43 | redirect_uri: REDIRECT_URI,
|
|---|
| 44 | grant_type: 'authorization_code',
|
|---|
| 45 | });
|
|---|
| 46 | const r = await fetch(TOKEN_URL, {
|
|---|
| 47 | method: 'POST',
|
|---|
| 48 | headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|---|
| 49 | body,
|
|---|
| [c80e78b] | 50 | });
|
|---|
| [9e27d64] | 51 | if (!r.ok) throw new Error(`Google token-exchange faalde: ${r.status}`);
|
|---|
| 52 | return r.json(); // { access_token, id_token, ... }
|
|---|
| [c80e78b] | 53 | }
|
|---|
| 54 |
|
|---|
| [9e27d64] | 55 | // Returns { sub, email, email_verified, name, picture }.
|
|---|
| 56 | export async function fetchUserinfo(accessToken) {
|
|---|
| 57 | const r = await fetch(USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|---|
| 58 | if (!r.ok) throw new Error(`Google userinfo faalde: ${r.status}`);
|
|---|
| 59 | return r.json();
|
|---|
| [c80e78b] | 60 | }
|
|---|