Přeskočit obsah

Uživatelé, role a sdílení dat — Specifikace

Navazuje na: auth-organization.md (role, auth), sync-backup.md (zálohy), s3development.md (developer API).


1. Struktura uživatelů ve firmě

1.1 Systémové role (system_role)

Každý uživatel má přesně jednu systémovou roli — určuje co vidí a co může dělat v rámci celé platformy:

super_admin      Avaxis tým — plný přístup ke všem firmám, datům,
                 portálu, podpoře a billing. Může impersonovat kohokoliv.

company_admin    Správce firmy (ředitel, IT admin) — plný přístup
                 ke své firmě: uživatelé, role, aplikace, úložiště,
                 sdílené adresáře. Vidí svá data i data svých uživatelů
                 (zákonná archivace, přebrání agendy).

user             Zaměstnanec — vidí jen přidělené aplikace a
                 svá vlastní data + sdílené adresáře kde má přístup.

1.2 Firemní role (company_roles)

Pojmenované skupiny definované company adminem pro organizaci přístupu k aplikacím a sdíleným adresářům. Každý uživatel může mít více rolí.

Příklady:
  Účetní          → přístup k Fakturace Pro + sdílený adresář „Faktury"
  Mzdový účetní   → přístup k Mzdový systém + soukromý adresář mezd
  Skladník        → přístup k Sklad Manager
  Obchodník       → přístup k CRM
  Ředitel         → přístup ke všemu (company_admin má vždy plný přístup)

1.3 Vztahová mapa

Firma (company)
 ├── Company admin(i)
 │     └── spravuje vše: uživatele, role, adresáře, předplatná
 ├── Uživatelé
 │     ├── může mít více firemních rolí
 │     ├── přiřazené aplikace (per role nebo přímo)
 │     └── přístup ke sdíleným adresářům (per role nebo přímo)
 └── Firemní role (company_roles)
       ├── Účetní → {aplikace: [fakturace-pro], adresáře: [faktury]}
       ├── Skladník → {aplikace: [sklad-manager], adresáře: [sklad-docs]}
       └── ...

1.4 Přidělení aplikací — rozšíření

Stávající model app_assignments zůstává beze změny. Nově se stejný princip (per-role nebo per-user) používá i pro sdílené adresáře — viz sekce 3.


2. Datový model — typy dat

2.1 Typy dat per uživatel/firma

Typ Kdo vlastní Kdo čte Kdo píše S3 umístění
Soukromá data uživatele Uživatel Uživatel, company_admin Uživatel users/{user-id}/data/{app-slug}/
Nastavení uživatele Uživatel Uživatel, company_admin Uživatel users/{user-id}/settings/{app-slug}/
Sdílený adresář Firma Dle ACL Dle ACL shared/dirs/{dir-id}/
Firemní zálohy (data) Firma company_admin, super_admin Launcher (automaticky) backups/{app-slug}/{user-id}/
GDPR identita Uživatel Uživatel, company_admin Systém users/{user-id}/identity.json

2.2 Klíčový princip: zálohy patří firmě, ne uživateli

Uživatel Jan Novák je zaměstnanec firmy Demo s.r.o.
  → Jan pracuje s aplikací Fakturace Pro
  → Vystavuje faktury, ukládá je lokálně
  → Launcher automaticky zálohuje do S3 pod company_id firmy
  → Jan může zálohy číst (obnovit svá data)
  → Jan NEMŮŽE zálohy mazat
  → Pokud Jan odejde z firmy:
      • Firma (company_admin) stále vlastní zálohy
      • Firma VIDÍ Janovu práci (zákonná archivace faktur)
      • Janův GDPR identifikátor (jméno, e-mail) lze anonymizovat
      • Faktury samotné zůstanou beze změny

3. Sdílené adresáře (Shared Directories)

3.1 Co je sdílený adresář

Sdílený adresář je oblast v S3 kam mohou zapisovat i číst více uživatelů. Vytváří ho company_admin a určuje kdo k němu má přístup.

Příklady sdílených adresářů:
  „Faktury 2025"       → Účetní (čtení i zápis), Ředitel (jen čtení)
  „HR dokumenty"       → Mzdový účetní (čtení i zápis), IT admin (bez přístupu)
  „Sklad dokumenty"    → Skladník (čtení i zápis), Vedoucí (jen čtení)
  „Celopodnikové"      → všichni zaměstnanci (jen čtení)

3.2 ACL model sdíleného adresáře

Každý sdílený adresář má seznam přístupových pravidel (ACL). Pravidlo je přiřazeno: - konkrétnímu uživateli (user_id) - celé roli (role_id) — zdědí všichni členové role - celé firmě (all_company = true)

