mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-25 09:46:47 +00:00
feat: graduate Host Console to Community admins (#1669)
* feat: graduate Host Console to Community admins Make Host Console available to Community and Admiral admins (system:console), add host-console-community for mixed fleets, and keep opaque API tokens off the host shell. * docs: document Host Console deep links Cover root and stack-scoped Console URLs, correct the phone treatment note, and pin parse/build round-trips in senchoRoute tests. * fix: bind Host Console socket to the resolved node Treat unresolved activeNode as loading, target the WebSocket with an explicit nodeId, and wait for stack deep-link hydration so the shell cannot open on the wrong node or compose root. Add regression coverage for node/stack retargeting and fail-closed directory resolution. * fix: harden Host Console node binding, audit acting_as, and console_session tokens Reject unknown or malformed nodeIds before spawning a PTY. Record hub operators in audit_log.acting_as for remote console_session bridges. Path-scope and one-time-consume console_session JWTs so Host Console mints cannot open container exec or be replayed. * test: expect acting_as in audit CSV export header Align the CSV export assertion with the P0-2B acting_as column added to audit log exports.
This commit is contained in:
@@ -103,7 +103,7 @@ auditLogRouter.get('/export', async (req: Request, res: Response): Promise<void>
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="audit-log-${timestamp}.csv"`);
|
||||
|
||||
const headers = ['id', 'timestamp', 'username', 'method', 'path', 'status_code', 'node_id', 'ip_address', 'summary'];
|
||||
const headers = ['id', 'timestamp', 'username', 'acting_as', 'method', 'path', 'status_code', 'node_id', 'ip_address', 'summary'];
|
||||
const rows = result.entries.map(e =>
|
||||
headers.map(h => escapeCsvField(e[h as keyof typeof e])).join(','),
|
||||
);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Router, type Request, type Response } from 'express';
|
||||
import { authMiddleware } from '../middleware/auth';
|
||||
import { requireAdmin, requirePaid } from '../middleware/tierGates';
|
||||
import { requireAdmin } from '../middleware/tierGates';
|
||||
import { rejectApiTokenScope } from '../middleware/apiTokenScope';
|
||||
import { mintConsoleSession } from '../helpers/consoleSession';
|
||||
import {
|
||||
isConsoleSessionPath,
|
||||
mintConsoleSession,
|
||||
sanitizeActingAs,
|
||||
} from '../helpers/consoleSession';
|
||||
|
||||
/**
|
||||
* Mint a short-lived `console_session` JWT. Used by the gateway when it
|
||||
@@ -11,15 +15,28 @@ import { mintConsoleSession } from '../helpers/consoleSession';
|
||||
* remote's upgrade handler on interactive paths, so the gateway authenticates
|
||||
* with the long-lived token, asks for this short-lived one, then forwards
|
||||
* the WS upgrade using it.
|
||||
*
|
||||
* Body:
|
||||
* - `path` (required): `host-console` | `container-exec`
|
||||
* - `acting_as` (optional): hub operator for remote audit. On node_proxy mints
|
||||
* the body value is used (sanitized). On browser admin mints the signed-in
|
||||
* username is stamped so a Bearer console_session cannot erase attribution.
|
||||
*/
|
||||
export const consoleRouter = Router();
|
||||
|
||||
consoleRouter.post('/console-token', authMiddleware, (req: Request, res: Response): void => {
|
||||
if (rejectApiTokenScope(req, res, 'API tokens cannot generate console tokens.')) return;
|
||||
if (!requireAdmin(req, res)) return;
|
||||
if (!requirePaid(req, res)) return;
|
||||
try {
|
||||
res.json({ token: mintConsoleSession() });
|
||||
const path = req.body?.path;
|
||||
if (!isConsoleSessionPath(path)) {
|
||||
res.status(400).json({ error: 'Invalid or missing console path' });
|
||||
return;
|
||||
}
|
||||
const actingAs = req.machineAuthScope === 'node_proxy'
|
||||
? sanitizeActingAs(req.body?.acting_as)
|
||||
: sanitizeActingAs(req.user?.username);
|
||||
res.json({ token: mintConsoleSession({ path, actingAs }) });
|
||||
} catch (error) {
|
||||
console.error('Failed to issue console token:', error);
|
||||
res.status(500).json({ error: 'Failed to issue console token' });
|
||||
|
||||
Reference in New Issue
Block a user