style: fix import sorting and formatting after lint

ruff auto-fix for import ordering in cabinet/subscription.py and
formatting adjustments across changed files.
This commit is contained in:
Fringg
2026-03-12 22:58:35 +03:00
parent 3efa24bab3
commit f59b215645
13 changed files with 3371 additions and 139 deletions
+243
View File
@@ -0,0 +1,243 @@
# RBAC + ABAC Design for Bedolaga Cabinet
**Date:** 2026-02-25
**Status:** Approved
**Approach:** Hybrid RBAC + ABAC (Attribute-Based Access Control)
## Overview
Full role-based access control with attribute-based policies for the Telegram bot admin cabinet. Replaces the current binary `isAdmin` check (ADMIN_IDS env var) with granular permissions, hierarchical roles, ABAC policy engine, and comprehensive audit logging.
## Architecture Decisions
- **Hierarchy:** superadmin > admin > moderator (managed via `level` field)
- **Management:** Through cabinet UI only (no invite links)
- **Audit logging:** ALL admin API calls (GET included)
- **Role templates:** 5 presets (Superadmin, Admin, Moderator, Marketer, Support) + custom roles
- **Assignment:** Via UI, superadmin/admin assigns roles to users from the user list
## Data Model
### Tables
**`admin_roles`** — role definitions with permission groups
| Column | Type | Description |
|--------|------|-------------|
| id | SERIAL PK | |
| name | VARCHAR(100) UNIQUE | "Moderator", "Marketer" |
| description | TEXT | Human-readable role description |
| level | INTEGER DEFAULT 0 | Hierarchy: 0=viewer, 50=moderator, 100=admin, 999=superadmin |
| permissions | JSONB | ["users:read", "tickets:*", ...] |
| color | VARCHAR(7) | HEX badge color for UI |
| icon | VARCHAR(50) | Icon name for UI |
| is_system | BOOLEAN DEFAULT false | System role, cannot be deleted |
| is_active | BOOLEAN DEFAULT true | Soft disable |
| created_by | BIGINT FK users.id | |
| created_at | TIMESTAMPTZ | |
| updated_at | TIMESTAMPTZ | |
**`user_roles`** — M2M user-to-role assignment
| Column | Type | Description |
|--------|------|-------------|
| id | SERIAL PK | |
| user_id | BIGINT FK users.id | |
| role_id | INTEGER FK admin_roles.id | |
| assigned_by | BIGINT FK users.id | Who assigned |
| assigned_at | TIMESTAMPTZ | |
| expires_at | TIMESTAMPTZ NULL | Temporary role (vacation cover, etc.) |
| is_active | BOOLEAN DEFAULT true | |
| UNIQUE(user_id, role_id) | | |
**`access_policies`** — ABAC attribute-based policies
| Column | Type | Description |
|--------|------|-------------|
| id | SERIAL PK | |
| name | VARCHAR(200) | Policy name |
| description | TEXT | |
| role_id | INTEGER FK admin_roles.id NULL | Bound to role or global |
| priority | INTEGER DEFAULT 0 | Evaluation order |
| effect | VARCHAR(10) | "allow" or "deny" |
| conditions | JSONB | Attribute conditions (see format below) |
| resource | VARCHAR(100) | "users", "tickets", "*" |
| actions | JSONB | ["read", "edit"] or ["*"] |
| is_active | BOOLEAN DEFAULT true | |
| created_by | BIGINT FK users.id | |
| created_at | TIMESTAMPTZ | |
**Conditions JSONB format:**
```json
{
"time_range": {"start": "09:00", "end": "18:00", "timezone": "Europe/Moscow"},
"ip_whitelist": ["192.168.1.0/24"],
"max_actions_per_hour": 100,
"require_2fa": true,
"user_attributes": {"status": ["active"]}
}
```
**`admin_audit_log`** — immutable action log (INSERT only)
| Column | Type | Description |
|--------|------|-------------|
| id | BIGSERIAL PK | |
| user_id | BIGINT FK users.id | Who acted |
| action | VARCHAR(100) | "users:edit", "roles:create" |
| resource_type | VARCHAR(50) | "user", "role", "ticket" |
| resource_id | VARCHAR(100) NULL | ID of affected resource |
| details | JSONB | Before/after diff |
| ip_address | INET | |
| user_agent | TEXT | |
| status | VARCHAR(20) | "success", "denied", "error" |
| request_method | VARCHAR(10) | GET/POST/PUT/DELETE |
| request_path | TEXT | |
| created_at | TIMESTAMPTZ | |
### Permission Registry
Format: `section:action`. Wildcard: `section:*` (all actions), `*:*` (superadmin).
| Section | Actions |
|---------|---------|
| users | read, edit, block, delete, sync |
| tickets | read, reply, close, settings |
| stats | read, export |
| broadcasts | read, create, edit, delete, send |
| tariffs | read, create, edit, delete |
| promocodes | read, create, edit, delete, stats |
| promo_groups | read, create, edit, delete |
| promo_offers | read, create, edit, send |
| campaigns | read, create, edit, delete, stats |
| partners | read, edit, approve, revoke, settings |
| withdrawals | read, approve, reject |
| payments | read, export |
| payment_methods | read, edit |
| servers | read, edit |
| remnawave | read, sync, manage |
| traffic | read, export |
| settings | read, edit |
| roles | read, create, edit, delete, assign |
| audit_log | read, export |
| channels | read, edit |
| ban_system | read, ban, unban |
| wheel | read, edit |
| apps | read, edit |
| email_templates | read, edit |
| pinned_messages | read, create, edit, delete |
| updates | read, manage |
### Preset Roles
1. **Superadmin** (level 999, system): `*:*`
2. **Admin** (level 100, system): all except `roles:delete` on system roles
3. **Moderator** (level 50): `users:read,edit,block`, `tickets:*`, `ban_system:*`
4. **Marketer** (level 30): `campaigns:*`, `broadcasts:*`, `promocodes:*`, `promo_offers:*`, `stats:read`, `pinned_messages:*`
5. **Support** (level 20): `tickets:read,reply`, `users:read`
## Backend Architecture
### Policy Engine (PermissionService)
Evaluation flow:
1. Get user roles (active, not expired)
2. Merge all permissions from roles
3. Check requested permission (exact match or wildcard)
4. If access_policies exist → evaluate conditions (time, IP, rate limit)
5. Deny policies take priority over allow
6. Return: allow/deny + reason
### FastAPI Dependency
Replace `get_current_admin_user` with parameterized `require_permission()`:
```python
def require_permission(*permissions: str):
async def dependency(user=Depends(get_current_cabinet_user), ...):
# Check via PermissionService
# Log to audit_log
# Return user if ok, else 403
return dependency
```
### JWT Enhancement
Add to JWT payload:
```json
{
"permissions": ["users:read", "users:edit", "tickets:*"],
"role_level": 50,
"roles": ["Moderator"]
}
```
### New API Endpoints
```
GET /cabinet/admin/roles — list roles
POST /cabinet/admin/roles — create role
PUT /cabinet/admin/roles/:id — update role
DELETE /cabinet/admin/roles/:id — delete (non-system)
GET /cabinet/admin/roles/users — users with roles
POST /cabinet/admin/roles/assign — assign role to user
DELETE /cabinet/admin/roles/assign/:id — revoke role
GET /cabinet/admin/policies — list policies
POST /cabinet/admin/policies — create policy
PUT /cabinet/admin/policies/:id — update policy
DELETE /cabinet/admin/policies/:id — delete policy
GET /cabinet/admin/audit-log — log with filters
GET /cabinet/admin/audit-log/export — CSV/JSON export
GET /cabinet/auth/me/permissions — current user permissions
```
### Audit Middleware
Logs every request to `/admin/*`: user_id, action, resource, details, IP, status.
### Hierarchy Rule
Users can only manage roles with `level` lower than their own.
## Frontend Architecture
### Permission Store (Zustand)
`usePermissionStore`:
- State: permissions[], roles[], roleLevel, isLoading
- Actions: fetchPermissions(), hasPermission(), hasAnyPermission(), hasAllPermissions(), canManageRole()
### Route Guards
`PermissionRoute` component replaces `AdminRoute` with permission parameter.
### PermissionGate Component
Hides/shows UI elements based on permissions with optional fallback.
### New Pages
1. **AdminRoles** — role CRUD with permission matrix
2. **AdminRoleAssign** — assign roles to users
3. **AdminPolicies** — ABAC policy management with visual condition builder
4. **AdminAuditLog** — filterable timeline with before/after diffs
### i18n
New keys in all 4 locales (ru, en, zh, fa).
## Migration Strategy
1. Auto-create Superadmin role for users from ADMIN_IDS/ADMIN_EMAILS
2. `get_current_admin_user` stays backward-compatible via `require_permission("*:*")`
3. Gradual route migration to `require_permission()`
4. `is_admin` endpoint returns true if user has ANY role (level > 0)
## Security
- Backend always re-validates (JWT is UI hint only)
- Rate limiting on RBAC endpoints
- Deny policies win over allow
- Cannot delete last superadmin
- Cannot lower own level
- Audit log is immutable (INSERT only, no UPDATE/DELETE)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff