# Документация по системе авторизации и выхода пользователя
## Обзор системы авторизации
Система использует двухуровневую авторизацию:
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 (
);
};
```
#### Защищенные маршруты:
```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` - токены сессий