# Документация по системе авторизации и выхода пользователя ## Обзор системы авторизации Система использует двухуровневую авторизацию: 1. **Локальная авторизация** - через WebSocket с пультом 2. **Серверная авторизация** - через внешний сервер ws.tirpriz.ru --- ## Процесс авторизации пользователя ### 1. Клиентская часть (Frontend) #### Инициализация авторизации: ```javascript // www/start.jsc case "avt": // Если авторизация успешна if (postData.user != false) { avt = postData.user; sessionStorage.avt = JSON.stringify(avt); showPage(page.run, true); alert.add && alert.add({content: "Добро пожаловать "+(avt.fio ? avt.fio : avt.tel)+"!"}); } else { showPage(page.avt, false); alert.add && alert.add({content: "Неверный логин или пароль!"}); } ``` ### 2. Серверная часть (Backend) #### Обработка запроса авторизации: ```javascript // ws.js:380-410 case "avt": let user = false; // Авторизация по паролю if (postData.pass && postData.tel in avt && avt[postData.tel].pass == postData.pass) { user = JSON.parse(JSON.stringify(avt[postData.tel])); delete user.pass; hash[postData.tel] = genHash(15); user.hash = hash[postData.tel]; await fs.writeFile('./data/hash.ini', JSON.stringify(hash)); } // Авторизация по токену (hash) if (postData.hash && postData.tel in hash && postData.tel in avt && hash[postData.tel] == postData.hash) { user = JSON.parse(JSON.stringify(avt[postData.tel])); user.hash = hash[postData.tel]; delete user.pass; } wsClient.cfg.avt = user; if (user != false) { // Отправляем данные пользователя wsClient.send(JSON.stringify({ do: "avt", user, synch_log: log.synch_log(), VER, games: game.getgame(), info: game.getInfo(), admins: avt, pult: game.cfg.pult, tir: game.cfg.tir, esp_ping: game.cfg.pingStatus })); // Регистрируем вход if (!(wsClient.cfg.avt._id in game.cfg.avt)) { log.save({do: "login", adminId: wsClient.cfg.avt._id}); game.cfg.avt[wsClient.cfg.avt._id] = true; } } ``` --- ## Процесс выхода из системы ### 1. Клиентская часть #### Инициация выхода: ```javascript // www/layout/menu/menu.jsc
// www/page/run/run.jsc
``` #### Обработка ответа выхода: ```javascript // www/start.jsc:166-170 case "logout": avt = {}; delete sessionStorage.avt; location.reload(); ``` ### 2. Серверная часть #### Обработка запроса выхода: ```javascript // ws.js:412-417 case "logout": wsClient.send(JSON.stringify({do: "logout"})); log.save({do: "logout", adminId: wsClient.cfg.avt._id}); wsClient.cfg.avt = false; delete game.cfg.avt[wsClient.cfg.avt._id]; ``` --- ## Взаимодействие с внешним сервером ### 1. Подключение к серверу ```javascript // ws-toserver.js:11 global.socket_to_server = new WebSocket("ws://ws.tirpriz.ru/ws"); ``` ### 2. Авторизация на сервере ```javascript // ws-toserver.js:23-49 socket_to_server.onopen = async (e) => { // Собираем информацию об администраторах let admins = []; wsServer.clients.forEach(client => { if (client?.cfg?.avt) admins.push({ adminId: client.cfg.avt.id, ip: client._socket.remoteAddress }); }); // Формируем данные авторизации const authData = { do: "avt", tip: "pult", serialcpu, admins, VER, network: os.networkInterfaces(), disk, freemem: os.freemem(), totalmem: os.totalmem() }; // Отправляем данные авторизации socket_to_server.send(JSON.stringify(authData)); }; ``` ### 3. Синхронизация данных ```javascript // ws-toserver.js:85-120 case "avt": conn_to_server = true; // Проверяем версии данных и обновляем если нужно if (postData.tir && postData.tir.access.ver != ver.admin) { let avt = await send_to_server({do: "admin/pult"}); if (avt.do = "admin/pult") { let avt_file = {}; for (let i = 0; i < avt.admins.length; i++) { avt_file[avt.admins[i].tel] = avt.admins[i]; } await fs.writeFile('./data/avt.ini', JSON.stringify(avt_file)); ver.admin = avt.ver; } } ``` --- ## Система токенов (hash) ### 1. Генерация токена ```javascript // При успешной авторизации по паролю генерируется токен hash[postData.tel] = genHash(15); user.hash = hash[postData.tel]; await fs.writeFile('./data/hash.ini', JSON.stringify(hash)); ``` ### 2. Валидация токена ```javascript // Проверка токена при последующих запросах if (postData.hash && postData.tel in hash && postData.tel in avt && hash[postData.tel] == postData.hash) { user = JSON.parse(JSON.stringify(avt[postData.tel])); user.hash = hash[postData.tel]; } ``` --- ## Роли и доступы ### 1. Проверка авторизации ```javascript // ws.js:41 if (!wsClient.cfg.avt && postData.do != 'avt') { // Блокируем доступ неавторизованным пользователям return; } ``` ### 2. Типы ролей - **Техник** - доступ к технической смене - **Оператор** - доступ к обычной смене - **Админ** - полный доступ --- ## Миграция на React ### 1. Рекомендуемая архитектура #### Redux Store для авторизации: ```javascript // store/authSlice.js const authSlice = createSlice({ name: 'auth', initialState: { user: null, isAuthenticated: false, token: null, loading: false, error: null }, reducers: { loginStart: (state) => { state.loading = true; state.error = null; }, loginSuccess: (state, action) => { state.user = action.payload.user; state.token = action.payload.hash; state.isAuthenticated = true; state.loading = false; // Сохраняем в sessionStorage sessionStorage.setItem('avt', JSON.stringify(action.payload.user)); }, loginFailure: (state, action) => { state.error = action.payload; state.loading = false; }, logout: (state) => { state.user = null; state.token = null; state.isAuthenticated = false; sessionStorage.removeItem('avt'); }, restoreAuth: (state, action) => { const savedAuth = sessionStorage.getItem('avt'); if (savedAuth) { state.user = JSON.parse(savedAuth); state.isAuthenticated = true; } } } }); ``` #### WebSocket хук для авторизации: ```javascript // hooks/useAuth.js export const useAuth = () => { const dispatch = useDispatch(); const { user, isAuthenticated, loading, error } = useSelector(state => state.auth); const login = async (tel, password) => { dispatch(loginStart()); try { const response = await sendMessage({ do: "avt", tel, pass: password }); if (response.user !== false) { dispatch(loginSuccess(response)); return { success: true }; } else { dispatch(loginFailure("Неверный логин или пароль")); return { success: false, error: "Неверный логин или пароль" }; } } catch (error) { dispatch(loginFailure(error.message)); return { success: false, error: error.message }; } }; const logout = () => { sendMessage({ do: "logout" }); dispatch(logout()); }; const restoreAuth = () => { dispatch(restoreAuth()); }; return { user, isAuthenticated, loading, error, login, logout, restoreAuth }; }; ``` #### Компонент авторизации: ```javascript // components/Auth/LoginForm.jsx const LoginForm = () => { const [tel, setTel] = useState(''); const [password, setPassword] = useState(''); const { login, loading, error } = useAuth(); const handleSubmit = async (e) => { e.preventDefault(); const result = await login(tel, password); if (result.success) { // Переход на главную страницу navigate('/dashboard'); } }; return (
setTel(e.target.value)} placeholder="Телефон" required /> setPassword(e.target.value)} placeholder="Пароль" required /> {error &&
{error}
}
); }; ``` #### Защищенные маршруты: ```javascript // components/ProtectedRoute.jsx const ProtectedRoute = ({ children, requiredRole }) => { const { isAuthenticated, user } = useAuth(); if (!isAuthenticated) { return ; } if (requiredRole && !hasAccess(user, requiredRole)) { return
Недостаточно прав доступа
; } return children; }; const hasAccess = (user, requiredRole) => { switch (requiredRole) { case 'tech': return user.groupz?.access?.tools === true; case 'operator': return user.groupz?.access?.smena === true; case 'admin': return user.groupz?.access?.admin === true; default: return true; } }; ``` ### 2. WebSocket интеграция #### WebSocket Context: ```javascript // context/WebSocketContext.js const WebSocketContext = createContext(); export const WebSocketProvider = ({ children }) => { const [socket, setSocket] = useState(null); const [connected, setConnected] = useState(false); const dispatch = useDispatch(); useEffect(() => { const ws = new WebSocket('ws://localhost:9000'); ws.onopen = () => { setConnected(true); setSocket(ws); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); switch (data.do) { case 'avt': if (data.user !== false) { dispatch(loginSuccess(data)); } else { dispatch(loginFailure("Авторизация не удалась")); } break; case 'logout': dispatch(logout()); break; // ... другие случаи } }; ws.onclose = () => { setConnected(false); setSocket(null); setTimeout(() => { // Переподключение }, 5000); }; return () => ws.close(); }, []); const sendMessage = (message) => { if (socket && connected) { socket.send(JSON.stringify(message)); } }; return ( {children} ); }; ``` --- ## Ключевые особенности системы 1. **Двойная авторизация** - локальная и серверная 2. **Токенная система** - hash токены сохраняются в файл 3. **Роли доступа** - техник, оператор, админ 4. **Автоматическая синхронизация** - данные пользователей обновляются с сервера 5. **Логирование** - все входы/выходы записываются в лог 6. **Восстановление сессии** - через sessionStorage 7. **Реконнект** - автоматическое переподключение к серверу ## Важные файлы для миграции - `ws.js` - основная логика авторизации - `ws-toserver.js` - взаимодействие с внешним сервером - `www/start.jsc` - клиентская обработка авторизации - `data_start/avt.ini` - пользователи системы - `data_start/hash.ini` - токены сессий