diff --git a/API.md b/API.md index dd7f1c0..2e20559 100644 --- a/API.md +++ b/API.md @@ -445,6 +445,45 @@ Base 前缀 `/api/v1/admin`。 --- +### 6.1 `POST /api/v1/admin/employees` + +创建员工账号。 + +- **请求体字段**: + +| 字段 | 类型 | 必填 | 限制 | 说明 | +|---|---|---|---|---| +| `username` | string | 是 | 2~64 字符 | 登录名,workspace 内唯一 | +| `display_name` | string | 是 | 1~100 字符 | 显示名称 | +| `email` | string | 否 | ≤255 字符 | 邮箱,全局唯一 | +| `role_code` | string | 否 | `admin` \| `developer` | 默认 `developer` | +| `password` | string | 是 | 8~72 字符 | 登录密码 | + +- **密码说明**: + - 密码明文**不会**存入数据库,后端使用 bcrypt 哈希后保存到 `password_hash`。 + - 请求体中 `password` 必填,长度必须在 8~72 字符之间,否则返回 `422`。 + - 创建成功后的响应**不**包含 `password` 或 `password_hash`。 + +- **响应 201**: + ```json + { + "request_id": "...", + "data": { + "user_id": "...", + "username": "...", + "display_name": "...", + "email": "...", + "status": "active", + "role_code": "developer", + "role_name": "...", + "created_at": "..." + }, + "meta": {} + } + ``` + +> `PATCH` / `DELETE` 员工接口**不**涉及密码字段,也不返回密码相关信息。 + ## 七、Jupyter 路由 > **本节是 Nginx 行为,不是直接 HTTP 端点**。前端**不要**直接调用。 diff --git a/backend/src/backend/admin.py b/backend/src/backend/admin.py index d911bd7..9ad2d00 100644 --- a/backend/src/backend/admin.py +++ b/backend/src/backend/admin.py @@ -9,6 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from common.db.models import Roles, Users, WorkspaceMembers from common.ids import new_ulid +from common.auth.passwords import hash_password from backend.dependencies import ( RequestContext, database_session, @@ -26,6 +27,7 @@ class EmployeeCreate(BaseModel): display_name: str = Field(min_length=1, max_length=100) email: str | None = Field(default=None, max_length=255) role_code: Literal["admin", "developer"] = "developer" + password: str = Field(min_length=8, max_length=72) class EmployeeUpdate(BaseModel): @@ -133,7 +135,7 @@ async def create_employee( username=username, display_name=display_name, email=payload.email.strip() if payload.email else None, - password_hash="demo-login-disabled", + password_hash=hash_password(payload.password), status="active", ) session.add(user)