# Техническое задание: Интеграция smart-home-serv с gnexus-auth

## 1. Цели

1. Удалить локальную регистрацию/аутентификацию (таблицы `users`, `sessions`, `profiles`).
2. Использовать `gnexus-auth` (auth.gnexus.space) как единый источник идентичности.
3. Реализовать ролевую модель: **superadmin**, **admin**, **user**, **guest**.
4. Реализовать permission-based ACL с возможностью урезания прав для конкретного пользователя админом/суперадмином.
5. Поддержать группы пользователей, синхронизируемые с `gnexus-auth`.
6. Обработать webhooks от `gnexus-auth` для актуализации ролей, прав, статусов.
7. Минимальные изменения в существующем API устройств/скриптов/зон.

## 2. Архитектурные решения

### 2.1. Пакет `gnexus/auth-client` и HTTP-клиент

**Пакет:** `gnexus/auth-client` (репозиторий `git.gnexus.space/root/gnexus-auth-client-php`).

**Подключение:** через Composer `repositories.type = vcs`:
```json
"repositories": [{
    "type": "vcs",
    "url": "https://git.gnexus.space/root/gnexus-auth-client-php.git"
}],
"require": {
    "gnexus/auth-client": "^0.1"
}
```

**PSR-реализации:** пакет требует только **интерфейсы** PSR-18/17/7 (`psr/http-client`, `psr/http-factory`, `psr/http-message`). Реализацию пишем сами — thin cURL-адаптер:
- `CurlHttpClient` — PSR-18 `ClientInterface`
- `Psr7Factory` — PSR-17 `RequestFactoryInterface` + `StreamFactoryInterface`
- `Psr7Request`, `Psr7Response`, `Psr7Stream` — PSR-7 DTO
- Ожидаемый объём ~350 строк, zero дополнительных Composer-зависимостей.
- Расположение: `server/SHServ/Integrations/GAuth/Http/`.

### 2.2. Хранение токенов

**Решение:** PHP `$_SESSION` + опциональная таблица `shserv_sessions` для долгосрочного хранения refresh-токена.

**Обоснование:**
- Access token живёт 15 минут — храним в `$_SESSION`.
- Refresh token живёт 30 дней — при long-term «запомнить меня» сохраняем в `shserv_sessions` (cookie → session_id → refresh_token).
- Это позволяет восстанавливать сессию после закрытия браузера без повторного OAuth-флоу.
- При logout — отзываем refresh token через `gnexus-auth` и чистим локальную сессию.

### 2.3. Группы пользователей

**Решение:** синхронизировать с `gnexus-auth` через webhooks `group.user_added` / `group.user_removed`.

**Обоснование:**
- Группы создаются/удаляются в админке `gnexus-auth`.
- SHServ реплицирует структуру групп и membership в локальные таблицы.
- Права групп применяются как union к правам пользователя.

### 2.4. Guest

**Решение:** `guest` — явная client role в `gnexus-auth`, аутентифицированный пользователь.

**Обоснование:**
- По умолчанию **без авторизации ничего нельзя** — все endpoints требуют `Authorization: Bearer`.
- `guest` — это реальная роль, назначаемая в `gnexus-auth` (как client role, не system_role).
- Новым пользователям по умолчанию даётся роль `guest` с минимальным набором прав.
- Админ/суперадмин может повысить роль до `user` или `admin`.
- Это обеспечивает полный контроль: каждый, кто заходит в систему, — идентифицирован.

## 3. Модель данных (новые таблицы)

### 3.1. `shserv_users` — локальное отображение пользователей

```sql
CREATE TABLE shserv_users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    gauth_user_id VARCHAR(64) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL,
    display_name VARCHAR(255),
    avatar_url VARCHAR(500),
    system_role VARCHAR(32) NOT NULL DEFAULT 'user',   -- superadmin | admin | user
    status VARCHAR(32) NOT NULL DEFAULT 'active',         -- active | blocked | archived
    max_role VARCHAR(32) NULL,                          -- роль, назначенная в gnexus-auth
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_gauth (gauth_user_id),
    INDEX idx_email (email)
);
```

### 3.2. `shserv_roles` — локальные роли (mirror + override)

