Skip to content

Profil (/profile)

Správa uživatelského profilu, per-aplikačních preferencí, notifikačních preferencí a Web Push subscripcí.

Lazy vytváření

Profil a preference se vytvářejí automaticky při prvním přístupu — není potřeba žádná explicitní inicializace.


GET /profile/me — vlastní profil

Vrátí globální profil přihlášeného uživatele. Pokud profil neexistuje, vytvoří se automaticky.

http
GET /api/profile/me
Authorization: Bearer <token>

Response

json
{
  "success": true,
  "data": {
    "id": 42,
    "email": "jan.novak@firma.cz",
    "firstName": "Jan",
    "lastName": "Novák",
    "displayName": "Jan Novák",
    "avatarUrl": "https://example.com/avatar.jpg",
    "locale": "cs",
    "theme": "light",
    "timezone": "Europe/Prague",
    "brand": "atrea",
    "identityProfileUrl": "https://login.example.com/profile",
    "createdAt": "2024-01-15T10:00:00.000Z",
    "updatedAt": "2024-01-20T14:30:00.000Z"
  }
}
PoleHodnotyVýchozí
locale"cs", "en", ... nebo nullnull = systémový jazyk (en)
theme"light", "dark", "system""system"
timezoneplatná IANA timezone nebo nullnull = systémové pásmo
brandslug brandu (např. "atrea", "vallox")"atrea"
identityProfileUrlZITADEL profil pro jméno, heslo, passkeys a další auth metodyskládá API z zitadel.publicUrl

Synchronizace se ZITADELem

firstName, lastName a displayName jsou synchronizované obousměrně:

  • změna přes PUT /profile/me se nejdřív uloží do ZITADELu a teprve po úspěchu lokálně;
  • změna v Login v2 „Upravit profil“ se po úspěšném ZITADEL zápisu okamžitě propíše interním profile-sync voláním;
  • webhook user.updated zůstává záložní cestou pro změny mimo Login v2;
  • nový ZITADEL token dorovná profil jako záložní mechanismus, pokud webhook nedorazil;
  • starší token nesmí přepsat novější lokální změnu.

Email zůstává neměnným centrálním identifikátorem a brand je preference v user-api, nikoli organizace vlastnící uživatele v ZITADELu.


PUT /profile/me — aktualizace profilu

http
PUT /api/profile/me
Authorization: Bearer <token>
Content-Type: application/json

Request body

json
{
  "firstName": "Jan",
  "lastName": "Novák",
  "displayName": "Jan Novák",
  "avatarUrl": "https://example.com/avatar.jpg",
  "locale": "cs",
  "theme": "dark",
  "brand": "vallox"
}

Všechna pole jsou volitelná — aktualizují se pouze ta, která pošlete.


GET /profile/me/admin-status — admin status

Vrátí kompletní admin/role profil přihlášeného uživatele — FE z toho rozhoduje, které admin záložky zobrazit, bez extra requestů.

http
GET /api/profile/me/admin-status
Authorization: Bearer <token>

Response

json
{
  "code": "OK",
  "response": {
    "email": "jan.novak@firma.cz",
    "isSuperAdmin": false,
    "appAdminFor": ["kgb", "ui-template"],
    "panelPermissions": ["panel.access", "users.view", "support.view"],
    "roles": [
      { "appCode": "kgb", "roleCodes": ["designer"] },
      { "appCode": "ui-template", "roleCodes": ["user"] }
    ]
  }
}
PoleTypVýznam
isSuperAdminbooleanUživatel je v super_admins.
appAdminForstring[]Seznam appCode, kde má uživatel záznam v app_admins.
rolesArray<{ appCode, roleCodes }>Globální role uživatele přes user_app_roles.

Migrace ze starší verze

Dříve endpoint vracel pouze { isSuperAdmin, email }. Nová pole jsou aditivní — staré klienty stále fungují, ale FE už nemusí pro každou aplikaci dotazovat /users/admins/apps/....


POST /profile/me/check — ověření jednoho oprávnění (JWT)

User-scoped varianta POST /check. Uživatel se bere výhradně z JWT tokenu (req.token) — v body není žádný email, takže nelze podvrhnout cizí identitu. Díky tomu jde endpoint bezpečně vystavit veřejně.

