feat: Guest WiFi, network intelligence, firmware orchestration, platform automation, api-ssl (v0.16.3-beta)
Guest WiFi (Hotspot): one-click captive-portal wizard that creates the guest SSID (virtual AP per radio), VLAN/bridge segregation + L3 VLAN interface, pool, DHCP, portal, rate-limited guest profile, and masquerade; vouchers with limits + printable sheets; live guest sessions; walled garden. Network intelligence: per-client device fingerprinting (vendor/hostname → category) with a persisted per-client override on the detail page; rogue/neighbor AP detection from stored AP scans (evil-twin flagging); Mist-style anomaly insights (client-count/CPU vs same-hour 14-day baseline + error bursts) in the Operations engine. Firmware orchestration: staged wave-based RouterOS rollouts (canary first) with pre-upgrade backup, reboot-ride-out, version verification, halt-on-failure, and scheduling; live per-device progress + cancel. Platform & automation: scoped API tokens (mtm_, read/write, hashed at rest, Bearer auth in requireAuth); outbound webhooks (12 events, HMAC-signed) fired from the alert pipeline + firmware orchestrator; scheduled email reports. api-ssl: RouterOSClient negotiates TLS on port 8729 and accepts RouterOS's default self-signed certs (rejectUnauthorized:false), so SSL-secured API devices connect without manual cert trust. Fixes #57. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
efb430d934
commit
9dfb8a5333
@@ -1,10 +1,14 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { createHash } from 'crypto';
|
||||
import { query } from '../config/database';
|
||||
|
||||
export interface AuthPayload {
|
||||
userId: number;
|
||||
username: string;
|
||||
role: string;
|
||||
/** Set when the request authenticated with an API token instead of a session. */
|
||||
tokenAuth?: boolean;
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -26,6 +30,27 @@ export function verifyToken(token: string): AuthPayload {
|
||||
return jwt.verify(token, JWT_SECRET) as AuthPayload;
|
||||
}
|
||||
|
||||
// API tokens ("mtm_…") are hashed at rest; scope maps onto the existing role
|
||||
// model (read → viewer, write → operator) so requireWrite/requireAdmin keep
|
||||
// working unchanged. Admin actions always need a real session.
|
||||
async function authenticateApiToken(token: string): Promise<AuthPayload | null> {
|
||||
const hash = createHash('sha256').update(token).digest('hex');
|
||||
const rows = await query<{ id: number; name: string; scope: string; expires_at: string | null }>(
|
||||
`SELECT id, name, scope, expires_at FROM api_tokens WHERE token_hash = $1`, [hash]
|
||||
).catch(() => []);
|
||||
const t = rows[0];
|
||||
if (!t) return null;
|
||||
if (t.expires_at && new Date(t.expires_at).getTime() < Date.now()) return null;
|
||||
// Fire-and-forget usage tracking
|
||||
void query(`UPDATE api_tokens SET last_used_at = NOW() WHERE id = $1`, [t.id]).catch(() => {});
|
||||
return {
|
||||
userId: -t.id,
|
||||
username: `token:${t.name}`,
|
||||
role: t.scope === 'write' ? 'operator' : 'viewer',
|
||||
tokenAuth: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
@@ -34,6 +59,18 @@ export function requireAuth(req: Request, res: Response, next: NextFunction): vo
|
||||
}
|
||||
|
||||
const token = authHeader.slice(7);
|
||||
|
||||
if (token.startsWith('mtm_')) {
|
||||
authenticateApiToken(token)
|
||||
.then((payload) => {
|
||||
if (!payload) { res.status(401).json({ error: 'Invalid or expired API token' }); return; }
|
||||
req.user = payload;
|
||||
next();
|
||||
})
|
||||
.catch(() => res.status(401).json({ error: 'Invalid or expired API token' }));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
req.user = verifyToken(token);
|
||||
next();
|
||||
|
||||
Reference in New Issue
Block a user