Files
egorin/server/src/auth.js
T
GermanandClaude Opus 4.8 b1c52ab748 Железный Егорин — форк Орбиты: тёплый стиль (amber), логин с odyssey WebGL-молнией справа, энергомониторинг+панель
Ребренд Мониторинг Орбита -> Железный Егорин; accent green->amber #F59E0B; LoginScreen правая половина = Lightning (WebGL, hue 35 тёплый) под стеклянной формой; левая панель — тёплый градиент.
Доступ агентам: репо публичный, push german-токеном.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 23:57:16 +03:00

90 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// RBAC-зеркало фронта + простой HMAC-токен (без внешних зависимостей).
import crypto from 'node:crypto';
import { config } from './config.js';
export const USERS = [
{
id: 'admin',
name: 'Администратор',
role: 'Полный доступ',
admin: true,
access: {
energy: { caps: ['view', 'edit', 'reports'], objects: 'all' },
smart: { caps: ['view', 'edit', 'reports'], objects: 'all' },
gps: { caps: ['view', 'edit', 'reports'], objects: 'all' },
netinfra: { caps: ['view', 'edit', 'reports'], objects: 'all' },
security: { caps: ['view', 'edit', 'reports'], objects: 'all' },
},
},
{ id: 'pcn-operator', name: 'Дежурный ПЦН', role: 'Оператор пульта', access: { security: { caps: ['view', 'edit', 'reports'], objects: 'all' } } },
{ id: 'energetik-ro', name: 'Сидоров А.', role: 'Энергетик · просмотр', access: { energy: { caps: ['view'], objects: ['cab-07'] } } },
{ id: 'energetik-rw', name: 'Петров В.', role: 'Энергетик · правка', access: { energy: { caps: ['view', 'edit', 'reports'], objects: 'all' } } },
{ id: 'logist', name: 'Кузнецова М.', role: 'Логист транспорта', access: { gps: { caps: ['view', 'edit', 'reports'], objects: 'all' } } },
{ id: 'netadmin', name: 'Орлов Д.', role: 'Сетевой инженер', access: { netinfra: { caps: ['view', 'edit', 'reports'], objects: 'all' } } },
{
id: 'demo',
name: 'Демо-доступ',
role: 'Только просмотр',
access: {
energy: { caps: ['view'], objects: 'all' },
smart: { caps: ['view'], objects: 'all' },
gps: { caps: ['view'], objects: 'all' },
netinfra: { caps: ['view'], objects: 'all' },
security: { caps: ['view'], objects: 'all' },
},
},
];
export const TYPE_SECTION = { transport: 'gps', energy: 'energy', realty: 'smart', network: 'netinfra' };
const TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000; // срок жизни сессии — 30 дней
const STATE_TTL_MS = 10 * 60 * 1000; // OAuth state (CSRF) — 10 минут
export function sign(user) {
const body = Buffer.from(JSON.stringify({ id: user.id, t: Date.now() })).toString('base64url');
const sig = crypto.createHmac('sha256', config.jwtSecret).update(body).digest('base64url');
return `${body}.${sig}`;
}
// Возвращает id пользователя из токена (сам пользователь резолвится в tenants). null если подпись/срок невалидны.
export function verifyId(token) {
if (!token) return null;
const [body, sig] = token.split('.');
if (!body || !sig) return null;
const exp = crypto.createHmac('sha256', config.jwtSecret).update(body).digest('base64url');
if (exp !== sig) return null;
try {
const p = JSON.parse(Buffer.from(body, 'base64url').toString());
if (!p.t || Date.now() - p.t > TOKEN_TTL_MS) return null; // сессия просрочена
return p.id || null;
} catch {
return null;
}
}
// CSRF-state для OAuth: подписанный короткоживущий токен (stateless, без хранилища сессий)
export function signState() {
const body = Buffer.from(JSON.stringify({ n: crypto.randomBytes(9).toString('hex'), t: Date.now() })).toString('base64url');
const sig = crypto.createHmac('sha256', config.jwtSecret).update('state:' + body).digest('base64url');
return `${body}.${sig}`;
}
export function verifyState(state) {
if (!state) return false;
const [body, sig] = String(state).split('.');
if (!body || !sig) return false;
const exp = crypto.createHmac('sha256', config.jwtSecret).update('state:' + body).digest('base64url');
if (exp !== sig) return false;
try {
const p = JSON.parse(Buffer.from(body, 'base64url').toString());
return !!p.t && Date.now() - p.t <= STATE_TTL_MS;
} catch {
return false;
}
}
export const can = (user, section, cap) => !!user?.access?.[section]?.caps?.includes(cap);
export function isObjectAllowed(user, section, id) {
const o = user?.access?.[section]?.objects;
return o === 'all' || (Array.isArray(o) && o.includes(id));
}