Přeskočit obsah

Auth & Organizace — Specifikace

Přehled

Systém podporuje více firem (multi-tenant). Každá firma je identifikována IČO. Uvnitř firmy má každý uživatel přiřazené role a seznam viditelných aplikací. Oprávnění přiděluje firemní admin (ředitel) nebo super admin (Avaxis tým).

Hierarchie uživatelů

Super Admin (Avaxis)
  └── vidí a spravuje vše — firmy, uživatele, katalog, support
        ▼ aktivuje aplikace pro firmu (na základě předplatného)

Company Admin (Ředitel / firemní admin)
  └── spravuje uživatele své firmy
  └── přiděluje aplikace rolím nebo konkrétním uživatelům

Uživatel (zaměstnanec)
  └── vidí pouze aplikace, které mu byly přiděleny
  └── nemůže měnit oprávnění

Datový model

-- Firmy
companies (
  id          UUID PRIMARY KEY,
  ico         VARCHAR(8) UNIQUE NOT NULL,
  name        VARCHAR(255) NOT NULL,
  address     TEXT,
  plan        VARCHAR(50),       -- basic | standard | premium
  is_active   BOOLEAN DEFAULT true,
  created_at  TIMESTAMPTZ
)

-- Uživatelé
users (
  id              UUID PRIMARY KEY,
  company_id      UUID REFERENCES companies(id),
  email           VARCHAR(255) UNIQUE NOT NULL,
  name            VARCHAR(255),
  system_role     VARCHAR(50),   -- super_admin | company_admin | user
  is_active       BOOLEAN DEFAULT true,
  is_deleted      BOOLEAN DEFAULT false,  -- soft delete (GDPR anonymizace)
  deleted_at      TIMESTAMPTZ,
  tfa_method      VARCHAR(20),   -- NULL | 'totp' | 'qr_app' — aktivní metoda
  tfa_enabled     BOOLEAN DEFAULT false,
  tfa_secret      TEXT,          -- šifrovaný TOTP secret / QR session klíč
  tfa_disabled_by UUID REFERENCES users(id),  -- kdo vypnul (support)
  tfa_disabled_at TIMESTAMPTZ,
  last_login_at   TIMESTAMPTZ,
  created_at      TIMESTAMPTZ
)

-- Pojmenované role v rámci firmy (Účetní, Skladník, Obchodník...)
company_roles (
  id          UUID PRIMARY KEY,
  company_id  UUID REFERENCES companies(id),
  name        VARCHAR(100),
  created_by  UUID REFERENCES users(id)
)

-- Přiřazení role uživateli
user_company_roles (
  user_id   UUID REFERENCES users(id),
  role_id   UUID REFERENCES company_roles(id),
  PRIMARY KEY (user_id, role_id)
)

-- Přidělení aplikací — per role NEBO per uživatel (ne obojí)
app_assignments (
  id           UUID PRIMARY KEY,
  company_id   UUID REFERENCES companies(id),
  app_id       UUID REFERENCES apps(id),
  role_id      UUID REFERENCES company_roles(id),  -- NULL = per-user
  user_id      UUID REFERENCES users(id),           -- NULL = per-role
  granted_by   UUID REFERENCES users(id),
  granted_at   TIMESTAMPTZ,
  CONSTRAINT role_or_user CHECK (
    (role_id IS NOT NULL AND user_id IS NULL) OR
    (role_id IS NULL     AND user_id IS NOT NULL)
  )
)

Logika viditelnosti aplikací

Uživatel X z Firmy A vidí aplikaci Y pokud platí ALESPOŇ JEDNA podmínka:
  1. app_assignments: company_id=A, app_id=Y, user_id=X
  2. app_assignments: company_id=A, app_id=Y, role_id IN (role uživatele X)

Navíc musí platit:
  • Firma A má aktivní předplatné pro aplikaci Y
  • Aplikace Y je public NEBO custom pro company_id=A
SELECT DISTINCT a.*
FROM apps a
JOIN app_assignments aa ON aa.app_id = a.id
LEFT JOIN user_company_roles ucr
  ON ucr.role_id = aa.role_id AND ucr.user_id = :user_id
WHERE aa.company_id = :company_id
  AND (aa.user_id = :user_id OR ucr.user_id = :user_id)
  AND (a.visibility = 'public' OR a.client_id = :company_id)

Autentizace — přihlášení

Základní přihlášení (e-mail + heslo)

POST /auth/login
  { email, password }

Pokud uživatel nemá 2FA:
  → vrátí access_token, refresh_token, offline_jwt

Pokud uživatel má 2FA aktivní:
  → vrátí { requires_2fa: true, session_token: "..." }
  → klient zobrazí výzvu pro 2FA
  → POST /auth/2fa/verify { session_token, code }
  → vrátí access_token, refresh_token, offline_jwt

