mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 07:59:42 +02:00
feat(multiuser): phase 3, ownership threading + scoping
Threads per-user ownership through sessions, cases, cron, and the permission policy. All scoping is a no-op in single-user mode (isMultiUserMode() guards). Sessions - Session.owner stamped at every create path from req.authUser / job.owner: POST /api/sessions, /api/run, /api/quick-start, ralph start, cron launch, plan generation. Round-trips through recovery (MuxSession.owner mirror, read muxSession.owner ?? savedState?.owner) and the mux layer. - findSessionOrFail(ctx, id, req) now does a NOT_FOUND owner check (never 403, so other users' session existence is not leaked); wired at ~50 call sites. - List endpoints filtered by owner: GET /api/sessions, /api/sessions/unified (live+persisted+lifecycle scoped, host-wide transcripts admin-only), cron jobs. Permission policy (section 6.3) - resolveClaudeModeForUsername wraps getClaudeModeConfig at every spawn site so a non-granted user is forced to --permission-mode auto (bypass -> auto), including recovery (or a reboot would un-downgrade). buildPromptArgs now respects the session's claudeMode, closing the one-shot (runPrompt) bypass hole. - Shell mode and cron launchCommand require canBypassPermissions: 403 at POST /api/sessions, /api/quick-start create, cron job create, AND cron fire time (re-checked against the owner's current grant). Cases - resolveCasesDir(user): per-user ~/codeman-users/<name>/cases in multi-user, the shared ~/codeman-cases otherwise. All case CRUD + ralph + plan + quick-start resolve through it. resolveCasePath is owner-aware. - GET /api/cases scoped per user (own folders; legacy linked cases admin-only; remote/docker cases owner-filtered). RemoteCase/DockerCase gain owner, stamped at link/quickcreate/import. - Remote + Docker host CRUD is admin-only. - Non-admin workingDir confinement (the linchpin): realpath must resolve inside the user's space, enforced at POST /api/sessions and /api/run BEFORE any disk write. Limits - sessionCapacityState / sessionCapacityMessage centralize the global + per-user cap (CODEMAN_MAX_SESSIONS_PER_USER, default global/2), replacing the 6 copy-pasted MAX_CONCURRENT_SESSIONS checks. Tests: test/ownership-scoping.test.ts (case isolation, host-CRUD gate, workingDir + shell gates, and the scoping helpers). Deferred to phase 4: WS owner gate, SSE fan-out filtering, file-route preview/thumbnail helper scoping, push routing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -409,6 +409,17 @@ export async function deleteUserSpace(username: string): Promise<void> {
|
||||
/** The synthetic admin used in single-user mode so downstream has one code path. */
|
||||
export const SYNTHETIC_ADMIN: AuthUser = { username: 'admin', role: 'admin' };
|
||||
|
||||
/**
|
||||
* Whether a username may run arbitrary commands (shell mode, cron launchCommand,
|
||||
* other CLIs' bypass). Single-user or an unset owner: allowed. In multi-user a
|
||||
* MISSING user (e.g. deleted) fails closed (non-privileged). Used at cron fire time.
|
||||
*/
|
||||
export async function canUsernameRunPrivilegedCommands(username: string | undefined): Promise<boolean> {
|
||||
if (!isMultiUserMode() || !username) return true;
|
||||
const user = await findUser(username);
|
||||
return canRunPrivilegedCommands(user ?? { role: 'user' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the effective Claude mode for a username by looking up the grant. In
|
||||
* single-user mode (or for an unknown owner) the global mode passes through.
|
||||
|
||||
Reference in New Issue
Block a user