Статус: Актуальный
Создан: 2026-06-06
Последнее обновление: 2026-06-06
После аудита авторизации (OAuth + SPA same-domain) выявлено 17 проблем разной критичности.
Этот документ — единственный источник правды по порядку исправлений. Каждая фаза должна быть завершена полностью перед переходом к следующей.
Цель: закрыть дыры, которые позволяют злоумышленнику или багу получить/сохранить несанкционированный доступ.
access_token из URL query stringSet-Cookie: shserv_session=<session_token> (HttpOnly, Secure, SameSite=Lax), фронтенд хранит только факт аутентификации в памяти (Pinia), а API-клиент отправляет cookie автоматически (credentials: "include").server/SHServ/Controllers/AuthController.php — убрать ?access_token= из redirect, установить cookie в callback().server/SHServ/Integrations/GAuth/Store/DbTokenStore.php — генерация session_token уже есть, расширить колонки.webclient/src/app/main.js — удалить извлечение access_token из window.location.search.webclient/src/api/auth.js — оставить только localStorage как fallback, но основной auth — cookie.access_token.shserv_session.api/http.js не добавляет Authorization: Bearer если нет token в localStorage (cookie работает сама).expires_at и status='active' при Bearer resolutionserver/SHServ/Integrations/GAuth/AuthControllerTrait.php:
resolve_user_by_bearer() — добавить ['expires_at', '>', date('Y-m-d H:i:s')] в where.load_user_by_id() — добавить ['status', '=', 'active'].status != 'active') пользователя возвращает 401.authStore.init()init().webclient/src/app/main.js — инициализировать auth до app.use(router).webclient/src/router/index.js — guard должен доверять isAuthenticated, не редиректить если isLoading./#/devices не показывает login при валидной сессии.return_to от access_token при 401-редиректеreturn_to содержит старый access_token.webclient/src/api/client.js — guard при 401 формирует return_to через URLSearchParams.delete('access_token').access_token.Цель: пользователь остаётся залогиненым дни и недели без ручных повторных входов.
webclient/src/stores/auth.js — в init() при 401 сначала вызвать refreshToken(), потом повторить apiGet("/auth/me").webclient/src/api/client.js — при 401 ответа делать один вызов /auth/refresh (с queue, чтобы не делать N параллельных), затем retry исходного запроса.refresh() и logout()$_SESSION, но SPA может работать без session cookie.server/SHServ/Controllers/AuthController.php — refresh() и logout() должны извлекать access_token из Authorization header, искать session по нему в shserv_sessions, и работать от найденной записи.POST /auth/refresh с Authorization: Bearer <token> возвращает новый токен даже без PHP session cookie.POST /auth/logout с валидным Bearer отзывает сессию.expires_in на фронтенде (proactive refresh)webclient/src/api/auth.js — сохранять expires_at (timestamp) рядом с token в localStorage.webclient/src/stores/auth.js — setInterval или setTimeout на обновление за 60 секунд до expiration.main.js и LoginPage.vue оба пытаются обработать callback → лишний me.webclient/src/features/auth/pages/LoginPage.vue — удалить onMounted логику с route.query.access_token.webclient/src/app/main.js — оставить единственную точку обработки.LoginPage.vue не содержит логики OAuth callback.Цель: защитить от stolen tokens, несовместимостей окружения и abuse.
server/SHServ/Integrations/GAuth/Store/DbTokenStore.php — put() добавляет ip_address, user_agent.server/SHServ/Integrations/GAuth/AuthControllerTrait.php — resolve_user_by_bearer() проверяет совпадение.session_suspicious.getallheaders() для nginxserver/SHServ/Controllers/WebhookController.php — заменить getallheaders() на fallback из $_SERVER./etc/nginx/sites-enabled/default (prod) — limit_req_zone + limit_req для /auth/./auth/login или /auth/callback в минуту с одного IP → 429.PermissionResolver делает 3 запроса к БД на каждый API call.server/SHServ/Integrations/GAuth/PermissionResolver.php — кешировать результат в $_SESSION на время запроса или с TTL.require_permission() в рамках одного запроса не бьют в БД.Цель: убрать технический долг, привести код к стандартам проекта.
server/database/migrations/2026_06_06_000001_gauth_integration.php — заменить raw INSERT IGNORE на ThinBuilder::insert() или параметризованные запросы.webclient/src/features/auth/pages/LoginPage.vue — заменить var(--color-*) на .text-primary, .text-muted, .border-subtle и т.д.<style scoped> блоке нет var(--color-*).server/.env и server/SHServ/.env дублируют GAuth секреты. Поддерживать два файла рискованно.server/SHServ/config.php — уже ищет .env в текущей и родительской директории, так что server/SHServ/.env можно удалить если он дублирует.Перед переходом к следующей фазе проверить:
npm test, server/tests/) не сломаны.Phase-1: cookie-based session for OAuth callback).git pull (не ручные правки).