← EP21 Internal Docs Podpora21.cz
Solution Design · DS-0006 · v0.1.0

Klientský portál Phase 2 — autentizace a dashboard

draft Authors: david.sorf, claude-sonnet-4-6 Created: 2026-04-10 Updated: 2026-04-10
portal auth client phase2
Phase 1 klientského portálu zpřístupnila anonymní podání ticketu (Quick Ticket) a statický landing page. Klienti ale nemají způsob, jak sledovat stav svých ticketů po podání — chybí autentizovaný přístup. Tato specifikace definuje Phase 2: registraci a přihlášení klienta, client dashboard (seznam vlastních ticketů), detail ticketu přes ticket_code, a volitelný profil. Zvolený auth model: sdílený `podpora21.user` s `role='client'`, propojený FK `support_client.user_id`.
Related docs:
Related tasks:

Motivace a kontext

Phase 1 umožnila anonymní podání ticketu přes Quick Ticket formulář — klient zadá jméno, email a popis problému, systém vrátí ticket_code. Po podání ale klient nemá žádný způsob jak sledovat stav, přidávat komentáře, nebo nahlížet na historii svých požadavků. Každý re-visit je anonymní. Bez Phase 2 musí agent ručně informovat klienta o každé změně. Cílem Phase 2 je dát klientovi jednoduché, zabezpečené rozhraní: registrace přes email, přihlášení, dashboard s vlastními tickety, detail ticketu.

Auth model — volba implementace

Zvažovány tři možnosti:

Option A: Separátní tabulka client_account — vlastní user tabulka jen pro klienty. Čisté oddělení, ale duplicitní auth logika (session, login form, hash), nutnost dvou session mechanismů.

Option B: Nette Identity + separátní authenticator — klientský authenticator vedle agentského. Stále sdílí session systém, ale vyžaduje dvě identity a přepínání v BasePresenter.

Option C (zvoleno): Sdílený podpora21.user s role='client' — klientský account je standardní user řádek se role='client'. Existující auth (UserAuthenticator, session, BasePresenter) funguje beze změny. Přidáme pouze: (1) CHECK constraint na role = 'client', (2) FK support_client.user_id, (3) PortalPresenter s RBAC guardou role IN ('client'), (4) registrační flow find-or-create.

Výhody Option C: minimální změny v auth infrastruktuře, jeden session mechanismus, sdílený password hash, přímá vazba klient-account přes FK.

Budoucí migrace na Keycloak/SAML/OIDC: sdílený user model s role='client' usnadní případnou budoucí migraci — OIDC sub claim se mapuje na user.id, roles zůstávají. Authenticator se vymění za OIDC bridge, ostatní kód zůstane.

Datový model — změny schématu

Požadované změny v DB:

1. podpora21.user.role — rozšíření CHECK constraint: přidat 'client' do povolených hodnot. Aktuální: CHECK (role IN ('agent', 'admin')). Nové: CHECK (role IN ('agent', 'admin', 'client')).

2. podpora21.support_client.user_id — nový nullable FK na podpora21.user(id). Propojuje anonymní support_client záznam s registrovaným účtem. NULL = klient nemá účet. Populated při registraci nebo při prvním přihlášení (find-or-create).

3. Indexy: support_client(user_id) pro rychlý lookup ticketů přihlášeného klienta.

Žádné jiné tabulky se nemění. Ticket tabulka zůstává beze změny — tickety jsou stále vázány na client_id (support_client).

URL mapa — nové routes

Registrační flow — find-or-create

Registrace klienta: klient zadá email + heslo (+ volitelně jméno).

find-or-create logic (transakční):

1. Hledej support_client WHERE tenant_id = current_tenant AND email = input_email.

2. Pokud existuje a user_id IS NOT NULL → účet již existuje, přesměruj na login s flash.

3. Pokud existuje a user_id IS NULL → vytvoř user záznam (role='client'), UPDATE support_client SET user_id = new_user.id.

4. Pokud neexistuje → INSERT support_client + INSERT user + FK update, vše atomicky.

5. Po úspěchu: přihlásit uživatele (Nette Identity), přesměrovat na /portal/dashboard.

Hash hesla: password_hash($password, PASSWORD_BCRYPT) — stejný způsob jako agentský user.

V Phase 2 není email verifikace — pro jednoduchost přijmeme any valid email. Phase 3 může přidat email confirmation token.

Client dashboard

Dashboard zobrazuje tickety přihlášeného klienta.

Query:

SELECT t.*
FROM podpora21.support_ticket t
JOIN podpora21.support_client c ON c.id = t.client_id
WHERE t.tenant_id = :tenant_id
  AND c.user_id = :logged_in_user_id
ORDER BY t.created_at DESC
LIMIT 50

Bezpečnostní invariant: dvě podmínky — tenant_id (tenant isolation) + c.user_id (client isolation). Klient nikdy nevidí tickety jiného klienta ani jiného tenantu.

Zobrazení: tabulka s ticket_number, ticket_code, title, status badge, created_at, link na detail. Stránkování v Phase 3.

Ticket detail (klientský pohled)

Route: /portal/ticket/<code> kde <code> = ticket_code (6 znaků).

Autorizace: ticket musí patřit přihlášenému klientovi — ticket.client_id → support_client.id WHERE user_id = logged_in_user.id AND tenant_id = current_tenant. Při neúspěchu: 403 nebo redirect na dashboard.

Zobrazení: readonly pohled — title, description, status badge, created_at, resolved_at. Komentáře (thread) = Phase 3. Žádné action buttons v Phase 2 (klient nemůže měnit status, přidávat přílohy).

Cross-role guard: pokud je přihlášený agent/admin a navštíví /portal/*, přesměrovat na /helpdesk (nebo zobrazit 403).

Bezpečnostní model

Implementační kroky

Rozhodnutí z review (2026-04-10)

Omezení Phase 2

Changelog

VersionDateAuthorNote
0.1.02026-04-10david.sorf + claude-sonnet-4-6

Initial draft — auth model (Option C: shared podpora21.user role=client), data-model (user_id FK), URL map (7 routes), registration find-or-create, security model, 13 implementation steps.

0.1.12026-04-10david.sorf

Review decisions recorded: sdílený auth + budoucí Keycloak migrace; portal-layout.latte separátní; QT→účet jako separátní DS/ADR; email přes mail21.cz; URL přes ticket_code (viz DS-0004).