```sql
CREATE TABLE shserv_roles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    slug VARCHAR(32) NOT NULL UNIQUE,          -- superadmin | admin | user | guest
    name VARCHAR(64) NOT NULL,
    is_system TINYINT(1) NOT NULL DEFAULT 0,  -- system role из gnexus-auth
    default_permissions JSON,                  -- массив permission slugs
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

### 3.3. `shserv_permissions` — реестр прав

```sql
CREATE TABLE shserv_permissions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    slug VARCHAR(64) NOT NULL UNIQUE,          -- devices.scan | devices.control | scripts.edit | areas.manage | admin.users | admin.roles
    name VARCHAR(128) NOT NULL,
    description TEXT,
    default_for_roles JSON,                    -- {"superadmin": true, "admin": true, "user": false, "guest": false}
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

### 3.4. `shserv_user_permissions` — персональные overrides

```sql
CREATE TABLE shserv_user_permissions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    permission_slug VARCHAR(64) NOT NULL,
    granted TINYINT(1) NOT NULL DEFAULT 1,     -- 1 = разрешено, 0 = явно запрещено
    set_by_user_id INT NULL,                  -- кто установил override
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_user_perm (user_id, permission_slug),
    FOREIGN KEY (user_id) REFERENCES shserv_users(id) ON DELETE CASCADE
);
```

### 3.5. `shserv_groups` — группы (реплика из gnexus-auth)

```sql
CREATE TABLE shserv_groups (
    id INT AUTO_INCREMENT PRIMARY KEY,
    gauth_group_id VARCHAR(64) NOT NULL UNIQUE,
    slug VARCHAR(64) NOT NULL,
    name VARCHAR(128) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

### 3.6. `shserv_group_members` — membership

```sql
CREATE TABLE shserv_group_members (
    id INT AUTO_INCREMENT PRIMARY KEY,
    group_id INT NOT NULL,
    user_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_membership (group_id, user_id),
    FOREIGN KEY (group_id) REFERENCES shserv_groups(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES shserv_users(id) ON DELETE CASCADE
);
```

### 3.7. `shserv_group_permissions` — права групп

```sql
CREATE TABLE shserv_group_permissions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    group_id INT NOT NULL,
    permission_slug VARCHAR(64) NOT NULL,
    granted TINYINT(1) NOT NULL DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_group_perm (group_id, permission_slug),
    FOREIGN KEY (group_id) REFERENCES shserv_groups(id) ON DELETE CASCADE
);
```

### 3.8. `shserv_sessions` — локальные сессии (для refresh-токенов)

```sql
CREATE TABLE shserv_sessions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    session_token VARCHAR(128) NOT NULL UNIQUE,
    refresh_token VARCHAR(255),
    access_token VARCHAR(255),
    expires_at TIMESTAMP NULL,
    ip_address VARCHAR(45),
    user_agent VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_token (session_token),
    FOREIGN KEY (user_id) REFERENCES shserv_users(id) ON DELETE CASCADE
);
```

## 4. Интеграционный слой (`server/SHServ/Integrations/GAuth/`)

Пакет `gnexus/auth-client` уже предоставляет: `GAuthClient`, `GAuthConfig`, `HttpTokenEndpoint`, `HttpRuntimeUserProvider`, `HmacWebhookVerifier`, `JsonWebhookParser`, `PkceGenerator`, весь DTO и exception набор. Мы реализуем только то, что пакет оставляет на усмотрение consuming-приложения:

```
Integrations/GAuth/
  Http/
    CurlHttpClient.php          -- PSR-18 ClientInterface (thin cURL)
    Psr7Factory.php             -- PSR-17 RequestFactory + StreamFactory
    Psr7Request.php             -- PSR-7 RequestInterface
    Psr7Response.php            -- PSR-7 ResponseInterface
    Psr7Stream.php              -- PSR-7 StreamInterface
  Store/
    SessionStateStore.php       -- StateStoreInterface (на $_SESSION)
    SessionPkceStore.php        -- PkceStoreInterface (на $_SESSION)
    DbTokenStore.php            -- TokenStoreInterface (на shserv_sessions)
  Webhook/
    WebhookRouter.php           -- route event.type → handler
    Handlers/
      UserHandler.php           -- user.* events
      RoleHandler.php           -- client.roles_changed
      PermissionHandler.php     -- client.permissions_changed
      GroupHandler.php          -- group.user_added / removed
      SessionHandler.php        -- auth.global_logout / session.revoked
  AuthService.php               -- high-level wrapper: login URL, callback, logout, me
  UserResolver.php              -- маппинг AuthenticatedUser → shserv_users
  PermissionResolver.php        -- resolve effective permissions
