Files
vue-pult/server/AUTH_LOGOUT_DOCUMENTATION.md
sasha 3e90269b0b Initial commit: Vue.js тир управления система
- Клиентская часть Vue 3 + Vite
- Серверная часть Node.js + WebSocket
- Система авторизации и смен
- Управление игровыми портами
- Поддержка тем (светлая/темная)
- Адаптивный дизайн

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-19 12:24:22 +03:00

14 KiB
Raw Blame History

Документация по системе авторизации и выхода пользователя

Обзор системы авторизации

Система использует двухуровневую авторизацию:

  1. Локальная авторизация - через WebSocket с пультом
  2. Серверная авторизация - через внешний сервер ws.tirpriz.ru

Процесс авторизации пользователя

1. Клиентская часть (Frontend)

Инициализация авторизации:

// 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)

Обработка запроса авторизации:

// 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. Клиентская часть

Инициация выхода:

// www/layout/menu/menu.jsc
<div class="logout" onclick="socket.send(JSON.stringify({do:'logout'}));"></div>

// www/page/run/run.jsc
<div onmousedown="socket.send(JSON.stringify({do:'logout'})); DIVconfirm.blur();">
    <txt "Да">
</div>

Обработка ответа выхода:

// www/start.jsc:166-170
case "logout":
    avt = {};
    delete sessionStorage.avt;
    location.reload();

2. Серверная часть

Обработка запроса выхода:

// 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. Подключение к серверу

// ws-toserver.js:11
global.socket_to_server = new WebSocket("ws://ws.tirpriz.ru/ws");

2. Авторизация на сервере

// 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. Синхронизация данных

// 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. Генерация токена

// При успешной авторизации по паролю генерируется токен
hash[postData.tel] = genHash(15); 
user.hash = hash[postData.tel];
await fs.writeFile('./data/hash.ini', JSON.stringify(hash));

2. Валидация токена

// Проверка токена при последующих запросах
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. Проверка авторизации

// ws.js:41
if (!wsClient.cfg.avt && postData.do != 'avt') {
    // Блокируем доступ неавторизованным пользователям
    return;
}

2. Типы ролей

  • Техник - доступ к технической смене
  • Оператор - доступ к обычной смене
  • Админ - полный доступ

Миграция на React

1. Рекомендуемая архитектура

Redux Store для авторизации:

// 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 хук для авторизации:

// 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 };
};

Компонент авторизации:

// 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 (
        <form onSubmit={handleSubmit}>
            <input 
                type="tel" 
                value={tel} 
                onChange={(e) => setTel(e.target.value)}
                placeholder="Телефон"
                required 
            />
            <input 
                type="password" 
                value={password} 
                onChange={(e) => setPassword(e.target.value)}
                placeholder="Пароль"
                required 
            />
            <button type="submit" disabled={loading}>
                {loading ? 'Вход...' : 'Войти'}
            </button>
            {error && <div className="error">{error}</div>}
        </form>
    );
};

Защищенные маршруты:

// components/ProtectedRoute.jsx
const ProtectedRoute = ({ children, requiredRole }) => {
    const { isAuthenticated, user } = useAuth();
    
    if (!isAuthenticated) {
        return <Navigate to="/login" replace />;
    }
    
    if (requiredRole && !hasAccess(user, requiredRole)) {
        return <div>Недостаточно прав доступа</div>;
    }
    
    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:

// 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 (
        <WebSocketContext.Provider value={{ socket, connected, sendMessage }}>
            {children}
        </WebSocketContext.Provider>
    );
};

Ключевые особенности системы

  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 - токены сессий