JWT — access token payload

{
  "sub": "usr_abc123",
  "company_id": "comp_xyz",
  "system_role": "user",
  "tfa_verified": true,
  "exp": 1745000000,
  "iat": 1744999100
}

JWT — offline token payload (rozšířený)

{
  "sub": "usr_abc123",
  "company_id": "comp_xyz",
  "system_role": "user",
  "apps": ["fakturace-pro", "ucetnictvi-lite"],
  "exp": 1747591100,
  "iat": 1744999100,
  "sig": "RS256 podpis soukromým klíčem Avaxis"
}

Refresh flow

Access token expiruje (15 min)
  → POST /auth/refresh { refresh_token }
  → nový access_token + rotovaný refresh_token
  → starý refresh_token okamžitě invalidován

Dvoufaktorová autentizace (2FA)

Principy

  • Výchozí stav: 2FA je vypnuté pro všechny uživatele
  • Aktivace: dobrovolná v Nastavení účtu po přihlášení
  • Vypnutí podporou: support admin může vypnout 2FA (ztráta telefonu)
  • Ověření před aktivací: uživatel musí prokázat funkčnost před zapnutím

Metody (obě dostupné, uživatel si vybere)

Metoda A — QR kód přes AVAX mobilní aplikaci
  1. Přihlašovací obrazovka zobrazí QR kód (platnost 2 min)
  2. Uživatel otevře AVAX Android appku
  3. Naskenuje QR kód
  4. Appka odešle podepsaný token na API
  5. API ověří → přihlášení dokončeno

Metoda B — TOTP (Google Authenticator, Authy, 1Password...)
  1. Přihlašovací obrazovka zobrazí pole pro 6místný kód
  2. Uživatel otevře autentifikační appku
  3. Zadá aktuální 6místný kód (platnost 30s)
  4. API ověří TOTP secret → přihlášení dokončeno

Aktivace 2FA (flow)

Uživatel jde do Nastavení → Zabezpečení → Aktivovat 2FA

Krok 1 — Volba metody:
  ○ QR kód (AVAX mobilní aplikace)
  ○ TOTP kód (Google Authenticator / jiná appka)

Krok 2 — Nastavení:
  TOTP: zobrazí QR kód pro naskenování do autentifikátoru
         + záložní kód (backup code) pro případ ztráty telefonu
  QR app: odkaz ke stažení AVAX Android appky + párování

Krok 3 — OVĚŘENÍ FUNKČNOSTI (povinné před aktivací):
  "Zadejte kód z vaší aplikace pro ověření správného nastavení"
  [ 6místný kód nebo QR scan ]
  → pokud ověření úspěšné → 2FA aktivní
  → pokud neúspěšné → 2FA se NEAKTIVUJE, uživatel zkusí znovu

Krok 4 — Potvrzení aktivace:
  "2FA je nyní aktivní. Uložte si záložní kódy na bezpečné místo."
  [ Zobrazit záložní kódy ] [ Stáhnout PDF ]

Záložní kódy (backup codes)

Při aktivaci vygeneruje API 8 jednorázových záložních kódů (každý 16 znaků)
Uloží se jako bcrypt hash (nikdy plaintext)
Při použití záložního kódu → kód je okamžitě invalidován
Počet zbývajících kódů zobrazen v Nastavení
Pokud zbývá ≤ 2 kódy → upozornění uživateli

Vypnutí 2FA support adminem (ztráta telefonu)

Support admin v Správě portálu → detail uživatele → [Vypnout 2FA]
  → Povinná poznámka důvodu (zobrazí se v audit logu)
  → Záznam: { actor: support_admin, action: 'disable_2fa',
               target_user: ..., reason: "...", timestamp: ... }
  → Uživateli přijde e-mail: "2FA bylo deaktivováno administrátorem.
     Pokud jste toto nežádali, kontaktujte okamžitě support."
  → Uživatel se může znovu přihlásit bez 2FA a nově nastavit

Kdo může vypnout 2FA:
  ✓ Avaxis support_agent nebo super_admin
  ✗ Company admin nemůže vypnout 2FA svých uživatelů
    (bezpečnostní pravidlo — jen neutrální třetí strana)

2FA a offline režim

Desktop aplikace s aktivním 2FA:
  → Online přihlášení: plné 2FA ověření
  → Offline spuštění (platný offline JWT): 2FA se nevyžaduje
    (zařízení je již považováno za důvěryhodné po online přihlášení)
  → Offline JWT expiruje: nutné online přihlášení včetně 2FA

Databáza 2FA relací (QR metoda)