```

## 5. Ролевая модель и права

### 5.1. Роли

| Роль | Описание | Источник | Тип |
|------|----------|----------|-----|
| `superadmin` | Полный доступ | `AuthenticatedUser->systemRole` | system_role |
| `admin` | Администрирование SHServ | `AuthenticatedUser->systemRole` | system_role |
| `user` | Обычный пользователь | `AuthenticatedUser->systemRole` | system_role |
| `guest` | Аутентифицированный с минимальными правами | `ClientAccess->roleIds[]` | client_role |

**Модель:**
- `systemRole` (`superadmin`/`admin`/`user`) определяет административный уровень.
- `clientAccessList[].roleIds[]` — клиентские роли в контексте SHServ (где `clientId === GAUTH_CLIENT_ID`).
- `guest` — client role, назначается по умолчанию новым пользователям.
- **Нет анонимного доступа** — любой запрос к API требует валидного access token.

### 5.2. Права (starter set)

Пакет `gnexus/auth-client` возвращает `permissionIds[]` в `ClientAccess` (вместе с `roleIds[]`). Эти ID мапятся на локальные `shserv_permissions.slug`.

Starter set прав SHServ:

```text
devices.view          -- просмотр списка устройств
devices.scan          -- сканирование сети
devices.control       -- управление устройствами
devices.setup         -- добавление новых устройств
devices.edit          -- редактирование устройств
devices.delete        -- удаление устройств
areas.view            -- просмотр зон
areas.manage          -- управление зонами
scripts.view          -- просмотр скриптов
scripts.edit          -- редактирование скриптов
scripts.run           -- запуск скриптов
firmware.view         -- просмотр прошивок
firmware.upload       -- загрузка прошивок
admin.users           -- управление пользователями
admin.roles           -- управление ролями/правами
admin.audit           -- просмотр аудита
settings.edit         -- изменение настроек системы
```

### 5.3. Алгоритм определения эффективных прав

```
effective_permissions(user) =
  union(
    permissions_from_role(user.system_role),           -- дефолт по роли
    permissions_from_groups(user.groups),                -- права групп
    user_overrides(user.id)                              -- персональные overrides
  )
  minus explicitly_denied(user.id)
