---
layout: 'page'
uri: '/guides/permissions'
position: 2
slug: 'guides-permissions'
parent: 'guides'
navTitle: 'Permissions'
title: 'Permissions'
description: 'Jak fungují role a permissions. Deklarace v handlerech, kontrola přes bus, seznam pro frontend.'
---
# Permissions
Permissions se nikde neudržují v listu — vznikají **automaticky** z command/query handlerů. Každý handler deklaruje svou permission a tím se sám registruje.
## Princip
1. Každý command/query handler implementuje buď `shared.Permissioned` (vyžaduje permission) nebo `shared.SkipPermission` (explicitní opt-out).
2. `PermissionsRegistry` posbírá permissions ze všech handlerů při startu.
3. `PermissionChecker` ji používá v bus middleware — zablokuje request, pokud role usera nesedí.
4. Login/profile response obsahuje `permissions: []string` — frontend podle nich skryje UI.
## Formát permission
```
<doména>:<akce> např. profile:read, auth:logout, admin:users:create
```
**Pravidlo:** role `admin` má přístup ke všemu. Ostatní role mají **všechno kromě `admin:*`**.
## Deklarace v handleru
### Command s permission
```go
type DeleteUserCommand struct {
UserID string
}
func (DeleteUserCommand) RequiredPermission() string { return "admin:users:delete" }
```
### Command bez permission (veřejný)
```go
type LoginCommand struct {
Nickname string
Password string
}
func (LoginCommand) SkipPermissionCheck() {}
```
Každý command/query **MUSÍ** implementovat jedno z těch dvou. Jinak `AuthorizeMiddleware` vrátí error — ochrana proti zapomenuté deklaraci.
## Kontrola (backend)
Middleware `AuthorizeMiddleware` se spouští automaticky v command/query bus. Pro každý dispatch:
1. Pokud command implementuje `SkipPermission` → propustí.
2. Pokud implementuje `Permissioned` → zavolá `PermissionChecker.Check(ctx, permission)`.
3. Checker přečte `AuthClaims` z kontextu a volá `shared.IsPermissionAllowedForRole(permission, role)`.
4. Když role nesedí → `*shared.PermissionError` (HTTP 403). Když nejsou claims → `*shared.AuthError` (HTTP 401).
## Seznam pro frontend
Login a `/profile` response obsahují seznam permissions pro konkrétní roli:
```json
{
"access_token": "...",
"access_expiration": 900,
"user": {
"id": "...",
"nickname": "alice",
"role": "user",
"permissions": ["auth:logout", "profile:read", "profile:update"]
}
}
```
Tento seznam sestavuje `PermissionsRegistry`:
```go
reg := shared.NewPermissionsRegistry([]shared.Permissioned{
authcmd.LogoutCommand{},
profilecmd.ChangePasswordCommand{},
profileqry.GetProfileQuery{},
usercmd.CreateUserCommand{},
usercmd.UpdateUserCommand{},
usercmd.DeleteUserCommand{},
userqry.ListUsersQuery{},
dashboardqry.GetUserDashboardQuery{},
dashboardqry.GetAdminDashboardQuery{},
})
reg.ForRole("admin") // vše
reg.ForRole("user") // bez admin:*
```
Wire registruje všechny handlery ve `container_provider.go` (provider `providePermissionsRegistry`) — kdykoliv přidáš nový `Permissioned` handler, přidáš ho i do tohoto seznamu. Žádná druhá konfigurace neexistuje — permission registry je jediný zdroj pravdy a derived přímo z kódu.
## Použití ve frontendu
```typescript
const { hasPermission, hasAnyPermission, isAdmin } = useAuth();
hasPermission('admin:users:create')
hasAnyPermission(['profile:read', 'profile:update'])
isAdmin()
```
Detaily [useAuth API](/guides/frontend-utils#useauth).
## Přidání nové permission
1. Vytvoř command/query s `RequiredPermission()` vracející novou hodnotu (např. `"reports:export"`).
2. V `container_provider.go` přidej instanci commandu do slice pro `NewPermissionsRegistry`.
3. `make di && make test` — registry o ní ví, handler je chráněný, frontend ji dostane v login response.
Žádný seznam permissions k ručnímu udržování neexistuje — tím je zajištěno, že **permission == existující handler**.
---
[← Authentication](/guides/auth.md) | [Frontend Utils →](/guides/frontend-utils.md)