Vyhodnocení je identické s interním /check (stejná výpočetní funkce, stejná cache): deaktivovaný uživatel nemá žádná práva, jinak superadmin > app_admin (pro svůj app) > resource_override > role > none.

http
POST /api/profile/me/check
Authorization: Bearer <token>
Content-Type: application/json

Request body

json
{
  "app": "kgb",
  "permission": "projects.edit",
  "resourceType": "project",   // volitelné — jen pro per-entita check
  "resourceId": "42"           // volitelné
}
PoleTypPovinnéPopis
appstringKód aplikace
permissionstringKód oprávnění
resourceTypestringTyp zdroje pro resource-level check
resourceIdstringID konkrétního záznamu
  • resourceType + resourceId vynechány → globální (role) vyhodnocení.
  • Uvedeny → započítá resource override u práv s global: false.

Response

json
{ "allowed": true }

Chybějící app/permission400. Bez/neplatný token → 401.


POST /profile/me/check/bulk — ověření více oprávnění (JWT)

User-scoped varianta POST /check/bulk. Uživatel z tokenu, žádný email v body.

http
POST /api/profile/me/check/bulk
Authorization: Bearer <token>
Content-Type: application/json

Request body

json
{
  "app": "kgb",
  "permissions": ["projects.edit", "projects.delete", "reports.view"],
  "resourceType": "project",   // volitelné
  "resourceId": "42"           // volitelné
}

Response

json
{ "projects.edit": true, "projects.delete": false, "reports.view": true }

Chybějící app/permissions400. Bez/neplatný token → 401.


POST /profile/me/check/effective — všechna efektivní oprávnění (JWT)

User-scoped varianta POST /check/effective. Používá FE k rozhodnutí, co zobrazit/skrýt. Uživatel z tokenu, žádný email v body.

http
POST /api/profile/me/check/effective
Authorization: Bearer <token>
Content-Type: application/json

Request body

json
{
  "app": "kgb",
  "resourceType": "project",   // volitelné — efektivní práva na konkrétní entitě
  "resourceId": "42"           // volitelné
}

Response

Pole EffectivePermission (stejné schéma jako POST /check/effective):

Aktivní app admin dostane všechna definovaná práva daného appu se source: "app_admin"; tiered práva mají accessLevel: "edit" a binary práva accessLevel: "allowed". Resource override se pro app admina ignoruje.

json
[
  { "code": "projects.edit", "type": "tiered", "global": false, "accessLevel": "edit", "granted": true, "source": "role" },
  { "code": "projects.delete", "type": "binary", "global": true, "accessLevel": null, "granted": false, "source": "none" }
]

Chybějící app400. Bez/neplatný token → 401.

Kdy použít user-scoped /profile/me/check* vs. interní /check*?

  • /profile/me/check* (JWT): konzumující aplikace, které nejsou ve stejné síti. Identita se bere z tokenu, nelze podvrhnout cizí email. Lze vystavit veřejně.
  • /check* (bez auth): čistě server-to-server ve stejné Docker síti (background joby, admin nástroje), kde je potřeba ověřit libovolného uživatele podle email. Doporučeně nevystavovat ven.

GET /profile/me/apps — všechny per-app preference

http
GET /api/profile/me/apps
Authorization: Bearer <token>

Response

json
{
  "success": true,
  "data": [
    {
      "applicationCode": "crm",
      "showHelp": true,
      "notificationsEnabled": true
    },
    {
      "applicationCode": "vallox-panel",
      "showHelp": false,
      "notificationsEnabled": false
    }
  ]
}

GET /profile/me/apps/:appCode — aplikační auth bootstrap

Po úspěšném JWT ověření atomicky zaznamená skutečný login do konkrétní aplikace a vrátí její preference. Pokud preference neexistují, vytvoří je s výchozími hodnotami. Právě tento endpoint je hranice, ze které admin detail počítá firstLoginAt, lastLoginAt a loginCount.

Admin endpointy pro změnu preferencí tuto aktivitu nezapisují.