Příklad ACL pro adresář „Faktury 2025":

  role_id = Účetní  → can_read=true,  can_write=true
  role_id = Ředitel → can_read=true,  can_write=false
  user_id = Jana K. → can_read=true,  can_write=true   (přímé přidělení)

Výsledná oprávnění uživatele Jana Nováka (role: Účetní):
  → can_read=true, can_write=true  (dědí z role Účetní)

3.3 Datový model

-- Sdílené adresáře
storage_directories (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  company_id   UUID REFERENCES companies(id) NOT NULL,
  name         VARCHAR(255) NOT NULL,
  description  TEXT,
  app_slug     VARCHAR(100),    -- NULL = obecný (pro všechny aplikace)
  created_by   UUID REFERENCES users(id),
  is_active    BOOLEAN DEFAULT true,
  created_at   TIMESTAMPTZ DEFAULT now()
)

-- ACL záznamy (per adresář)
storage_directory_access (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  directory_id UUID REFERENCES storage_directories(id) ON DELETE CASCADE,
  -- JEDEN z těchto tří musí být NOT NULL:
  user_id      UUID REFERENCES users(id),
  role_id      UUID REFERENCES company_roles(id),
  all_company  BOOLEAN DEFAULT false,

  can_read     BOOLEAN NOT NULL DEFAULT true,
  can_write    BOOLEAN NOT NULL DEFAULT false,

  granted_by   UUID REFERENCES users(id),
  granted_at   TIMESTAMPTZ DEFAULT now(),

  CONSTRAINT one_target CHECK (
    (user_id IS NOT NULL)::int +
    (role_id IS NOT NULL)::int +
    (all_company = true)::int = 1
  )
)

-- Index pro rychlé vyhledání přístupů uživatele
CREATE INDEX idx_dir_access_user ON storage_directory_access(user_id) WHERE user_id IS NOT NULL;
CREATE INDEX idx_dir_access_role ON storage_directory_access(role_id) WHERE role_id IS NOT NULL;

3.4 Dotaz: jaké adresáře vidí uživatel?

SELECT DISTINCT sd.*,
  MAX(sda.can_write::int)::boolean AS can_write
FROM storage_directories sd
JOIN storage_directory_access sda ON sda.directory_id = sd.id
LEFT JOIN user_company_roles ucr
  ON ucr.role_id = sda.role_id AND ucr.user_id = :user_id
WHERE sd.company_id = :company_id
  AND sd.is_active = true
  AND (
    sda.user_id    = :user_id       -- přímé přidělení
    OR ucr.user_id = :user_id       -- přes roli
    OR sda.all_company = true       -- všichni ve firmě
  )
GROUP BY sd.id;

4. S3 adresářová struktura — kompletní

s3://avaxis/
├── companies/{company-id}/
│   │
│   ├── users/
│   │   └── {user-id}/
│   │       ├── identity.json              ← GDPR data (name, email)
│   │       ├── license.jwt                ← offline JWT cache
│   │       ├── data/
│   │       │   └── {app-slug}/
│   │       │       ├── manifest.json      ← sync metadata
│   │       │       ├── latest/            ← aktuální data (sync target)
│   │       │       │   ├── db.sqlite
│   │       │       │   └── files/
│   │       │       └── settings.json      ← app nastavení uživatele
│   │       └── backups/
│   │           └── {app-slug}/
│   │               └── {ISO-timestamp}/   ← point-in-time záloha
│   │                   ├── db.sqlite
│   │                   └── files/
│   │
│   └── shared/
│       └── dirs/
│           └── {dir-id}/                  ← sdílený adresář
│               ├── .meta.json             ← název, popis, created_by
│               └── {libovolná struktura}  ← aplikace si řídí obsah
└── apps/                                  ← katalog aplikací (odděleno)
    └── catalog/{app-slug}/...

4.1 Pravidla S3 prefixů

Scope tokenu S3 prefix Oprávnění
private companies/{cid}/users/{uid}/data/ GET + PUT
private_backup companies/{cid}/users/{uid}/backups/ GET (read-only)
shared + dir_id companies/{cid}/shared/dirs/{dir_id}/ GET + PUT (dle ACL)
admin_user + user_id companies/{cid}/users/{user_id}/ GET (plný)
admin_company companies/{cid}/ GET (plný)

Scope admin_user a admin_company dostane pouze company_admin a super_admin.


5. Storage Token API

Launcher a aplikace přistupují k S3 výhradně přes krátkodobé tokeny vydávané tímto API. Nikdy nepracují s permanentními S3 klíči.

5.1 Endpointy

GET  /storage/token
     Params: scope, [dir_id], [user_id], [app_slug]
     → { endpoint, bucket, prefix, access_key, secret_key,
          session_token, expires_at, can_write }

