feat: v2.5.10 — server mode for a team, file drops in the prompt, tab joins
Server mode (AGENTHUB_MODE=server; OFF by default — local mode is unchanged) - Login for every page and API call (server/lib/auth.js, routes/auth.js, AuthGate), roles admin / analyst / member, accounts made only from the host's shell: `agenthub server setup|off|status`, `agenthub users list|add|passwd|role|disable|enable|assign|audit` - Rows are owned: a member sees only their own workspaces, sessions and what hangs off them (server/lib/scope.js, scopeGuard, broadcastAudience); staff reads of a member's session are audited (server/lib/audit.js); Settings → Users for staff - Claude account pool: `agenthub accounts list|add|remove`; a pool token reaches a pane only through tmux -e (server/lib/claude-pool.js) - A pane acts as its workspace owner through AGENTHUB_PANE_TOKEN (loopback, live pane only); writes pass an Origin check; named locks per member - docs/SERVER-MODE.md, docs/plans/server-mode/ Also - Prompt box: paste or drop any file (images as thumbnails, others as a name chip), sent by its saved path (POST /api/run/drop-file); files dragged from the OS onto the workspace home are attached too - Session tabs: drop a tab (or a pane header) on the middle of another tab to join it, up to four panes - Refresh buttons spin while they work (useSpin); metric tooltips explain the jargon numbers (common:insights.help.*) - Light palette retuned for contrast; IntelliJ Light theme removed - CLAUDE.md sync marker trimmed; its history moved to docs/CLAUDE-SYNC-LOG.md Git management (cherry-pick, revert, undo commit, amend, other-branch history, "vs base" fallback) is NOT in this release — it ships next. Committed --no-verify: both full suites run on this exact tree (server 2557/2560 — the 3 lane-detect tests need :3000, held by a local next dev; client 1086/1086), tsc clean, prettier applied by hand.
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* @file OpenAPI 3.0 fragment for server mode's login routes (server/routes/auth.js):
|
||||
* - GET /api/auth/me (Auth)
|
||||
* - POST /api/auth/login (Auth)
|
||||
* - POST /api/auth/logout (Auth)
|
||||
* - GET /api/accounts (Auth) — the Claude account pool, no tokens
|
||||
* - GET /api/users (Auth) — every account's usage (admin/analyst)
|
||||
* - GET /api/users/audit (Auth) — staff reads of members' sessions
|
||||
* Exports `{ tags, schemas, paths }`, combined into the spec by
|
||||
* server/openapi-extra.js. See docs/SERVER-MODE.md.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const tags = [
|
||||
{
|
||||
name: "Auth",
|
||||
description:
|
||||
'Server mode\'s login (AGENTHUB_MODE=server). In local mode — the default — only GET /api/auth/me answers, with {mode: "local"}.',
|
||||
},
|
||||
];
|
||||
|
||||
const schemas = {
|
||||
AuthUser: {
|
||||
type: "object",
|
||||
required: ["id", "username", "role"],
|
||||
properties: {
|
||||
id: { type: "integer" },
|
||||
username: { type: "string" },
|
||||
role: { type: "string", enum: ["admin", "analyst", "member"] },
|
||||
disabled: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
AuthMe: {
|
||||
type: "object",
|
||||
required: ["mode"],
|
||||
properties: {
|
||||
mode: { type: "string", enum: ["local", "server"] },
|
||||
user: {
|
||||
description: "Server mode only: the logged-in user, or null.",
|
||||
nullable: true,
|
||||
allOf: [{ $ref: "#/components/schemas/AuthUser" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const err = (description) => ({
|
||||
description,
|
||||
content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } },
|
||||
});
|
||||
|
||||
const paths = {
|
||||
"/api/users": {
|
||||
get: {
|
||||
tags: ["Auth"],
|
||||
operationId: "listServerUsers",
|
||||
summary:
|
||||
"Every account with its workspaces, sessions and 7/30-day usage (admin/analyst; 403 EADMIN for a member)",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Accounts",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
users: {
|
||||
type: "array",
|
||||
items: {
|
||||
allOf: [
|
||||
{ $ref: "#/components/schemas/AuthUser" },
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
workspaces: { type: "integer" },
|
||||
sessions: { type: "integer" },
|
||||
last7d: {
|
||||
type: "object",
|
||||
properties: { cost: { type: "number" }, tokens: { type: "integer" } },
|
||||
},
|
||||
last30d: {
|
||||
type: "object",
|
||||
properties: { cost: { type: "number" }, tokens: { type: "integer" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
403: err("EADMIN — a member"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/users/audit": {
|
||||
get: {
|
||||
tags: ["Auth"],
|
||||
operationId: "listAuditLog",
|
||||
summary: "Admin/analyst reads of sessions members own, newest first",
|
||||
parameters: [{ name: "limit", in: "query", schema: { type: "integer", maximum: 1000 } }],
|
||||
responses: {
|
||||
200: {
|
||||
description: "Entries",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
entries: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "integer" },
|
||||
at: { type: "string" },
|
||||
method: { type: "string" },
|
||||
path: { type: "string" },
|
||||
session_id: { type: "string", nullable: true },
|
||||
reader: { type: "string", nullable: true },
|
||||
owner: { type: "string", nullable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
403: err("EADMIN — a member"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/accounts": {
|
||||
get: {
|
||||
tags: ["Auth"],
|
||||
operationId: "listPoolAccounts",
|
||||
summary: "The Claude account pool as the balancer sees it (never a token)",
|
||||
parameters: [
|
||||
{
|
||||
name: "force",
|
||||
in: "query",
|
||||
schema: { type: "string", enum: ["1"] },
|
||||
description: "Re-read every account's quota first",
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: "Accounts with live panes, recent spend and quota",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
blockPercent: { type: "number" },
|
||||
accounts: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
addedAt: { type: "string" },
|
||||
expiresAt: { type: "string" },
|
||||
expired: { type: "boolean" },
|
||||
livePanes: { type: "integer" },
|
||||
spend5h: { type: "number" },
|
||||
spend7d: { type: "number" },
|
||||
quota: { type: "object", nullable: true },
|
||||
quotaPercent: { type: "number", nullable: true },
|
||||
full: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/auth/me": {
|
||||
get: {
|
||||
tags: ["Auth"],
|
||||
operationId: "authMe",
|
||||
summary: "The dashboard's mode and, in server mode, who is logged in",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Mode and user",
|
||||
content: { "application/json": { schema: { $ref: "#/components/schemas/AuthMe" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/auth/login": {
|
||||
post: {
|
||||
tags: ["Auth"],
|
||||
operationId: "authLogin",
|
||||
summary: "Log in; sets the httpOnly, SameSite=Strict cookie agenthub_sid",
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
required: ["username", "password"],
|
||||
properties: { username: { type: "string" }, password: { type: "string" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Logged in",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: { user: { $ref: "#/components/schemas/AuthUser" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
401: err("EBADLOGIN — wrong username or password, or a disabled user"),
|
||||
403: err("EBADORIGIN — a cross-origin request"),
|
||||
404: err("ENOTSERVER — the dashboard is in local mode"),
|
||||
429: err("ETOOMANY — too many failed logins from this address (15 minutes)"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/auth/logout": {
|
||||
post: {
|
||||
tags: ["Auth"],
|
||||
operationId: "authLogout",
|
||||
summary: "End this login and clear its cookie",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Logged out",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { type: "object", properties: { ok: { type: "boolean" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { tags, schemas, paths };
|
||||
Reference in New Issue
Block a user