/
threed
/
claud-web
Обзор
Документация
Войти
/
threed
/
claud-web
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
lib/auth.js
87 строк
3 KB
Alexander Zelenski
Уведомления о завершении работы Claude, стабильный вход и PWA
04 авг 2026, 14:26
04 авг 2026, 14:26
b6ac18e
Код
Авторство
О чём код?
/** * Аутентификация: подписанная cookie-сессия, Basic Auth и токены для Socket.IO. * * Порядок проверки — cookie, затем Basic. Basic нужен для первого входа: * после него выдаётся долгоживущая cookie (см. lib/session.js), поэтому * системный диалог логина не всплывает даже там, где Safari забыл креды. * * Токены остались фолбэком для Socket.IO на случай, если cookie недоступна: * клиент берёт токен обычным REST и передаёт его в auth-поле handshake. */ const crypto = require('crypto'); const session = require('./session'); const TOKEN_TTL_MS = 5 * 60 * 1000; const tokens = new Map(); // token -> срок действия function checkCredentials(username, password) { return username === process.env.AUTH_USERNAME && password === process.env.AUTH_PASSWORD; } function parseBasicHeader(header) { if (!header || !header.startsWith('Basic ')) return null; const decoded = Buffer.from(header.slice(6), 'base64').toString('utf8'); const idx = decoded.indexOf(':'); if (idx === -1) return null; return { username: decoded.slice(0, idx), password: decoded.slice(idx + 1) }; } /** Express-middleware: cookie-сессия, иначе Basic Auth (и выдача cookie). */ function basicAuth(req, res, next) { const cookies = session.parseCookies(req.headers.cookie); const current = session.verify(cookies[session.COOKIE_NAME]); if (current.valid) { // Скользящее окно: продлеваем cookie, пока пользователь активен if (current.stale) session.setCookie(req, res); return next(); } const creds = parseBasicHeader(req.headers.authorization); if (creds && checkCredentials(creds.username, creds.password)) { session.setCookie(req, res); return next(); } res.setHeader('WWW-Authenticate', 'Basic realm="Claud Web"'); res.status(401).json({ error: 'Authentication required' }); } function issueToken() { const token = crypto.randomBytes(24).toString('hex'); tokens.set(token, Date.now() + TOKEN_TTL_MS); return token; } function verifyToken(token) { if (typeof token !== 'string') return false; const expires = tokens.get(token); if (!expires) return false; if (Date.now() > expires) { tokens.delete(token); return false; } return true; } /** * Middleware для io.use(). Порядок: cookie сессии (браузер шлёт её и на * handshake — самый надёжный путь), затем токен, затем Basic-заголовок. */ function socketAuth(socket, next) { const cookies = session.parseCookies(socket.handshake.headers.cookie); if (session.verify(cookies[session.COOKIE_NAME]).valid) return next(); if (verifyToken(socket.handshake.auth?.token)) return next(); const creds = parseBasicHeader(socket.handshake.headers.authorization); if (creds && checkCredentials(creds.username, creds.password)) return next(); next(new Error('Authentication required')); } setInterval(() => { const now = Date.now(); for (const [token, expires] of tokens) { if (now > expires) tokens.delete(token); } }, 60 * 1000).unref(); module.exports = { basicAuth, socketAuth, issueToken };