/** * @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ĩ */ 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 };