GET  /storage/directories
     → seznam sdílených adresářů přístupných pro přihlášeného uživatele
     → [{ id, name, description, app_slug, can_write, created_at }]

GET  /storage/directories/{id}
     → detail adresáře včetně ACL (jen company_admin)

POST /storage/directories
     Body: { name, description, app_slug? }
     → vytvoří adresář (jen company_admin)

DELETE /storage/directories/{id}
     → deaktivuje adresář (jen company_admin)

GET  /storage/directories/{id}/access
     → výpis ACL záznamů (jen company_admin)

POST /storage/directories/{id}/access
     Body: { user_id | role_id | all_company, can_read, can_write }
     → přidá přístupové pravidlo (jen company_admin)

DELETE /storage/directories/{id}/access/{access_id}
     → odebere přístupové pravidlo (jen company_admin)

GET  /storage/usage
     → { used_bytes, limit_bytes, breakdown_by_app, breakdown_by_user }
     → company_admin vidí celou firmu, uživatel vidí jen svá data

5.2 Token generování — logika

# Backend: POST /storage/token

def generate_storage_token(user, scope, dir_id=None, app_slug=None):
    cid = str(user.company_id)
    uid = str(user.id)

    match scope:
        case "private":
            prefix    = f"companies/{cid}/users/{uid}/data/"
            can_write = True

        case "private_backup":
            prefix    = f"companies/{cid}/users/{uid}/backups/"
            can_write = False   # zálohy jsou read-only pro uživatele

        case "shared":
            # Ověř ACL
            acl = get_directory_acl(dir_id, user)
            if not acl.can_read:
                raise PermissionError()
            prefix    = f"companies/{cid}/shared/dirs/{dir_id}/"
            can_write = acl.can_write

        case "admin_user":
            # Jen company_admin nebo super_admin
            require_admin(user)
            prefix    = f"companies/{cid}/users/{target_user_id}/"
            can_write = False   # admin čte, nemaže

        case "admin_company":
            require_admin(user)
            prefix    = f"companies/{cid}/"
            can_write = False

    # STS AssumeRole nebo S3 pre-signed URL s omezeným prefixem
    return issue_sts_token(prefix, can_write, ttl=3600)

5.3 STS politika — příklad pro scope private

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
    "Resource": [
      "arn:aws:s3:::avaxis",
      "arn:aws:s3:::avaxis/companies/COMPANY-ID/users/USER-ID/data/*"
    ]
  }]
}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:ListBucket"],
    "Resource": [
      "arn:aws:s3:::avaxis",
      "arn:aws:s3:::avaxis/companies/COMPANY-ID/users/USER-ID/backups/*"
    ]
  }]
}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
    "Resource": [
      "arn:aws:s3:::avaxis",
      "arn:aws:s3:::avaxis/companies/COMPANY-ID/shared/dirs/DIR-ID/*"
    ]
  }]
}

6. Zálohovací software — launcher integrace

6.1 Co launcher zálohuje automaticky

Per uživatel per aplikace:

  Nastavení (vždy, zdarma):
    app.paths.settings → S3: users/{uid}/data/{slug}/settings.json
    Trigger: okamžitě při každé změně (file watcher)
    Retence: posledních 10 verzí (rotace)

  Data (dle nastavení, defaultně zapnuto):
    app.paths.data → S3: users/{uid}/data/{slug}/latest/
    Trigger: SDK zavolá app.sync.request() → launcher provede sync
    Záloha: snapshot do users/{uid}/backups/{slug}/{timestamp}/
    Retence: dle company_data_retention (default 10 let)

6.2 Launcher UI — zálohy a sdílení

MainWindow → menu: „Úložiště" → DropDown:
  📦  Zálohy              → BackupScreen
  📁  Sdílené adresáře    → SharedDirsScreen
  📊  Využití místa       → StorageUsageScreen

BackupScreen

Zálohy dat aplikací:

  Fakturace Pro          Poslední záloha: dnes 14:23  45 MB
    [Zálohovat nyní]  [Obnovit zálohu ▾]  [Historie]

  Mzdový systém          Poslední záloha: dnes 09:01  32 MB
    [Zálohovat nyní]  [Obnovit zálohu ▾]  [Historie]

  ─────────────────────────────────────────────────
  Celkové využití:  ████████░░░░░░  780 MB / 1 000 MB

SharedDirsScreen

Sdílené adresáře (company_admin vidí navíc tlačítko [+ Nový adresář]):

  📁 Faktury 2025              Přístup: Účetní (čtení+zápis), Ředitel (čtení)
     [Otevřít]  [Nahrát soubor]  [...]

  📁 HR dokumenty              Přístup: Mzdový účetní (čtení+zápis)
     [Otevřít]

  📁 Celopodnikové             Přístup: Všichni (jen čtení)
     [Otevřít]

