Přeskočit obsah

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

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.

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.

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

  1. Supabase Pro upgrade timing — kdy, ideálně před prod rolloutem rodičům.
  2. Žákovský login doménastudent.sofie.education vs. sdílená @sofie.education? Ovlivní pozdější Google provider config.
  3. Trigger pro přidání OAuth později — kvalitativní (staff stěžuje) nebo kvantitativní (frekvence loginů)?

Proaktivní upozornění

  • Doručitelnost ze Supabase doménnoreply@mail.app.supabase.io můž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 logauth.audit_log_entries zachycuje login events, pro export ředitelce stačí query.