Každý frontend musí tento endpoint zavolat právě při dokončení svého autentizovaného bootstrapu. Samotné ověření společného JWT nestačí: user-api z něj bez dalšího kontextu nemůže spolehlivě poznat, do které cílové aplikace se uživatel přihlásil. appCode se nikdy neodvozuje z rolí, preferencí ani z oprávnění.

http
GET /api/profile/me/apps/crm
Authorization: Bearer <token>

Response

json
{
  "success": true,
  "data": {
    "applicationCode": "crm",
    "showHelp": true,
    "notificationsEnabled": true
  }
}

POST /profile/me/apps/:appCode/activity — průběžná aktivita

Konzumující aplikace jej může volat při autentizovaném používání. Backend zápis agreguje nejvýše jednou za pět minut pro uživatele a aplikaci. Aktualizuje pouze lastActivityAt; login timestampy ani loginCount nemění.

http
POST /api/profile/me/apps/crm/activity
Authorization: Bearer <token>

POST /profile/me/apps/:appCode/logout — logout event

Zapíše auditní událost logout. Souhrn loginů nemění.

POST /internal/auth/application-events — serverová integrační cesta

Backend aplikace nebo centrální login callback může stejná autoritativní data zapsat přes interní API. Endpoint vyžaduje x-internal-api-key; nesmí se volat přímo z prohlížeče.

http
POST /api/internal/auth/application-events
Content-Type: application/json
x-internal-api-key: <secret>

{
  "email": "user@example.com",
  "appCode": "crm",
  "event": "login"
}

event podporuje:

  • login — nastaví první/poslední přihlášení, poslední aktivitu a zvýší loginCount;
  • activity — nejvýše jednou za pět minut posune pouze poslední aktivitu;
  • logout — zapíše auditní událost, ale nemění počítadlo přihlášení.

Frontendová a serverová cesta jsou alternativy. Jedna konkrétní aplikace má pro login používat právě jednu z nich, aby jeden login nezvýšil počítadlo dvakrát.


PUT /profile/me/apps/:appCode — aktualizace per-app preference

http
PUT /api/profile/me/apps/crm
Authorization: Bearer <token>
Content-Type: application/json

Request body

json
{
  "showHelp": false,
  "notificationsEnabled": true
}
PoleTypPopis
showHelpbooleanZobrazovat nápovědu v UI
notificationsEnabledbooleanGlobální přepínač notifikací pro tuto aplikaci

GET /profile/:email — profil jiného uživatele (admin)

http
GET /api/profile/jan.novak@firma.cz
Authorization: Bearer <admin-token>

Auth: JWT + AppAdmin nebo SuperAdmin


Notifikační preference

GET /profile/me/notifications/:appCode

Vrátí per-type notifikační preference pro aplikaci.

http
GET /api/profile/me/notifications/crm
Authorization: Bearer <token>

Response

json
{
  "code": "OK",
  "response": [
    {
      "notificationTypeId": 1,
      "code": "new_task",
      "name": "Nový úkol",
      "description": "Notifikace o přidělení nového úkolu (může být null).",
      "emailEnabled": true,
      "pushEnabled": true,
      "userCanDisable": true,
      "email": true,
      "push": false
    }
  ]
}
PoleTypVýznam
descriptionstring | nullVždy explicitní null, nikdy undefined — FE může zobrazit přímo.
emailEnabled / pushEnabledbooleanMaster switch z admin konfigurace (kanál povolen pro aplikaci).
userCanDisablebooleanPokud false, UI musí přepínače skrýt nebo zamknout.
email / pushbooleanAktuální stav přepínače pro uživatele (default z defaultEmail/defaultPush).

Seed generic typu

Každá aplikace má automaticky seedovaný notifikační typ generic (idempotent při init + nově při POST /applications). Díky tomu se v UI hned objeví přepínač pro testovací notifikace bez ručního seedu.

PUT /profile/me/notifications/preferences/bulk — bulk update

http
PUT /api/profile/me/notifications/preferences/bulk
Authorization: Bearer <token>
Content-Type: application/json
json
{
  "updates": [
    { "notificationTypeId": 1, "email": true, "push": true },
    { "notificationTypeId": 2, "email": false }
  ]
}

PUT /profile/me/notifications/preferences/:notificationTypeId — jeden typ