tfa_qr_sessions (
  id          UUID PRIMARY KEY,
  user_id     UUID REFERENCES users(id),
  session_token VARCHAR(64),    -- náhodný token v QR kódu
  status      VARCHAR(20),      -- 'pending' | 'approved' | 'expired'
  device_id   VARCHAR(255),     -- mobilní zařízení které schválilo
  expires_at  TIMESTAMPTZ,      -- +2 minuty od vytvoření
  created_at  TIMESTAMPTZ
)

tfa_backup_codes (
  id          UUID PRIMARY KEY,
  user_id     UUID REFERENCES users(id),
  code_hash   VARCHAR(255),     -- bcrypt hash
  used_at     TIMESTAMPTZ       -- NULL = nevyužit
)

SSO

Rozhodnutí: Nyní implementujeme pouze e-mail + heslo. Architektura je připravena pro přidání OIDC v budoucnu — auth service je izolovaná vrstva, OIDC provider lze zapojit bez změny zbytku systému.


Správa uživatelů — Company Admin

Co může

  • Vytvářet / deaktivovat uživatele své firmy
  • Vytvářet pojmenované role (Účetní, Skladník...)
  • Přiřazovat role uživatelům
  • Přidělovat aplikace rolím nebo jednotlivým uživatelům
  • Odebírat oprávnění
  • Vidět přehled aktivit uživatelů (přihlášení, stažené aplikace)
  • Vidět záznamy o impersonaci svých uživatelů Avaxis adminem

Co NEMŮŽE

  • Přidělovat aplikace bez aktivního předplatného firmy
  • Vidět chat nebo tikety jiných firem
  • Spravovat uživatele jiných firem
  • Vypnout 2FA svým uživatelům (pouze Avaxis support)

Audit log

Typy záznamů a retention

Typ záznamu                              Retention    Úložiště po expiraci
────────────────────────────────────────────────────────────────────────────
Přihlášení / odhlášení                   90 dní       S3 archiv
Neúspěšné přihlášení (brute-force det.)  90 dní       S3 archiv
Aktivace / deaktivace 2FA                1 rok        S3 archiv
Vypnutí 2FA support adminem              1 rok        S3 archiv (+ notif.)
Změny oprávnění (grant/revoke app)       1 rok        S3 archiv
Vytvoření / deaktivace uživatele         1 rok        S3 archiv
Impersonace (start + end)                1 rok        S3 archiv
Změny předplatného / fakturace           7 let        S3 archiv (daň. povin.)
GDPR souhlasy a žádosti o výmaz          vztah + 3 r  S3 archiv
Support komunikace                       vztah + 1 r  S3 archiv

Schéma

audit_log (
  id          UUID PRIMARY KEY,
  actor_id    UUID REFERENCES users(id),
  action      VARCHAR(100),
  -- přihlášení:   'login', 'logout', 'login_failed', 'login_2fa'
  -- 2FA:          'tfa_enabled', 'tfa_disabled', 'tfa_disabled_by_support'
  -- oprávnění:    'grant_app', 'revoke_app', 'create_role', 'assign_role'
  -- uživatelé:    'create_user', 'deactivate_user', 'delete_user'
  -- impersonace:  'impersonate_start', 'impersonate_end'
  target_type VARCHAR(50),    -- 'user' | 'role' | 'company' | 'app'
  target_id   UUID,
  payload     JSONB,          -- before/after stav, IP adresa, device_id
  retention   VARCHAR(20),    -- '90d' | '1y' | '7y' | 'contract+1y' ...
  created_at  TIMESTAMPTZ
)

Záznamy starší než jejich retention jsou automaticky přesunuty do S3 archivu (levné úložiště, stále dostupné pro compliance audit, ale ne v hlavní DB).


Rozhodnuté otázky

Otázka Rozhodnutí
SSO Nyní ne — pouze e-mail + heslo; architektura připravena pro OIDC
2FA výchozí stav Vypnuté, uživatel aktivuje dobrovolně v Nastavení
2FA metody QR kód (AVAX mobilní app) + TOTP záloha — obojí dostupné
2FA aktivace Povinné ověření funkčnosti před aktivací
2FA vypnutí Pouze Avaxis support_agent nebo super_admin (ne company admin)
2FA vypnutí — notifikace E-mail uživateli okamžitě po vypnutí
2FA + offline Offline JWT = důvěryhodné zařízení, 2FA se nevyžaduje
Audit log — přihlášení 90 dní, pak S3 archiv
Audit log — oprávnění, 2FA 1 rok, pak S3 archiv
Audit log — fakturace 7 let (daňová povinnost)
Audit log — GDPR Trvání vztahu + 3 roky
Audit log — archivace Automatické přesunutí do S3 po uplynutí retention

Otevřené otázky

Všechny otázky k auth-organization.md jsou vyřešeny.


Poslední aktualizace: 2026-04-21