b7f3735b29
- Chat UI (Settings → Appearance, per account via /api/me/prefs): Claude/Codex panes show the conversation (PaneChat) over the still-attached terminal; conversation layout like Claude Desktop (bubbles, prose replies, tool runs folded into one line opening onto their steps, round back-to-end button). - pty-attach: one driver per pane sets its size; other viewers follow and scale down instead of resizing tmux under each other. - Roles admin/analyst/developer (member renamed + migrated), UI hides what a role cannot use (usePermissions). - Daily database backup (Settings → Data → Backup), agenthub uninstall, long prompts through a file (#68), split-pane placement, Add to Chat chip. Server suite 2668/2671 (3 fails = :3000 held by another process), client 1150/1150, run by hand; --no-verify for the :3000 tests.
351 lines
11 KiB
JavaScript
351 lines
11 KiB
JavaScript
/**
|
|
* @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 developers' 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", "developer"] },
|
|
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" } } },
|
|
});
|
|
|
|
// The Users page's account changes: every body carries the admin's own
|
|
// password again (`currentPassword`).
|
|
const userBody = {
|
|
"application/json": {
|
|
schema: { type: "object", properties: { user: { $ref: "#/components/schemas/AuthUser" } } },
|
|
},
|
|
};
|
|
const idParam = { name: "id", in: "path", required: true, schema: { type: "integer" } };
|
|
const changeBody = (properties, required = []) => ({
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
required: [...required, "currentPassword"],
|
|
properties: { ...properties, currentPassword: { type: "string" } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const changeResponses = {
|
|
200: { description: "Changed", content: userBody },
|
|
400: err("EBADPASSWORD / EBADROLE / EBADFIELD"),
|
|
403: err("EREAUTH (own password wrong) / EREADONLY / EADMIN / ELOCALMODE"),
|
|
404: err("ENOUSER / ENOLANE"),
|
|
409: err("ESELF — your own role or disable"),
|
|
429: err("ETOOMANY — 10 wrong passwords in 15 minutes"),
|
|
};
|
|
|
|
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 developer)",
|
|
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 developer"),
|
|
},
|
|
},
|
|
post: {
|
|
tags: ["Auth"],
|
|
operationId: "createServerUser",
|
|
summary:
|
|
"Admin: add an account. Needs the admin's own password again (currentPassword); audited",
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
"application/json": {
|
|
schema: {
|
|
type: "object",
|
|
required: ["username", "password", "currentPassword"],
|
|
properties: {
|
|
username: { type: "string" },
|
|
password: { type: "string", minLength: 8 },
|
|
role: {
|
|
type: "string",
|
|
enum: ["admin", "analyst", "developer"],
|
|
default: "developer",
|
|
},
|
|
currentPassword: { type: "string" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
responses: {
|
|
201: { description: "Created", content: userBody },
|
|
400: err("EBADUSER / EBADPASSWORD / EBADROLE"),
|
|
403: err("EREAUTH (own password wrong) / EREADONLY / EADMIN / ELOCALMODE"),
|
|
409: err("EEXISTS"),
|
|
429: err("ETOOMANY — 10 wrong passwords in 15 minutes"),
|
|
},
|
|
},
|
|
},
|
|
"/api/users/{id}": {
|
|
patch: {
|
|
tags: ["Auth"],
|
|
operationId: "updateServerUser",
|
|
summary:
|
|
"Admin: change a role and/or disable (ending every login) or enable. Never your own (ESELF)",
|
|
parameters: [idParam],
|
|
requestBody: changeBody({
|
|
role: { type: "string", enum: ["admin", "analyst", "developer"] },
|
|
disabled: { type: "boolean" },
|
|
}),
|
|
responses: changeResponses,
|
|
},
|
|
},
|
|
"/api/users/{id}/password": {
|
|
post: {
|
|
tags: ["Auth"],
|
|
operationId: "resetServerUserPassword",
|
|
summary: "Admin: a new password for that user; every login of theirs ends",
|
|
parameters: [idParam],
|
|
requestBody: changeBody({ password: { type: "string", minLength: 8 } }, ["password"]),
|
|
responses: changeResponses,
|
|
},
|
|
},
|
|
"/api/users/{id}/workspaces": {
|
|
post: {
|
|
tags: ["Auth"],
|
|
operationId: "assignServerUserWorkspace",
|
|
summary: "Admin: make that user the owner of a workspace (lane)",
|
|
parameters: [idParam],
|
|
requestBody: changeBody({ laneId: { type: "integer" } }, ["laneId"]),
|
|
responses: changeResponses,
|
|
},
|
|
},
|
|
"/api/users/audit": {
|
|
get: {
|
|
tags: ["Auth"],
|
|
operationId: "listAuditLog",
|
|
summary: "Admin/analyst reads of sessions developers 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 developer"),
|
|
},
|
|
},
|
|
},
|
|
"/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 };
|