http
PUT /api/profile/me/notifications/preferences/1
Authorization: Bearer <token>
Content-Type: application/json
json
{
  "email": true,
  "push": false
}

WARNING

Uživatel může měnit preference pouze pro typy, kde userCanDisable = true. Pro ostatní typy vrátí API chybu 403.


Web Push subscripce

GET /profile/me/push/vapid-key — VAPID public key

Vrátí VAPID public key pro inicializaci Web Push v prohlížeči.

http
GET /api/profile/me/push/vapid-key
Authorization: Bearer <token>

Response

json
{
  "success": true,
  "data": {
    "vapidPublicKey": "BM8U3B..."
  }
}

POST /profile/me/push/subscribe — registrace subscripce

http
POST /api/profile/me/push/subscribe
Authorization: Bearer <token>
Content-Type: application/json
json
{
  "subscription": {
    "endpoint": "https://fcm.googleapis.com/fcm/send/...",
    "keys": {
      "p256dh": "BMutZ...",
      "auth": "Kd9..."
    }
  },
  "deviceLabel": "Můj notebook Chrome"
}

POST /profile/me/push/unsubscribe — odregistrování

http
POST /api/profile/me/push/unsubscribe
Authorization: Bearer <token>
Content-Type: application/json
json
{
  "endpoint": "https://fcm.googleapis.com/fcm/send/..."
}

GET /profile/me/push/subscriptions — seznam subscripcí

http
GET /api/profile/me/push/subscriptions
Authorization: Bearer <token>

Response

json
{
  "code": "OK",
  "response": [
    {
      "id": 12,
      "endpoint": "https://fcm.googleapis.com/fcm/send/...",
      "deviceLabel": "Můj notebook Chrome",
      "lastUsedAt": "2026-05-10T08:22:13.000Z",
      "createdAt": "2024-01-20T10:00:00.000Z",
      "updatedAt": "2026-05-10T08:22:13.000Z"
    }
  ]
}
PoleTypVýznam
lastUsedAtstring | nullČasová značka posledního úspěšného doručení (null, dokud z této subscripce nešel push).

DELETE /profile/me/push/subscriptions/:id — smazat jedno zařízení

Smaže subscription podle numerického id. Vlastnictví je vynuceno tokenem.

http
DELETE /api/profile/me/push/subscriptions/12
Authorization: Bearer <token>
http
204 No Content

Auto-cleanup

Při send-time se subscription, která vrátí HTTP 410 Gone nebo 404, automaticky smaže z DB, takže mrtvé záznamy nezůstávají.

Viz Web Push pro kompletní dokumentaci Web Push integrace.


In-app inbox

Notifikace se logují do notifications tabulky. Inbox endpointy nad ní vystavují historii pro UI (read/unread, mark read).

GET /profile/me/notifications/inbox

Vrátí poslední notifikace doručené aktuálnímu uživateli.

http
GET /api/profile/me/notifications/inbox?limit=50&unreadOnly=true&appCode=kgb
Authorization: Bearer <token>
Query paramTypDefaultVýznam
limitint (1–200)50Max počet položek.
unreadOnlyboolfalsePouze nepřečtené.
appCodestringZúžit na jednu aplikaci.
json
{
  "code": "OK",
  "response": [
    {
      "id": 421,
      "appCode": "kgb",
      "appName": "KGB",
      "type": "project_shared",
      "subject": "Sdílený projekt: Bytovka Praha",
      "status": "sent",
      "statusReason": null,
      "locale": "cs",
      "readAt": null,
      "createdAt": "2026-05-12T09:14:00.000Z",
      "payload": { "projectName": "Bytovka Praha" }
    }
  ]
}

GET /profile/me/notifications/inbox/unread-count

Levný počet nepřečtených pro badge v UI.

json
{ "code": "OK", "response": { "count": 3 } }

PATCH /profile/me/notifications/inbox/:id/read

Označí jednu položku za přečtenou. Vrací 204 No Content.

POST /profile/me/notifications/inbox/read-all

Označí všechny nepřečtené za přečtené, volitelně zúženo na { appCode }. Vrací { updated: number }.

Atrea User API — interní dokumentace