Статус: Актуальный
Создан: 2026-06-06
Последнее обновление: 2026-06-06 (Phase 3 завершена)
После аудита авторизации (OAuth + SPA same-domain) выявлено 17 проблем разной критичности.
Этот документ — единственный источник правды по порядку исправлений. Каждая фаза должна быть завершена полностью перед переходом к следующей.
Цель: закрыть дыры, которые позволяют злоумышленнику или багу получить/сохранить несанкционированный доступ.
access_token из URL query string ✅session_start() уже устанавливает session cookie, фронтенд хранит только факт аутентификации в памяти (Pinia), а API-клиент отправляет cookie автоматически (credentials: "include").server/SHServ/Controllers/AuthController.php — убрать ?access_token= из redirect ✅webclient/src/app/main.js — удалить извлечение access_token из window.location.search ✅webclient/src/api/auth.js — оставить localStorage как fallback ✅access_token.api/http.js не добавляет Authorization: Bearer если нет token в localStorage (cookie работает сама).expires_at и status='active' при Bearer resolution ✅server/SHServ/Integrations/GAuth/AuthControllerTrait.php:
resolve_user_by_bearer() — добавлена проверка expires_at ✅load_user_by_id() — добавлена проверка status === 'active' ✅status != 'active') пользователя возвращает 401.authStore.init() ✅init().webclient/src/stores/auth.js — init() кэшируется через initPromise, повторные вызовы — no-op ✅webclient/src/router/index.js — guard теперь async и делает await authStore.init() перед решением ✅/#/devices не показывает login при валидной сессии.return_to + очистка 401-редиректа ✅return_to + цикл OAuth, если return_to содержит старый access_token.server/SHServ/Controllers/AuthController.php — добавлен sanitizeReturnTo() для login() и callback(), разрешает только same-origin относительные пути или абсолютные URL текущего хоста ✅webclient/src/api/client.js — убран hasTokenInUrl workaround, 401 редиректит на login чисто ✅return_to=//evil.com → редирект на /.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 по access_token в 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 — setTimeout на обновление за 60 секунд до expiration, с cancel при logout ✅main.js и LoginPage.vue оба пытались обработать callback → лишний me.webclient/src/features/auth/pages/LoginPage.vue — очищен, не содержит логики OAuth callback ✅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() для nginx ✅server/SHServ/Controllers/WebhookController.php — заменить getallheaders() на fallback из $_SERVER ✅server/SHServ/Integrations/GAuth/RateLimiter.php — file-based sliding-window rate limiter ✅server/SHServ/Controllers/AuthController.php — checkAuthRateLimit() вызывается в login() и callback() ✅/auth/login или /auth/callback в минуту с одного IP → 429.PermissionResolver делает 3 запроса к БД на каждый API call.server/SHServ/Integrations/GAuth/PermissionResolver.php — static cache на время запроса ✅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 run build).Phase 1 auth security hotfixes: cookie-based session, bearer checks, router guard sync.git pull.npm run build).git pull.npm run build).git pull.