-- ==================================================================== -- 0061_portal_api_tokens — Bearer-Token für die native App (iOS/Android) -- ==================================================================== -- Die native App (React Native/Expo) kann keine Server Actions und kein -- iron-session-Cookie nutzen. Sie authentifiziert über den bestehenden -- Magic-Link-Code-Flow und erhält danach einen langlebigen Bearer-Token, -- der im Gerät (Expo SecureStore / Keychain) liegt und bei jedem -- /api/portal/*-Request im Authorization-Header mitgeschickt wird. -- -- Identität = E-Mail (wie die Portal-Session, lib/portal-session.ts). Eine -- Person kann mehrere aktive Karten/Profile mit derselben E-Mail haben; -- active_employee_id hält das zuletzt gewählte Profil (bei Multi-Profil), -- ist bei genau einem Profil bedeutungslos. -- -- Sicherheit: Es wird NUR der SHA-256-Hash des Tokens gespeichert (wie bei -- Admin-Invites) — ein DB-Leak gibt keine gültigen Tokens preis. Zugriff -- ausschließlich über Service-Role (die Route-Handler), kein anon-Zugriff. -- Gleitende Gültigkeit: expires_at rückt bei aktiver Nutzung nach vorne -- (analog zur 90-Tage-Trusted-Portal-Session). create table if not exists public.portal_api_tokens ( id uuid primary key default gen_random_uuid(), email text not null, token_hash text not null unique, -- Zuletzt gewähltes Profil bei Multi-Profil. NULL = noch nicht gewählt -- bzw. Single-Profil (dann löst der Server das eine aktive Profil auf). active_employee_id uuid references public.employees(id) on delete set null, user_agent text, created_at timestamptz not null default now(), last_used_at timestamptz not null default now(), expires_at timestamptz not null, revoked_at timestamptz ); create index if not exists portal_api_tokens_email_idx on public.portal_api_tokens (email); -- RLS an, keine anon-Policy → Default-Deny für anon. Service-Role umgeht RLS. alter table public.portal_api_tokens enable row level security; revoke all on public.portal_api_tokens from anon;