| 1 | // Google OAuth2 (per-instance). Raw via de ingebouwde fetch — geen passport-dep.
|
|---|
| 2 | // Config via env (per instance, in .env):
|
|---|
| 3 | // GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
|
|---|
| 4 | // GOOGLE_REDIRECT_URI = https://<dit-domein>/auth/google/callback
|
|---|
| 5 | // ADMIN_EMAIL = de Google-mail die owner/admin (god) is op deze instance
|
|---|
| 6 | // Niet geconfigureerd? Dan booten we gewoon door; /auth/google meldt netjes
|
|---|
| 7 | // "nog niet geconfigureerd" i.p.v. te crashen.
|
|---|
| 8 |
|
|---|
| 9 | const CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
|
|---|
| 10 | const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
|
|---|
| 11 | const REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || '';
|
|---|
| 12 |
|
|---|
| 13 | const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|---|
| 14 | const TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|---|
| 15 | const USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
|
|---|
| 16 |
|
|---|
| 17 | export function googleConfigured() {
|
|---|
| 18 | return !!(CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
|
|---|
| 19 | }
|
|---|
| 20 |
|
|---|
| 21 | export function authorizeUrl(state) {
|
|---|
| 22 | const p = new URLSearchParams({
|
|---|
| 23 | client_id: CLIENT_ID,
|
|---|
| 24 | redirect_uri: REDIRECT_URI,
|
|---|
| 25 | response_type: 'code',
|
|---|
| 26 | scope: 'openid email profile',
|
|---|
| 27 | state,
|
|---|
| 28 | access_type: 'online',
|
|---|
| 29 | prompt: 'select_account',
|
|---|
| 30 | });
|
|---|
| 31 | return `${AUTH_URL}?${p.toString()}`;
|
|---|
| 32 | }
|
|---|
| 33 |
|
|---|
| 34 | export async function exchangeCode(code) {
|
|---|
| 35 | const body = new URLSearchParams({
|
|---|
| 36 | code,
|
|---|
| 37 | client_id: CLIENT_ID,
|
|---|
| 38 | client_secret: CLIENT_SECRET,
|
|---|
| 39 | redirect_uri: REDIRECT_URI,
|
|---|
| 40 | grant_type: 'authorization_code',
|
|---|
| 41 | });
|
|---|
| 42 | const r = await fetch(TOKEN_URL, {
|
|---|
| 43 | method: 'POST',
|
|---|
| 44 | headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|---|
| 45 | body,
|
|---|
| 46 | });
|
|---|
| 47 | if (!r.ok) throw new Error(`Google token-exchange faalde: ${r.status} ${await r.text().catch(() => '')}`);
|
|---|
| 48 | return r.json(); // { access_token, id_token, ... }
|
|---|
| 49 | }
|
|---|
| 50 |
|
|---|
| 51 | // Returns { sub, email, email_verified, name, picture }.
|
|---|
| 52 | export async function fetchUserinfo(accessToken) {
|
|---|
| 53 | const r = await fetch(USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|---|
| 54 | if (!r.ok) throw new Error(`Google userinfo faalde: ${r.status}`);
|
|---|
| 55 | return r.json();
|
|---|
| 56 | }
|
|---|