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