Google Sign-In auth + email whitelist + session cookies

WHY: Doslej basic auth z deljenim AUTH_USER/AUTH_PASS — tvegan za delitev
z drugimi (urednika ipd.). Z Google Sign-In dobi vsak uporabnik svoj
account, dostop pa nadzira whitelist v env.

NEW BACKEND:
- GET  /login              — login stran z Google Sign-In gumbom (GSI popup)
- POST /auth/google/callback  — verify Google ID token + set session cookie
- GET  /auth/me            — vrne email + auth method (za frontend header)
- GET  /logout             — pobriše cookie + redirect /login
- POST /logout

AUTH FLOW:
1. Browser GET /              → check_auth() → ni session → redirect /login
2. /login                     → Google Sign-In popup (preko gsi/client.js)
3. User izbere Google account → JS pošlje credential na /auth/google/callback
4. Server verifies token (oauth2.googleapis.com/tokeninfo)
5. Email mora biti v ALLOWED_EMAILS env
6. Set HttpOnly+Secure session cookie (HMAC-SHA256, 30 dni)
7. Redirect /

SECURITY:
- Session token: base64url(email|expiry|HMAC). Server ne ranji ne hrani.
- HMAC z SESSION_SECRET (auto-derived iz AUTH_USER+AUTH_PASS če ni nastavljen)
- Cookie HttpOnly + Secure + SameSite=lax
- Token verify: aud check, iss check, exp check, email_verified check

BACKWARD COMPAT:
- HTTPBasic auth še vedno deluje (cron, scripte, API klici)
- check_auth() probaj prvo cookie, potem basic
- Brez GOOGLE_CLIENT_ID env: vse še vedno dela na basic auth

ENV VARS (treba dodati v Coolify):
- GOOGLE_CLIENT_ID=938379241163-pvb328plec2207rbtufic8u5fgb6mkn9.apps.googleusercontent.com
- ALLOWED_EMAILS=sebastjan.artic@gmail.com,ales.cadez@gmail.com
- SESSION_SECRET=<random 64-char hex> (optional — defaultni se izračuna)

FRONTEND:
- Header: 👤 email + ↪ Odjava gumb (samo ko je prijavljen)
- DOMContentLoaded fetcha /auth/me, prikaže email
This commit is contained in:
Claude
2026-05-04 12:26:53 +00:00
parent 12e8edba93
commit d2c9a48cc2
2 changed files with 324 additions and 15 deletions
+21 -3
View File
@@ -313,9 +313,15 @@
</style>
</head>
<body>
<header>
<h1><span class="accent-mark">1]</span> reels clipper</h1>
<span style="color: var(--muted); font-size: 13px;">biba.live</span>
<header style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px;">
<div style="display:flex; align-items:baseline; gap:12px;">
<h1 style="margin:0;"><span class="accent-mark">1]</span> reels clipper</h1>
<span style="color: var(--muted); font-size: 13px;">biba.live</span>
</div>
<div id="user-info" style="display:none; align-items:center; gap:10px; font-size:13px;">
<span style="color:var(--muted);">👤 <span id="user-email">…</span></span>
<a href="/logout" style="color:var(--muted); text-decoration:none; padding:4px 10px; border:1px solid #444; border-radius:4px; font-size:12px;" title="Odjava">↪ Odjava</a>
</div>
</header>
<main>
@@ -1163,6 +1169,18 @@
// Toggle pokaži/skrij že naložene + iskalnik
document.addEventListener("DOMContentLoaded", () => {
// Naloži user info v header
fetch("/auth/me").then(r => r.ok ? r.json() : null).then(u => {
if (u && u.email) {
const ui = document.getElementById("user-info");
const ue = document.getElementById("user-email");
if (ui && ue) {
ue.textContent = u.email;
ui.style.display = "flex";
}
}
}).catch(() => {});
const toggle = $("#show-uploaded");
if (toggle) toggle.addEventListener("change", refreshJobs);
const search = $("#jobs-search");