import React, { useMemo } from 'react' import Head from 'next/head' import { useSelector } from 'react-redux' import { Store } from 'redux' import { Container } from '@mui/material' import { wrapper } from '~/store' import { IRootState } from '~/store/IRootState' import { NextPageContext } from 'next' import { Autopark2022 } from '~/components/Autopark2022' import { OneTimeLoginFormBtn } from '~/components/Autopark2022/components/OneTimeLoginFormBtn' import { CreateNewProject } from '~/components/Autopark2022/components/ProjectList/components' import { setUserCheckerResponse, setIsOneTimePasswordCorrect } from '~/store/reducers/autopark' import { autoparkHttpClient, EAPIUserCode } from '~/utils/autoparkHttpClient' import { getInitialPropsBase, setCommonStore } from '~/utils/next' import { UniversalContainer } from '~/components/special-svg-content/UniversalContainer' import { AuthorizationRequired401Svg } from '~/components/special-svg-content/error/AuthorizationRequired401Svg' import { Layout } from '~/components/Layout/Layout' import { NoInternetConnectionSvg } from '~/components/special-svg-content/error/NoInternetConnectionSvg' const isDev = process.env.NODE_ENV === 'development' const baseURL = isDev ? 'http://localhost:5000/pravosleva-bot-2021/autopark-2022' : 'http://pravosleva.pro/express-helper/pravosleva-bot-2021/autopark-2022' type TPageService = { isOk: boolean; message?: string; hasAuthenticated: boolean; } interface IMyProjectsProps { userCheckerResponse: any; errorMsg: string | null; chat_id: string; _pageService: TPageService; statusCode?: number; isOffline: boolean; } export default function MyProjects({ userCheckerResponse, errorMsg, chat_id, _pageService, statusCode, isOffline, }: IMyProjectsProps) { const isBrowser = useMemo(() => typeof window !== 'undefined', []) const isOneTimePasswordCorrect = useSelector((state: IRootState) => state.autopark.isOneTimePasswordCorrect) // ========================================================================= // 🚨 ЖЕСТКИЙ БАРЬЕР ПРИВАТНОСТИ ДЛЯ ИНКОГНИТО (АНТИ-ОБХОД) // ========================================================================= // Если сервер или клиент выдали статус редиректа 302, либо авторизация ложна, // принудительно гасим рендер, пресекая Ghost-рендеринг приватных элементов! if (statusCode === 302 || !_pageService?.hasAuthenticated) { return (
Перенаправление на страницу авторизации...
) } // ========================================================================= // 📡 НОВОЕ: ВЕРХНИЙ ИЗОЛИРОВАННЫЙ ОФФЛАЙН-РУБЕЖ (Эффект No Internet) // ========================================================================= if (isOffline) { return (
{JSON.stringify({ _pageService }, null, 2)}
) } // ========================================================================= // 🛡️ ЗАЩИТНЫЙ БАРЬЕР UI-СЛОЯ ОШИБОК И АВТОРИЗАЦИИ (Стандартизация под SVG 401) // ========================================================================= // КЕЙС A: Пользователь не найден в базе данных бота Autopark if (userCheckerResponse?.code === 'not_found' || statusCode === 401) { return ( <>
{JSON.stringify({ _pageService }, null, 2)}
) } // КЕЙС Б: Системная ошибка рантайма / падение базы данных if (errorMsg || !_pageService?.isOk) { return (
{JSON.stringify({ _pageService }, null, 2)}
) } return ( <> AutoPark | Панель управления {/* Жёсткий запрет индексации приватной панели роботами */}
{isBrowser && (
{isOneTimePasswordCorrect ? ( ) : ( )}
)}
) } MyProjects.getInitialProps = wrapper.getInitialPageProps( (store: Store) => async (ctx: NextPageContext): Promise => { const { query } = ctx // Проверяем, что пришла строка и она преобразуется в валидное число const isQueryValidNumber = typeof query?.tg_chat_id === 'string' && !Number.isNaN(Number(query.tg_chat_id)) // Для логики и сетевых запросов используем чистую строку, если она валидна const chat_id = isQueryValidNumber ? String(query?.tg_chat_id) : undefined let errorMsg: string | null = null let statusCode = 200 let result = null let isOffline = false // Базовый рубеж защиты: если chat_id невалиден — сразу выкидываем ошибку 400 if (!chat_id) { if (ctx.res) ctx.res.statusCode = 400 return { userCheckerResponse: { ok: false }, errorMsg: null, isUserExists: false, chat_id: String(query?.tg_chat_id || ''), _pageService: { isOk: false, hasAuthenticated: false, message: `Неверный параметр идентификатора (ожидалось число), получено: \`${query?.tg_chat_id}\`` }, statusCode: 400, isOffline: false } } let baseProps = null try { // Собираем базовые пропсы авторизации и сессии движка baseProps = await getInitialPropsBase(ctx) } catch (baseErr: any) { // Если упал метод ядра — сеть полностью отсутствует console.error('🚨 [getInitialProps]: Сбой ядра из-за отсутствия сети.', baseErr) isOffline = true } // КЛИЕНТСКИЙ + СЕРВЕРНЫЙ МОСТ АВТОРИЗАЦИИ (ИДЕМПОТЕНТНОСТЬ) let isAuthorized = baseProps?.authData?.oneTime?.jwt?.isAuthorized === true // TODO [SECURITY]: 2/2 Текущая проверка через .includes() является поверхностной // и уязвимой для подделки (XSS / манипуляции в консоли). // Необходима доработка на строгий парсинг токена или вызов валидатора. if (typeof window !== 'undefined' && !isAuthorized) { // 🔥 КРИТИЧЕСКИЙ ФИКС: Синхронизируем имя куки 'autopark-2022.jwt' с кодом карточки машины! const hasAuthCookie = document.cookie.includes('token') || document.cookie.includes('jwt') || document.cookie.includes('autopark-2022.jwt') || document.cookie.includes('session'); if (hasAuthCookie) isAuthorized = true } const reduxState = store.getState() as IRootState if (reduxState.autopark.isOneTimePasswordCorrect === true) { isAuthorized = true } // РЕДИРЕКТ НА СТРАНИЦУ ВХОДА (Если сети нет — редирект блокируется, уступая место оффлайн экрану) if (!isAuthorized && !isOffline) { const currentPath = ctx.req ? ctx.req.url : window.location.pathname + window.location.search const backUrl = typeof query.from === 'string' ? query.from : currentPath const successUrl = typeof query.to === 'string' ? query.to : currentPath const redirectTarget = [ '/auth/login', '?', [ !!backUrl ? `from=${encodeURIComponent(backUrl)}` : '', (!!backUrl && !successUrl) ? 'back_is_locked_when_unlogged=1' : '', !!successUrl ? `to=${encodeURIComponent(successUrl)}` : '', `chat_id=${chat_id}` ].join('&') ].join('') if (ctx.res) { ctx.res.writeHead(302, { Location: redirectTarget }) ctx.res.end() } else { const Router = require('next/router').default Router.push(redirectTarget) } return { userCheckerResponse: { ok: false }, projectDataResponse: null, errorMsg: 'Redirecting to login...', chat_id, // project_id, _pageService: { isOk: false, hasAuthenticated: false }, statusCode: 302, isOffline: false } } // БЕЗОПАСНЫЙ СБОР ДАННЫХ И ПЕРЕХВАТ ИСКЛЮЧЕНИЙ СЕТИ (TRY / CATCH) if (!isOffline) { try { // 1. Делаем запрос к строго типизированному API-клиенту result = await autoparkHttpClient.getUserData({ tg: { chat_id: Number(chat_id), } }) // 2. ИСПРАВЛЕНО: Проверяем маркер сетевого сбоя, который сгенерировал сам клиент if (result?.code === EAPIUserCode.ServerError) { throw new Error(result.message || 'Network Error') } // Синхронизируем ответ с Redux-хранилищем if (result?.ok === true || result?.ok === false) { store.dispatch(setUserCheckerResponse(result)) } // Если пользователя физически нет в базе данных Telegram-бота if (result?.code === EAPIUserCode.NotFound) { if (ctx.res) ctx.res.statusCode = 401 statusCode = 401 } } catch (netError: any) { // Ловим любые падения сокетов, тайм-ауты или ENOTFOUND console.error('🚨 [getInitialProps]: Ошибка интернет-соединения при запросе профиля.', netError) isOffline = true statusCode = 503 // Статус Service Unavailable для поисковых ботов } } // 2. Логика быстрой проверки JWT-токенов одноразового пароля const _pageService: TPageService = { isOk: !errorMsg && statusCode === 200 && !isOffline, hasAuthenticated: isAuthorized, } // Блок switch(true) выполняется строго если мы в сети if (!isOffline && baseProps) { switch (true) { case baseProps.authData.oneTime.jwt.isAuthorized: _pageService.hasAuthenticated = true store.dispatch(setIsOneTimePasswordCorrect(true)) break case baseProps.authData.oneTime.jwt._service.isErrored: _pageService.hasAuthenticated = false _pageService.message = baseProps.authData.oneTime.jwt._service.message || 'Ошибка авторизации сессии (JWT Errored)' if (ctx.res) ctx.res.statusCode = 401 statusCode = 401 break default: break } } if (baseProps) { setCommonStore({ store, baseProps }) } return { userCheckerResponse: result || { ok: false }, errorMsg, isUserExists: result ? result.ok : false, chat_id, _pageService, statusCode, isOffline, } } )