Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,88 @@ flocks start --server-host 127.0.0.1 --webui-host 0.0.0.0
```
If remote access from a virtual machine fails, please specify the host as the virtual machine's IP.

### 4.4 Authentication & API Token

Since the local-account update, every HTTP path is protected by default — only
the WebUI bootstrap pages (`/`, `/auth/*`), static assets, and IM platform
webhooks (`/api/channel/{channel_id}/webhook`) are public.

Initial setup:

1. Open the WebUI and complete the **bootstrap-admin** flow to create the
single `admin` account.
2. The browser session cookie (`flocks_session`) is enough for the WebUI;
no extra steps are required.

Non-browser clients (TUI, SDKs, scripts):

- **Local loopback** (`127.0.0.1` / `::1` / `localhost`, no
`x-forwarded-for` header) is auto-trusted as `local-service` admin. This
covers TUI, plugin sub-processes, and CLI calls running on the same host.
- **Remote** clients must present an API token. The token lives in
`~/.flocks/config/.secret.json` under the secret id `server_api_token`.

On the **server**, generate (or rotate) the token — it is persisted on
the server's local secret store:

```bash
flocks admin generate-api-token # prints token; stores under server_api_token
```

On each **remote client**, store the same token value into the client's
own secret file (so the client SDK / TUI can attach it automatically):

```bash
flocks admin set-api-token --token <token-from-server>
```

Or attach it directly per request via either header:

```text
Authorization: Bearer <token>
X-Flocks-API-Token: <token>
```

Smoke test:

```bash
curl -H "Authorization: Bearer <token>" https://flocks.example.com/api/health
```

Reverse-proxy deployments:

- Always set `X-Forwarded-For` on the proxy. Without it, any direct
loopback request would be auto-elevated to `admin`. The middleware
intentionally refuses to trust loopback when this header is absent and a
proxy is in front.
- For HTTPS termination, also forward `X-Forwarded-Proto: https` so that
the secure-cookie flag is set correctly.

Recovery / lost password:

- Run `flocks admin generate-one-time-password` on the host. The admin
account is then forced into `must_reset_password=true`; the next WebUI
login is redirected to the change-password page. **All non-browser
endpoints return 403 in that state**, so do not run this against an
account that automation depends on without coordination.

Orphan sessions (CLI / background / inbound channels):

- Sessions created without an auth context (CLI commands, background
tasks, inbound IM-channel dispatchers) leave `owner_user_id` empty.
The bootstrap admin still sees them, but a later-added member account
would not. Backfill ownership with:

```bash
flocks admin reassign-orphan-sessions --username admin --dry-run # preview
flocks admin reassign-orphan-sessions --username admin # apply
```

The command summarises `scanned / orphaned / reassigned / failed`
counts; a non-zero `failed` exits with code 2 so CI / scripts can
detect partial-write situations and re-run after fixing the underlying
cause (typically a transient storage error).

## 5. Join our community

Scan the QR code with **WeChat** to join our official discussion group.
Expand Down
59 changes: 59 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,65 @@ flocks start --server-host 127.0.0.1 --webui-host 0.0.0.0
```
虚拟机远程访问失败请指定 host 为虚拟机 IP。

### 4.4 鉴权与 API Token

启用本地账号体系后,所有 HTTP 路径默认要求鉴权,仅以下路径放行:WebUI 引导页(`/`、`/auth/*`)、静态资源、以及 IM 平台 webhook 回调(`/api/channel/{channel_id}/webhook`)。

初次部署:

1. 打开 WebUI,按提示完成 **bootstrap-admin**,创建唯一的 `admin` 账号。
2. WebUI 会自动写入 `flocks_session` Cookie,浏览器侧无需额外配置。

非浏览器客户端(TUI / SDK / 脚本):

- **本机回环**(`127.0.0.1` / `::1` / `localhost`,且请求不带 `x-forwarded-for` 头)会被自动识别为 `local-service` 管理员,满足同机的 TUI、插件子进程、CLI 调用。
- **远程**调用必须携带 API Token。Token 存放于 `~/.flocks/config/.secret.json`,secret id 为 `server_api_token`。

在 **服务端** 生成(或轮换)token,会持久化到服务端本机的 secret store:

```bash
flocks admin generate-api-token # 打印 token 并写入 server_api_token
```

在 **每台远程客户端** 上把同一个 token 写入客户端自己的 secret 文件,让 SDK / TUI 自动携带:

```bash
flocks admin set-api-token --token <服务端打印的 token>
```

也可以按请求显式携带任一 Header:

```text
Authorization: Bearer <token>
X-Flocks-API-Token: <token>
```

快速验证:

```bash
curl -H "Authorization: Bearer <token>" https://flocks.example.com/api/health
```

反向代理部署:

- 反代必须主动注入 `X-Forwarded-For`。若缺失,凡是直连本机回环的请求都会被自动放行为 `admin`;中间件依靠该头来区分"真本机"与"经由反代的外部请求"。
- 若反代终止 HTTPS,请同时透传 `X-Forwarded-Proto: https`,以便服务端正确给 Cookie 加 `Secure` 标志。

忘记密码 / 应急恢复:

- 在服务器上执行 `flocks admin generate-one-time-password`,账号会被强制置为 `must_reset_password=true`;下次 WebUI 登录会跳转到改密页。**这种状态下所有非浏览器接口都会返回 403**,请勿在不通知调用方的情况下对依赖自动化的账号执行该命令。

无主 session(CLI / 后台任务 / inbound 渠道):

- 没有 auth 上下文创建出的 session(CLI 子命令、后台任务、IM 渠道入站 dispatcher)`owner_user_id` 字段为空。bootstrap admin 仍可看到,但**之后新增的 member 账号将完全看不到**。可通过下列命令把这类 session 批量赋给指定 admin:

```bash
flocks admin reassign-orphan-sessions --username admin --dry-run # 预览
flocks admin reassign-orphan-sessions --username admin # 实际写入
```

命令会输出 `scanned / orphaned / reassigned / failed` 四个计数;只要 `failed` 非零就以 exit code 2 退出,方便 CI / 脚本捕获"部分写入"情况、修复底层故障(一般是临时存储错误)后再次运行。

## 5. 加入社区

请使用**微信**扫描下方二维码,加入官方交流群。
Expand Down
8 changes: 8 additions & 0 deletions flocks/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Local account authentication package.
"""

from flocks.auth.context import AuthUser
from flocks.auth.service import AuthService

__all__ = ["AuthUser", "AuthService"]
44 changes: 44 additions & 0 deletions flocks/auth/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Authentication context helpers for request-scoped user identity.
"""

from __future__ import annotations

import contextvars
from typing import Optional

from pydantic import BaseModel, Field


class AuthUser(BaseModel):
"""Current authenticated local user."""

id: str
username: str
role: str = Field(..., description="admin or member")
status: str = Field("active", description="active or disabled")
must_reset_password: bool = False


_current_auth_user: contextvars.ContextVar[Optional[AuthUser]] = contextvars.ContextVar(
"current_auth_user",
default=None,
)


def set_current_auth_user(user: Optional[AuthUser]) -> contextvars.Token:
"""Set current request user in context."""

return _current_auth_user.set(user)


def reset_current_auth_user(token: contextvars.Token) -> None:
"""Reset request user context."""

_current_auth_user.reset(token)


def get_current_auth_user() -> Optional[AuthUser]:
"""Get current request user from context."""

return _current_auth_user.get()
Loading
Loading