StorageUsageScreen (jen company_admin)

Celkové využití firmy:
  ████████░░░░░░  780 MB / 1 000 MB

Rozpad dle uživatelů:
  Jan Novák (admin@demo.cz)    320 MB
  Jana Krásná (jana@demo.cz)   210 MB
  Petr Mlynář (petr@demo.cz)   145 MB
  Sdílené adresáře              105 MB

Rozpad dle aplikací:
  Fakturace Pro    420 MB
  Mzdový systém    280 MB
  Ostatní          180 MB

[Dokoupit prostor]

7. Předání agendy (přebrání dat uživatele)

Scénář: Jan Novák odchází z firmy, jeho agendu přebírá Jana Krásná.

7.1 Flow

1. company_admin → launcher → Uživatelé → Jan Novák → [Předat agendu]

2. Launcher zobrazí dialog:
   „Předat data Jana Nováka (Fakturace Pro) komu?"
   [Vybrat uživatele: Jana Krásná ▾]
   [Předat jako:]
     ○ Zkopírovat do soukromých dat Jany Krásné
     ○ Přesunout do sdíleného adresáře [vybrat/vytvořit]

3. Backend provede:
   a. Kopírování: S3 CopyObject — users/{jan-id}/data/{slug}/ → users/{jana-id}/data/{slug}/
      (záloha Jana zůstane beze změny — vlastní firma)
   b. Nebo vytvoří sdílený adresář „Agenda Jana Nováka" a zkopíruje tam

4. Jan Novák je deaktivován (is_active=false)
5. Janovy zálohy zůstanou firmě (company_admin je vidí)
6. Janův GDPR identifikátor lze anonymizovat na žádost

7.2 API endpoint

POST /org/users/{user_id}/transfer-data
Body: {
  "app_slug": "fakturace-pro",
  "mode": "copy_to_user" | "move_to_shared_dir",
  "target_user_id": "uuid" | null,
  "target_dir_id":  "uuid" | null,  (vytvoří nový pokud null + mode=move_to_shared_dir)
  "new_dir_name":   "Agenda Jana Nováka"  (jen pokud target_dir_id=null)
}
→ { "job_id": "...", "status": "pending" }  (asynchronní Celery job)

8. Implementační fáze

Fáze A — Backend (navazuje na F2 katalog)

1. DB migrace 004b:
     CREATE TABLE storage_directories
     CREATE TABLE storage_directory_access

2. Backend: /storage/* endpointy
     GET /storage/token (STS nebo scope-limited presigned)
     GET /storage/directories
     POST /storage/directories
     POST /storage/directories/{id}/access
     DELETE /storage/directories/{id}/access/{id}
     GET /storage/usage

3. MinIO/Ceph: konfigurace IAM politik
     Per-prefix STS tokeny (AssumeRole nebo alternativa pro MinIO)

Fáze B — Launcher obrazovky

4. BackupScreen     — přehled záloh, ruční záloha, obnovení
5. SharedDirsScreen — výpis adresářů, nahrání souboru
6. StorageUsageScreen — přehled využití (jen admin)

Fáze C — Sync agent (L2.3+ navazující)

7. Launcher: SyncAgent vlákno
     - file watcher (watchdog)
     - diff-based sync (manifest checksum)
     - záloha snapshotu před sync

8. SDK rozšíření: app.storage.app_data(), app.storage.shared(dir_id)

9. Bezpečnostní pravidla — souhrn

Akce user company_admin super_admin
Číst svá soukromá data
Psát do svých soukromých dat - -
Číst soukromá data jiného uživatele firmy -
Psát do soukromých dat jiného uživatele - - -
Číst sdílený adresář dle ACL
Psát do sdíleného adresáře dle ACL
Vytvářet sdílené adresáře -
Mazat zálohy - žádat schvalovat
Fyzicky mazat ze S3 - - jen GC job
Předávat agendu uživatele -
Anonymizovat GDPR data - žádat schvalovat

10. Otevřené otázky

Otázka Stav Poznámka
MinIO STS — podporuje scope-limited AssumeRole? ❓ Ověřit Alternativa: presigned URL se STS sessions
Sdílené adresáře — max počet per firma ❓ Rozhodnout Návrh: 50 (standard), 200 (premium)
Notifikace při nahrání do sdíleného adresáře ❓ Rozhodnout Vhodné pro tray notifikace
Předání agendy — atomicita S3 kopírování ❓ Implementace S3 nemá transakce — Celery job s checkpointy
Šifrování sdílených adresářů ❓ Rozhodnout AES-256 SSE-S3 (server) vs. E2E (premium)

Vytvořeno: 2026-04-25 | Navazuje na: auth-organization.md, sync-backup.md, s3development.md