Fix dashboard runtime deploy dependencies
All checks were successful
deploy / deploy (push) Successful in 22s

Proof: Include operator dashboard auth module and runtime package manifest updates required for the deployed operator-dashboard and ops-sentinel processes to start successfully.

Assumptions: React and ws belong in production dependencies because operator-dashboard now ships in the runtime image; operator-dashboard-auth is part of the deployed backend surface.

Still fake: External alert receiver remains unconfigured; this commit only fixes rollout blockers for the runtime-health/dashboard deploy.
This commit is contained in:
philipp 2026-04-08 19:36:45 +02:00
parent 0b7e5e2e6c
commit b8d731408e
3 changed files with 1989 additions and 6 deletions

1833
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,12 +12,23 @@
"ops:sentinel": "node src/apps/ops-sentinel.mjs",
"strategy:engine": "node src/apps/strategy-engine.mjs",
"trade:executor": "node src/apps/trade-executor.mjs",
"operator:dashboard": "node src/apps/operator-dashboard.mjs",
"operator-dashboard:build": "vite build --config vite.operator-dashboard.config.mjs",
"operator-dashboard:dev": "bash scripts/dev/operator-dashboard-dev.sh",
"operator-dashboard:forward": "bash scripts/dev/operator-dashboard-forward.sh",
"start": "node index.mjs",
"test": "node --test"
},
"dependencies": {
"kafkajs": "^2.2.4",
"near-api-js": "^7.2.0",
"pg": "^8.20.0"
"pg": "^8.20.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"ws": "^8.20.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^5.1.0",
"vite": "^7.1.12"
}
}

View file

@ -0,0 +1,149 @@
import crypto from 'node:crypto';
const DEFAULT_SESSION_COOKIE_NAME = 'operator_dashboard_session';
export function resolveDashboardRequestAuth({
mode = 'stub',
authorizationHeader = '',
cookieHeader = '',
username = 'admin',
password = '',
sessionCookieName = DEFAULT_SESSION_COOKIE_NAME,
} = {}) {
if (mode === 'stub') {
return {
authenticated: true,
subject: 'local-operator',
mode,
roles: ['operator'],
via: 'stub',
setSessionCookie: false,
sessionCookieName,
sessionToken: buildDashboardSessionToken({ username, password }),
};
}
if (mode !== 'basic') {
return {
authenticated: false,
subject: null,
mode,
roles: [],
via: 'unsupported',
failure_reason: 'unsupported_auth_mode',
setSessionCookie: false,
sessionCookieName,
sessionToken: buildDashboardSessionToken({ username, password }),
};
}
const sessionToken = buildDashboardSessionToken({ username, password });
const cookies = parseCookieHeader(cookieHeader);
if (cookies[sessionCookieName] && cookies[sessionCookieName] === sessionToken) {
return {
authenticated: true,
subject: username,
mode,
roles: ['operator'],
via: 'session_cookie',
setSessionCookie: false,
sessionCookieName,
sessionToken,
};
}
const basic = parseBasicAuthorizationHeader(authorizationHeader);
if (!basic) {
return {
authenticated: false,
subject: null,
mode,
roles: [],
via: 'missing',
failure_reason: 'missing_basic_auth',
setSessionCookie: false,
sessionCookieName,
sessionToken,
};
}
if (basic.username !== username || basic.password !== password) {
return {
authenticated: false,
subject: null,
mode,
roles: [],
via: 'basic_auth',
failure_reason: 'invalid_credentials',
setSessionCookie: false,
sessionCookieName,
sessionToken,
};
}
return {
authenticated: true,
subject: username,
mode,
roles: ['operator'],
via: 'basic_auth',
setSessionCookie: cookies[sessionCookieName] !== sessionToken,
sessionCookieName,
sessionToken,
};
}
export function buildDashboardSessionToken({ username = '', password = '' } = {}) {
return crypto
.createHash('sha256')
.update(`${username}:${password}`)
.digest('hex');
}
export function buildDashboardSessionCookie({
sessionCookieName = DEFAULT_SESSION_COOKIE_NAME,
sessionToken,
} = {}) {
return `${sessionCookieName}=${sessionToken}; Path=/; HttpOnly; SameSite=Lax`;
}
export function buildDashboardAuthChallengeHeader({ realm = 'unrip operator dashboard' } = {}) {
return `Basic realm="${realm}"`;
}
export function parseBasicAuthorizationHeader(value = '') {
if (!value || typeof value !== 'string') return null;
const [scheme, token] = value.split(/\s+/, 2);
if (!scheme || !token || scheme.toLowerCase() !== 'basic') return null;
try {
const decoded = Buffer.from(token, 'base64').toString('utf8');
const separatorIndex = decoded.indexOf(':');
if (separatorIndex === -1) return null;
return {
username: decoded.slice(0, separatorIndex),
password: decoded.slice(separatorIndex + 1),
};
} catch {
return null;
}
}
function parseCookieHeader(value = '') {
if (!value || typeof value !== 'string') return {};
return Object.fromEntries(
value
.split(';')
.map((part) => part.trim())
.filter(Boolean)
.map((part) => {
const separatorIndex = part.indexOf('=');
if (separatorIndex === -1) return [part, ''];
return [
part.slice(0, separatorIndex).trim(),
decodeURIComponent(part.slice(separatorIndex + 1).trim()),
];
}),
);
}