This commit is contained in:
+28
-1
@@ -25,7 +25,7 @@
|
||||
* the pending host-key token (token only returned on test / observation).
|
||||
*/
|
||||
|
||||
import { Router, type Request, type Response, type RequestHandler } from 'express';
|
||||
import { Router, type Request, type Response, type NextFunction, type RequestHandler } from 'express';
|
||||
import type Database from 'better-sqlite3';
|
||||
|
||||
import type { SshConnection, SshConnectionRepo, HostKeyVerifyResult } from '../ssh/connection-repo.js';
|
||||
@@ -78,6 +78,14 @@ export interface SshApiDeps {
|
||||
getUserId(req: Request): string | null;
|
||||
isAdmin(req: Request): boolean;
|
||||
getOrgIds(req: Request): string[];
|
||||
/**
|
||||
* Whether the bridge wired the auth subsystem. When `false` (no-auth
|
||||
* single-user deployment) requests carry no `req.user`, so the router
|
||||
* synthesizes a stable `local` owner — otherwise every endpoint 401s on
|
||||
* the null userId and SSH is unusable. Mirrors notes-api / memory-api /
|
||||
* user-folder-api. Defaults to `true` for existing (auth-only) callers.
|
||||
*/
|
||||
authActive?: boolean;
|
||||
|
||||
connectionRepo: SshConnectionRepo;
|
||||
grantsRepo: SshGrantsRepo;
|
||||
@@ -279,9 +287,27 @@ function jsonError(res: Response, status: number, error: string, detail?: unknow
|
||||
// User router — /api/ssh/connections + /api/ssh/grants
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* No-auth single-user mode: synthesize a `local` user so the per-user
|
||||
* connection store has a stable owner and `getUserId`/`isAdmin` resolve.
|
||||
* Role `admin` keeps the connection-management surface fully usable for the
|
||||
* lone local operator (in single-user mode the admin/grant checks only ever
|
||||
* compare against `local`-owned rows, so this never widens cross-user access).
|
||||
* No-op when auth is active (Passport has already populated `req.user`).
|
||||
*/
|
||||
function makeSshLocalUserMiddleware(authActive: boolean): RequestHandler {
|
||||
return (req: Request, _res: Response, next: NextFunction) => {
|
||||
if (!authActive && !(req as { user?: unknown }).user) {
|
||||
(req as { user?: unknown }).user = { id: 'local', role: 'admin', orgIds: [] };
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
export function createSshUserRouter(deps: SshApiDeps): Router {
|
||||
const router = Router();
|
||||
const testTimeoutMs = deps.connectionTestTimeoutMs ?? 5000;
|
||||
router.use(makeSshLocalUserMiddleware(deps.authActive ?? true));
|
||||
|
||||
// GET /api/ssh/connections — list owned (and globals visible via grant).
|
||||
// For Phase 5 we return:
|
||||
@@ -801,6 +827,7 @@ export function createSshUserRouter(deps: SshApiDeps): Router {
|
||||
|
||||
export function createSshAdminRouter(deps: SshApiDeps): Router {
|
||||
const router = Router();
|
||||
router.use(makeSshLocalUserMiddleware(deps.authActive ?? true));
|
||||
|
||||
// GET /api/ssh/admin/connections
|
||||
router.get('/connections', deps.requireAdmin, (_req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user