Design: Autentizace uživatelů — Magic Link only
Intent: rozšíření docs/intent/auth-and-roles.md o rodičovský přístup a sjednocení staff loginu. Author: architect (agent) + Tomáš (PO rozhodnutí) Date: 2026-05-14 Status: Accepted
Problém
Aktuální auth (Google OAuth + email/heslo fallback) má tři praktické problémy: (1) staff reálně používá M365, ne Google, takže Google tlačítko je nepoužívané; (2) rodičovský portál se spouští do 3 měsíců a nemá login flow; (3) password fallback drží attack surface, který nikdo nevyužívá. Zároveň je provider rozhodnutí na úrovni školy stále pohyblivé (M365 ↔ Google Workspace), takže cementovat ho do auth vrstvy je riziko.
Klíčové constraints
- Stack: Next.js 16, Supabase Auth (Cloud, Free → Pro upgrade plánován), Vercel.
- Sólo dev, ~10 učitelů, ~100 rodičů, později ~150 žáků. Každý OAuth provider = další tenant config + údržba.
- Provider rozhodnutí pohyblivé (M365 dnes, Google možná zítra) — auth nesmí být na něm závislá.
- GDPR / pending invariant (INV-AUTH-03): nový účet vždy
pending, ručně schvaluje Tomáš. - Žádná produkční data dnes (INV-META-01) → můžeme refactorovat auth bez migrace dat.
- Tomáš preferuje KISS / speed-to-use; explicitně chce „magic link only teď, OAuth později podle situace".
Varianty
A — Iterativní fix (drž Google + přidej magic link rodičům)
Drž Google OAuth pro staff, přidej Magic Link pro rodiče.
- ✅ Minimální změna kódu.
- ❌ Drží password fallback (chce pryč) a Google provider, který staff reálně nepoužívá (mají M365).
- ❌ Neřeší nesoulad mezi tím, co existuje, a tím, jak staff opravdu pracuje.
B — Unified login: MS365 (staff) + Magic Link (rodič)
Tlačítko „Microsoft" pro staff, e-mail input pro rodiče. Google později.
- ✅ Staff jednoklik login, auto-offboarding přes M365 admin.
- ❌ Vyžaduje Azure App registration, Entra admin přístup, secrets v Vercel.
- ❌ Cementuje M365 — kdyby Tomáš přešel na Google, retrofit (re-link identity).
- ❌ Dva paralelní login flow zvětšují attack surface.
C — Magic Link only (ZVOLENO)
Pro všechny (staff, rodiče, později žáci) jen Magic Link. Žádný OAuth provider teď. Až bude měřitelná potřeba, přidá se OAuth tlačítko vedle magic linku — bez breaking change.
- ✅ Setup ~zero. Enable Email provider v Supabase, default mailer (Pro upgrade). Žádný OAuth.
- ✅ Provider-agnostic. Tomáš si M365 ↔ Google přepne kdykoli bez dotyku na auth.
- ✅ Jeden code path, jeden flow.
- ✅ Staff friction je menší, než zní — Supabase JWT 1h + refresh token 1 rok + sliding refresh → klik magic link reálně jen při prvním přihlášení nebo po explicit logout.
- ✅ Defense-in-depth (middleware + RLS) funguje bez závislosti na provideru.
- ⚠️ Žádný cloud-side auto-offboarding (M365 admin suspend ≠ Sofie). Mitigace: ruční remove v Supabase, pro 10 učitelů triviální.
- ⚠️ Závislost na e-mailové doručitelnosti.
- ❌ Ztratíme „Sign in with Google" pro budoucí Drive/Calendar integraci — ale ty neexistují, doplníme provider až přijdou.
Rozhodnutí
Varianta C — Magic Link only. Cílový stav:
| Role | Login |
|---|---|
| Staff (učitel/ředitel/kancelář) | Magic Link na školní e-mail (@sofie.education) |
| Rodič | Magic Link na e-mail v guardians |
| Žák (Fáze 3+) | Magic Link, později doplnit Google Workspace for Education SSO |
| Apple Sign-In | Zamítnuto ($99/rok + JWT overhead, magic link pokryje stejný UX) |
| Dev / demo | Quick-login za KOSMO_DEMO_MODE, passwordless přes Supabase Admin API (admin.generateLink) |
Mailer: default Supabase (upgrade na Supabase Pro $25/měsíc před prod rolloutem rodičům — Free tier 4 maily/h nestačí). Custom SMTP / Resend integraci neimplementujeme.
Detaily kroků v plan filu / následném PR. Související ADR: 2026-05-14-auth-magic-link-only.md.
Budoucí rozšíření (až bude reálná potřeba)
- MS Azure SSO pro staff — pokud zůstaneme na M365 a začne vadit ruční offboarding: 1 PR, Azure provider + tlačítko nad magic-link input. Existující účty se napárují přes shodný e-mail (Supabase auto-link).
- Google SSO pro staff — pokud Tomáš migruje na Google Workspace: stejný postup. Magic link bridge řeší re-link identity automaticky.
- Google SSO pro žáky (Fáze 3+) — žákovský portál + Google Workspace for Education, vlastní intent brief.
Pořadí „magic link první, OAuth druhý" je správné — opačné pořadí by vyžadovalo gymnastiku s re-link identity.
Kritické soubory
| Soubor | Linie | Co se mění |
|---|---|---|
| web/src/app/login/page.tsx | 16–97 | refactor: jeden e-mail input + tlačítko „Pošli mi odkaz"; odstranit Google OAuth tlačítko + password formulář |
| web/src/app/login/actions.ts | 7–58 | nahradit login() (Google) za requestMagicLink(email); devQuickLogin() přepsat na passwordless přes admin.generateLink, gate KOSMO_DEMO_MODE |
| web/src/app/auth/callback/route.ts | 1–18 | beze změny (exchangeCodeForSession() zvládá magic link token) |
| web/src/utils/supabase/middleware.ts | 11–87 | beze změny |
| Supabase Dashboard | — | enable Email/OTP magic-link mode, TTL 900s, single-use; disable Google/Azure/Apple providery; Pro upgrade před prod rolloutem |
.env / Vercel |
— | KOSMO_DEMO_MODE, SUPABASE_SERVICE_ROLE_SECRET (jen dev/preview); odstranit GOOGLE_* |
| docs/intent/auth-and-roles.md | 21, 27, 31 | upravit: magic link only, Google odložen, Apple zamítnut |
| docs/invariants.md | INV-AUTH-04 + nový INV-AUTH-05 | rozšířit callback validaci; nový invariant „magic link only v produkci" |
Open questions
- Supabase Pro upgrade timing — kdy, ideálně před prod rolloutem rodičům.
- Žákovský login doména —
student.sofie.educationvs. sdílená@sofie.education? Ovlivní pozdější Google provider config. - Trigger pro přidání OAuth později — kvalitativní (staff stěžuje) nebo kvantitativní (frekvence loginů)?
Proaktivní upozornění
- Doručitelnost ze Supabase domén —
noreply@mail.app.supabase.iomůže vypadat „phishingově". UX vysvětlí, případně později přepneme na custom SMTP. - Email change pro rodiče — UX/admin task, ne auth.
- Bezpečnostní strop magic linku — útočník s přístupem k e-mailu má přístup do Sofie. Stejné jako password reset všude. Mitigace: krátký TTL, single-use, audit log.
- Magic link replay — single-use + 15min TTL pokrývá.
- GDPR audit log —
auth.audit_log_entrieszachycuje login events, pro export ředitelce stačí query.