```

- `superadmin` всегда имеет все права (fast path).
- `admin` / `user` / `guest` — через таблицы.
- Override `granted = 0` явно запрещает право, даже если оно есть у роли/группы.

### 5.4. Урезание прав

Админ/суперадмин через API `POST /api/v1/admin/users/{id}/permissions` может:
- Добавить право (`granted = 1`) — даёт право, которого нет у роли.
- Запретить право (`granted = 0`) — урезает право, имеющееся у роли.
- Удалить запись — возвращает дефолт роли.

## 6. API (новые и изменённые endpoints)

### 6.1. Auth flow (OAuth-like)

```text
GET  /auth/login     → редирект на gnexus-auth /oauth/authorize
GET  /auth/callback  → обмен code на tokens, создание/обновление local user, редирект в приложение
POST /auth/logout   → отзыв refresh token, очистка сессии
GET  /auth/me       → текущий пользователь + его effective permissions
```

### 6.2. Webhook endpoint

```text
POST /webhooks/gnexus-auth
```
- Verify HMAC-SHA256 через `GAuthClient::verifyWebhook()`.
- Parse через `GAuthClient::parseWebhook()`.
- Route на `WebhookHandler` по `event.type`.

### 6.3. Административные endpoints (требуют `admin.users` / `admin.roles`)

```text
GET    /api/v1/admin/users
GET    /api/v1/admin/users/{id}
PATCH  /api/v1/admin/users/{id}/role
POST   /api/v1/admin/users/{id}/permissions      -- {permission_slug, granted}
DELETE /api/v1/admin/users/{id}/permissions/{slug}
GET    /api/v1/admin/roles
GET    /api/v1/admin/permissions
GET    /api/v1/admin/groups
```

### 6.4. Защита существующих endpoints

**Все** существующие `/api/v1/*` endpoints (кроме устройственных `/about` и `/events/new`) требуют авторизации.

Для Fury Router (нет middleware) — внедряем проверку в базовый контроллер:
- Добавить `AuthControllerTrait` с методом `require_auth()` и `require_permission($slug)`.
- Вызывать в начале каждого защищённого метода контроллера.

### 6.5. Vue client API изменения

- `client.js` — добавить `Authorization: Bearer {access_token}` header ко всем запросам.
- При 401 от API — редирект на `/auth/login`.
- Logout — вызов `POST /auth/logout`, затем очистка local storage.

## 7. Vue web client изменения

### 7.1. Auth store (`src/stores/auth.js`)

```javascript
export const useAuthStore = defineStore('auth', () => {
  const user = ref(null);
  const permissions = ref([]);
  const isAuthenticated = computed(() => !!user.value);
  const hasPermission = (slug) => permissions.value.includes(slug);
  // ...
});
```

### 7.2. Login flow

1. Пользователь нажимает «Войти» → `window.location = '/auth/login'`.
2. gnexus-auth → callback → backend создаёт сессию → редирект на Vue app (`/#/`).
3. Vue app при старте вызывает `GET /auth/me` → получает user + permissions.

### 7.3. UI адаптации

- Условный рендеринг кнопок/меню по `hasPermission()`.
- Скрытие админ-разделов для non-admin.
- Login/Logout кнопки в `AppShell`.

## 8. Обработка webhooks

### 8.1. Подписываемся на события

```text
user.updated
user.blocked
user.unblocked
user.archived
user.restored
user.deleted
client.roles_changed
client.permissions_changed
client.access_granted
client.access_revoked
client.access_denied
group.user_added
group.user_removed
auth.global_logout
session.revoked
```

### 8.2. Логика обработки

| Событие | Действие |
|---------|----------|
| `user.updated` | Обновить `display_name`, `avatar_url`, `email` в `shserv_users` |
| `user.blocked` | `status = 'blocked'`, очистить сессии |
| `user.unblocked` | `status = 'active'` |
| `user.deleted` | Удалить из `shserv_users` (cascade cleanup) |
| `client.roles_changed` | Обновить `system_role` / client roles |
| `client.permissions_changed` | Обновить `shserv_user_permissions` |
| `group.user_added` | `INSERT shserv_group_members` |
| `group.user_removed` | `DELETE shserv_group_members` |
| `auth.global_logout` | Удалить все `shserv_sessions` для user |
| `session.revoked` | Удалить конкретную сессию из `shserv_sessions` |

## 9. Фазы реализации

### Phase 0 — Инфраструктура (1–2 дня)
- [x] Добавить thin PSR-18/17 cURL адаптер (`CurlHttpClient` + `Psr7Factory` + DTO).
- [x] Подключить `gnexus/auth-client` через Composer `vcs` репозиторий (`git.gnexus.space/root/gnexus-auth-client-php`).
- [x] Создать миграции (8 новых таблиц).
- [x] Создать `GAuthConfig` и `AuthService` wrapper.
- [x] Настроить `Client` в gnexus-auth (client_id, secret, redirect_uri, webhook).
- [x] Проверить совместимость: пакет требует PHP ^8.3, текущий сервер — 8.5.6 ✓

### Phase 1 — OAuth flow (2–3 дня)
- [x] `GET /auth/login` — `GAuthClient::buildAuthorizationRequest(..., scopes: ['openid','email','profile','roles','permissions'])` → редирект.
- [x] `GET /auth/callback` — `exchangeAuthorizationCode()`, `fetchUser()`.
  - `AuthenticatedUser` содержит: `userId`, `email`, `systemRole`, `status`, `profile`, `clientAccessList` (с `roleIds[]`, `permissionIds[]`).
  - Upsert в `shserv_users`, маппинг `gauth_user_id`.
- [x] `POST /auth/logout` — `revokeToken()` + local cleanup.
- [x] `GET /auth/me` — текущий пользователь + effective permissions.
- [x] `SessionStateStore` + `SessionPkceStore` на PHP `$_SESSION`.
- [x] `DbTokenStore` для refresh-токена (связь `session_token` ↔ `refresh_token`).

### Phase 2 — Права и роли (2–3 дня)
- [x] Seed таблицы `shserv_roles`, `shserv_permissions`.
- [x] `PermissionResolver` — алгоритм union/minus.
- [x] `AuthControllerTrait` — `require_auth()`, `require_permission()`.
- [x] Защитить существующие API endpoints.
- [ ] Админ endpoints для управления permissions.

### Phase 3 — Webhooks (1–2 дня)
- [x] `POST /webhooks/gnexus-auth` endpoint.
- [x] Верификация: `HmacWebhookVerifier::verify(rawBody, headers, secret)` (из пакета).
- [x] Парсинг: `JsonWebhookParser::parse(rawBody)` → `WebhookEvent` (из пакета).
- [x] `WebhookRouter` → event-specific handlers (UserHandler, RoleHandler, GroupHandler, SessionHandler).
- [ ] Настроить webhook subscription в gnexus-auth.

### Phase 4 — Vue client (2–3 дня)
- [x] Auth store (`src/stores/auth.js`) с `user`, `permissions`, `isAuthenticated`, `hasPermission()`.
- [x] Bearer token injection в `client.js` / `http.js`.
- [x] Interceptor: при 401 от API → `window.location = '/auth/login'` (backend сделает redirect в gnexus-auth).
- [x] Lazy token refresh: при 401 от `gnexus-auth` runtime API или `expiresAt` прошёл — backend-endpoint `POST /auth/refresh` обновляет access token.
- [x] Conditional UI: скрытие кнопок/разделов по `hasPermission()`.
- [x] Login/Logout в `AppShell`.

### Phase 5 — Legacy web client (1 день)
- [ ] Аналогичные изменения для `webclient_legacy` (если ещё используется в продакшене).

### Phase 6 — Cleanup (1 день)
- [x] Удалить `Example_AuthController.php`, старые auth routes.
- [ ] Архивировать legacy таблицы: `users` → `_legacy_users`, `sessions` → `_legacy_sessions`, `profiles` → `_legacy_profiles` (на случай аудита).
- [ ] Удалить `SHServ/Entities/User.php`, `Session.php`, `Profile.php` (или перенести в `_legacy/`).
- [ ] Обновить `docs/server-api.md` и `docs/architecture.md`.
- [ ] Smoke-test end-to-end.

## 10. Дополнительные решения и открытые вопросы

1. **Client credentials** — хранить в `.env`:
   ```
   GAUTH_BASE_URL=https://auth.gnexus.space
   GAUTH_CLIENT_ID=shserv
   GAUTH_CLIENT_SECRET=...
   GAUTH_WEBHOOK_SECRET=...
   ```
   Пакет `gnexus/auth-client` читает их через `GAuthConfig` (передаются явно в конструктор).

2. **Redirect URI** — регистрировать в gnexus-auth Client:
   - Prod: `https://smarthome.gnexus.space/auth/callback`
   - Dev: `http://smart-home-serv.local/auth/callback`
   - Local: `http://localhost/auth/callback` (если gnexus-auth разрешает local/dev origins).

3. **Token refresh** — lazy refresh:
   - Access token храним в `$_SESSION` + `expiresAt`.
   - При 401 от runtime API или `expiresAt` прошёл — frontend вызывает `POST /auth/refresh`.
   - Backend берёт refresh token из `DbTokenStore`, делает `GAuthClient::refreshToken()`, обновляет сессию.

4. **Миграция старых пользователей** — legacy таблицы (`users`, `sessions`, `profiles`) архивировать (`RENAME TABLE users TO _legacy_users`), не удалять.

5. **Аудит в SHServ** — админ-действия (смена роли, override permission) логировать в `shserv_audit`:
   ```sql
   CREATE TABLE shserv_audit (
       id INT AUTO_INCREMENT PRIMARY KEY,
       actor_user_id INT NOT NULL,
       action VARCHAR(64) NOT NULL,
       target_type VARCHAR(32),
       target_id INT,
       old_value JSON,
       new_value JSON,
       created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
   );
   ```

6. **Тестирование пакета** — пакет `gnexus/auth-client` имеет unit tests (`tests/Unit/`). Перед интеграцией рекомендуется прогнать их:
   ```bash
   cd packages/auth-client && php vendor/bin/phpunit
   ```
