Skip to content

Commit d59eff9

Browse files
committed
docs: bilingual README, applicability callout, bind-totp screenshot
- Add README_CN.md (Chinese mirror) with language switcher in both files - Add CAUTION block: high-confidentiality / coarse-grained-permission positioning - Add tagline: fast, lightweight, security- and privacy-first - Add bind-totp screenshot (QR + base32 secret redacted) - Fix license badge label, double space, producer-side gen_docs note - LICENSE: copyright year 2024 -> 2025
1 parent bd83f4c commit d59eff9

4 files changed

Lines changed: 315 additions & 8 deletions

File tree

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BSD 3-Clause License
22

3-
Copyright (c) 2024, 0xYeah
3+
Copyright (c) 2025, 0xYeah
44

55
Redistribution and use in source and binary forms, with or without
66
modification, are permitted provided that the following conditions are met:

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
# api_show — LAN-internal OpenAPI aggregation portal
22

3+
**English** · [中文](README_CN.md)
4+
35
[![Release](https://img.shields.io/github/v/release/0xYeah/api_show)](https://github.com/0xYeah/api_show/releases)
46
[![Go](https://img.shields.io/badge/go-1.24%2B-blue)](https://go.dev/)
5-
[![License](https://img.shields.io/badge/license-LICENSE-lightgrey)](LICENSE)
7+
[![License](https://img.shields.io/badge/license-BSD%203--Clause-blue)](LICENSE)
8+
9+
**A fast, lightweight, security- and privacy-first API documentation portal.**
10+
11+
> [!CAUTION]
12+
> **Applicability — read first.** `api_show` is built for **high-confidentiality, coarse-grained-permission** environments:
13+
>
14+
> -**Fits:** internal / LAN-only API documentation that should never reach the public internet, where access control is expressed at **project + tag-prefix** granularity (e.g. "Alice can see project `payments`, tags `auth*` and `users*`").
15+
> -**Does NOT fit:** SaaS-style multi-tenant docs portals, per-endpoint / per-field ACL, per-operation rate limiting, audit logging requirements, or any setup that needs to be exposed to the open internet.
16+
>
17+
> If you need fine-grained operation-level permissions, audit trails, SSO/OAuth federation, or public hosting — pick a different tool. `api_show` deliberately trades feature breadth for a small, auditable surface that fits inside a LAN.
618
719
`api_show` aggregates OpenAPI specs from multiple producer repos into a
820
single portal rendered by an embedded Redoc bundle (served locally — no
@@ -13,9 +25,12 @@ recompiling the portal.
1325

1426
## Screenshots
1527

16-
| Sign in | Portal — `demo_a` | Portal — `demo_b` |
17-
|---------|-------------------|-------------------|
18-
| ![Sign in](docs/screenshots/portal-login.png) | ![demo_a](docs/screenshots/portal-demo_a.png) | ![demo_b](docs/screenshots/portal-demo_b.png) |
28+
| Sign in | Bind TOTP (one-time) | Portal — `demo_a` | Portal — `demo_b` |
29+
|---------|----------------------|-------------------|-------------------|
30+
| ![Sign in](docs/screenshots/portal-login.png) | ![Bind TOTP](docs/screenshots/portal-bind-totp.png) | ![demo_a](docs/screenshots/portal-demo_a.png) | ![demo_b](docs/screenshots/portal-demo_b.png) |
31+
32+
> The QR code and base32 secret in the bind screenshot are intentionally
33+
> blurred / redacted — each first-time login generates a fresh secret.
1934
2035
Dark theme tokens match Redoc's left sidebar + right code panel. Schema
2136
labels and table rows lift contrast via `webroot/style.css` overrides
@@ -55,7 +70,7 @@ sudo ./install_api_show.sh
5570
sudo systemctl start api_show
5671
sudo $EDITOR /api_show/conf/api_show_config.toml # fill secret + upload_token
5772
sudo systemctl restart api_show
58-
sudo systemctl status api_show
73+
sudo systemctl status api_show
5974
```
6075

6176
`install_api_show.sh` actions:
@@ -214,8 +229,9 @@ DEPLOY_TOKEN=<must match api_show's upload_token>
214229
EOF
215230
```
216231

217-
The shipped `git_tag.sh` rejects loopback addresses (`127.0.0.1`, `localhost`,
218-
`[::1]`, `0.0.0.0`) by default. Set `ALLOW_LOOPBACK=1` only for local testing.
232+
Producers ship their own `gen_docs.sh` (or equivalent) in each repo. A
233+
reference implementation rejects loopback addresses (`127.0.0.1`, `localhost`,
234+
`[::1]`, `0.0.0.0`) unless `ALLOW_LOOPBACK=1` is set for local self-test.
219235

220236
Manual upload (local self-test):
221237

README_CN.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
# api_show —— 局域网内部 OpenAPI 聚合门户
2+
3+
[English](README.md) · **中文**
4+
5+
[![Release](https://img.shields.io/github/v/release/0xYeah/api_show)](https://github.com/0xYeah/api_show/releases)
6+
[![Go](https://img.shields.io/badge/go-1.24%2B-blue)](https://go.dev/)
7+
[![License](https://img.shields.io/badge/license-BSD%203--Clause-blue)](LICENSE)
8+
9+
**注重安全、隐私的快速、轻量化 API 文档展示服务。**
10+
11+
> [!CAUTION]
12+
> **适用性 —— 务必先读。** `api_show` 面向 **高保密性、权限粒度较粗** 的场景:
13+
>
14+
> -**适合:** 仅在内网 / 局域网暴露的 API 文档;权限按 **项目 + tag 前缀** 粒度控制(例:「Alice 可见 `payments` 项目的 `auth*` / `users*` 标签」)。
15+
> -**不适合:** SaaS 多租户文档门户、按端点 / 按字段的细粒度 ACL、按操作限流、合规审计日志,或任何需要直接暴露在公网的部署。
16+
>
17+
> 如果需要操作级细粒度权限、审计追踪、SSO/OAuth 联邦登录、公网托管 —— 请选别的工具。`api_show` 用功能广度换来更小、更可审计的攻击面,目标只有一个:内网。
18+
19+
`api_show` 把多个生产者仓库的 OpenAPI 文档聚合成单一门户,由内嵌的 Redoc 渲染(本地静态资源,无 CDN 依赖)。各生产者仓库在 tag 发版时把自己的 `docs/openapi.yaml` POST 到门户;用户打开同一个 URL,从下拉菜单里选项目 + 版本,就能看到渲染后的接口文档。文档更新不需要重新编译门户。
20+
21+
## 截图
22+
23+
| 登录 | 绑定 TOTP(首次) | 门户 — `demo_a` | 门户 — `demo_b` |
24+
|------|--------------------|------------------|------------------|
25+
| ![登录](docs/screenshots/portal-login.png) | ![绑定 TOTP](docs/screenshots/portal-bind-totp.png) | ![demo_a](docs/screenshots/portal-demo_a.png) | ![demo_b](docs/screenshots/portal-demo_b.png) |
26+
27+
> 绑定页中的二维码与 base32 secret 已主动模糊 / 遮罩 —— 每个用户首次登录都会生成全新的 secret。
28+
29+
暗色主题色板与 Redoc 左侧栏 + 右侧代码面板对齐。Schema 标签 / 表格行通过 `webroot/style.css``#api-reference` 作用域内提升对比度。
30+
31+
> **威胁模型 —— 仅限局域网。** 不暴露公网 IP,永远不上互联网。
32+
> `only_local = true`(默认)下,任何非私网 / 非环回 IP 命中 `/upload` 都会在应用层直接拒绝,不管 Bearer 是否正确。
33+
>
34+
> **认证 —— 三种模式**,由 TOML 中 `auth_mode` 字段切换:
35+
>
36+
> | 模式 | 行为 |
37+
> |-------------|-----------------------------------------------------------------------|
38+
> | `pwd` | 仅用户名 + 密码。无 TOTP。适合 IDE 断点调试。 |
39+
> | `pwd_totp` | 默认。先校验密码;若用户已绑定 TOTP,要求 6 位 2FA 码;否则直接发会话。 |
40+
> | `totp` | 无密码。TOTP 即凭证;首次登录通过 `/bind-totp` 自动绑定。 |
41+
>
42+
> **TOML 中永不存密码。** Argon2id 哈希存放在 `<data_dir>/pwd_blobs/` 下经 AES-GCM 加密的 sidecar blob。管理 CLI 可以创建用户而不需要知道明文密码 —— 见下文 [添加用户](#添加用户)
43+
44+
---
45+
46+
## 安装(局域网部署)
47+
48+
```bash
49+
# 1. 下载 release 包
50+
wget https://github.com/0xYeah/api_show/releases/download/<VERSION>/api_show_linux_release_<VERSION>.zip
51+
unzip api_show_linux_release_<VERSION>.zip && cd api_show
52+
53+
# 2. 一键安装(创建 /api_show/{api_show, conf/, logs/},安装 systemd unit)
54+
sudo ./install_api_show.sh
55+
56+
# 3. 启动 → 编辑配置 → 重启
57+
sudo systemctl start api_show
58+
sudo $EDITOR /api_show/conf/api_show_config.toml # 填 secret + upload_token
59+
sudo systemctl restart api_show
60+
sudo systemctl status api_show
61+
```
62+
63+
`install_api_show.sh` 行为:
64+
65+
| 动作 | 说明 |
66+
|-------------------|-------------------------------------------------------------------------------------|
67+
| `install`(默认) | 首次安装。已存在的二进制 + 配置会加 `_YYYY-MM-DD_HH-MM-SS` 备份后缀。 |
68+
| `update` | 停服务 → 替换二进制(保留 `data/` + `conf/`)→ 重启。 |
69+
| `uninstall` | 停服务 + 删 unit 文件 + 删 `/api_show/` 目录(不可恢复)。 |
70+
71+
---
72+
73+
## 配置
74+
75+
路径:`/api_show/conf/api_show_config.toml`(首次启动时自动用占位值生成)。
76+
77+
```toml
78+
# HMAC cookie 密钥 + 单 blob 密钥派生 salt。≥ 16 字节,ASCII。
79+
secret = "REPLACE_ME_at_least_16_chars"
80+
81+
# /upload 接受的 Bearer token。
82+
upload_token = "REPLACE_ME_bearer_token"
83+
84+
# 上传 spec + totp_blobs/ + pwd_blobs/ 的根目录。
85+
data_dir = "./data"
86+
87+
# HTTP 监听端口。仅绑 IPv4 0.0.0.0。
88+
port = 12110
89+
90+
# 内网模式。true = /upload 在应用层拒绝任何非私网 / 非环回 IP,
91+
# 与 Bearer 是否正确无关。
92+
only_local = true
93+
94+
# 认证策略:pwd | pwd_totp(默认)| totp
95+
auth_mode = "pwd_totp"
96+
97+
# 用户名册 + 单用户 ACL。改完重启即生效。
98+
# TOML 永不存密码 —— 密码以加密 blob 存放在 <data_dir>/pwd_blobs/。
99+
# 详见「添加用户」。
100+
[[users]]
101+
username = "alice"
102+
display = "Alice"
103+
acl = { demo_a = ["*"], demo_b = ["auth", "users"] }
104+
105+
[[users]]
106+
username = "bob"
107+
display = "Bob"
108+
acl = { demo_a = ["*"] }
109+
```
110+
111+
约束:
112+
113+
- `secret` 为空或不足 16 字节 → 进程拒绝启动。
114+
- `upload_token` 为空 → `/upload` 返回 `503 uploads disabled`
115+
- `auth_mode` 不在 `{pwd, pwd_totp, totp}` 范围 → 进程拒绝启动。
116+
- 不读取任何环境变量。配置只来自 TOML;改部署 = 覆盖文件。
117+
- 首次启动若文件不存在,会写入占位配置 —— 启动永不会因「缺配置文件」而失败。
118+
119+
---
120+
121+
## 登录流程
122+
123+
### `pwd` 模式
124+
125+
1. `GET /login` → 输入 `username` + 密码 → 提交。
126+
2. Argon2id 校验通过 → 发 7 天会话 cookie → `302``/`
127+
128+
### `pwd_totp` 模式(默认)
129+
130+
1. `GET /login` → 输入 `username` + 密码 → 提交。
131+
2. 密码通过 → 门户检查用户是否已绑 TOTP。
132+
- 未绑 → 直接发会话 → `302``/`
133+
- 已绑 → `302``/login-totp`
134+
3. `/login-totp` → 输入 6 位码 → 发会话 → `302``/`
135+
136+
### `totp` 模式
137+
138+
1. `GET /login` → 输入 `username`,TOTP 留空 → 提交。
139+
2. 账号未绑 → `302``/bind-totp`,扫二维码 + 输入首个码 →
140+
secret 加密落盘 → 发会话 → `302``/`
141+
3. 后续登录:`username` + 6 位码 → 校验 → 会话 → `/`
142+
143+
### 首次登录但还没设密码
144+
145+
如果用户是用 `add_user` 创建但没带 `-p`,或被 `reset_pwd` 重置过,下次登录时门户会重定向到 `/set-password`。用户自己挑密码(≥ 8 位);管理员永远看不到明文。
146+
147+
### 浏览文档
148+
149+
登录后,根页面用两个下拉菜单列出可见的项目 + 版本。当前选择会反映在 URL 上,方便分享给同一 ACL 的用户。
150+
151+
---
152+
153+
## 添加用户
154+
155+
管理 CLI 永远不会输出明文密码。两种添加模式:
156+
157+
**模式 1 —— 管理员设初始密码,用户后续自改:**
158+
159+
```bash
160+
sudo /api_show/api_show add_user alice -u "Alice" -p "InitChangeMe!8" -acl "demo_a=*;demo_b=auth"
161+
sudo systemctl restart api_show
162+
# 把初始密码以带外渠道告诉 Alice(如签名邮件)。
163+
# 任何时候管理员跑 `reset_pwd alice`,下次登录就走 /set-password 让 Alice 自己改。
164+
```
165+
166+
**模式 2 —— 用户首次登录时自己挑密码:**
167+
168+
```bash
169+
sudo /api_show/api_show add_user alice -u "Alice" -acl "demo_a=*"
170+
sudo systemctl restart api_show
171+
# 告诉 Alice 用任意密码访问 /login。
172+
# 她会被重定向到 /set-password,自己挑一个。
173+
```
174+
175+
`-acl` 语法:`proj1=tag1,tag2;proj2=*``*` 表示该项目的所有 tag。不带 `-acl` 会写入空 ACL 块(管理员之后可以直接编辑 TOML 中的 `[[users]]`)。
176+
177+
CLI 会向当前生效的配置文件追加一个 `[[users]]` 块(保留你的注释);带 `-p` 时还会写加密密码 blob。
178+
179+
---
180+
181+
## 维护命令
182+
183+
```bash
184+
./api_show version # 构建身份(commit / 时间 / OS / Go 版本)
185+
./api_show add_user <user> [-u <display>] # 追加 [[users]] 块 + 可选 pwd blob
186+
[-p <plaintext>] [-acl <spec>]
187+
./api_show reset_pwd <user> # 删 pwd blob;用户下次登录走 /set-password
188+
./api_show reset_totp <user> # 删 TOTP blob;用户下次登录重新绑定(totp / pwd_totp 模式)
189+
```
190+
191+
---
192+
193+
## 推送 spec(生产者侧)
194+
195+
生产者发版脚本只在 `git push --tags` 成功后才把 `docs/openapi.yaml` POST 到门户。endpoint + token 来自生产者仓库内一份 git-ignored 文件(不读环境变量):
196+
197+
```bash
198+
# 在生产者仓库根目录
199+
cat > .gen_docs.local <<'EOF'
200+
DEPLOY_ADDR=http://<portal-host>:12110
201+
DEPLOY_TOKEN=<必须与 api_show 的 upload_token 一致>
202+
# ALLOW_LOOPBACK=1 # 仅本机自测时打开
203+
EOF
204+
```
205+
206+
生产者各自维护自己的 `gen_docs.sh`(或同等脚本)。参考实现默认拒绝环回地址(`127.0.0.1``localhost``[::1]``0.0.0.0`),除非显式设置 `ALLOW_LOOPBACK=1` 走本地自测。
207+
208+
手动上传(本地自测):
209+
210+
```bash
211+
curl -X POST \
212+
-H "Authorization: Bearer <upload_token>" \
213+
--data-binary @./docs/openapi.yaml \
214+
"http://127.0.0.1:12110/upload?project=<proj_id>&version=v0.0.1&latest=true"
215+
```
216+
217+
| 参数 | 含义 |
218+
|-----------|------------------------------------------------------------------------------|
219+
| `project` | 项目 id(同时用作数据目录名 + ACL 键)。 |
220+
| `version` | `vX.Y.Z` 形式。 |
221+
| `latest` | `true` 把该版本标记为默认(在下拉菜单中默认选中)。 |
222+
223+
---
224+
225+
## 权限(ACL)
226+
227+
名册 + 权限存在 TOML 的 `[[users]]` 中:
228+
229+
- 每个 `acl` 映射:项目 id → 允许的 tag 前缀。
230+
- `*` = 该项目下所有 tag。
231+
- 用户 `acl` 中没有的项目 = 该用户完全不可见。
232+
- `GET /api/sources.json` 会过滤掉调用者无权访问的项目。
233+
- `GET /data/<proj>/<ver>/openapi.yaml` 会重写 spec,移除不允许的 operation + tag 声明。
234+
235+
**改名册 = 改配置 + 重启。** 编辑 `/api_show/conf/api_show_config.toml`(或用 `add_user` / `reset_pwd`),然后 `systemctl restart api_show`。无须打 tag / 发 release。
236+
237+
---
238+
239+
## 健康检查
240+
241+
```bash
242+
curl http://<portal-host>:12110/healthz # 200 OK = 存活
243+
```
244+
245+
---
246+
247+
## 升级
248+
249+
```bash
250+
sudo ./install_api_show.sh update
251+
```
252+
253+
`update` 保留 `/api_show/data/`(spec + TOTP/pwd blob)和 `/api_show/conf/`;只替换二进制。
254+
255+
---
256+
257+
## 卸载
258+
259+
```bash
260+
sudo ./install_api_show.sh uninstall
261+
```
262+
263+
停服务,删 unit 文件,递归删除 `/api_show/`(含数据 —— 不可恢复)。
264+
265+
---
266+
267+
## 安全边界
268+
269+
- 设计上仅限内网部署。`only_local = true` 下,`/upload` 拒绝外网 IP,与 token 无关。
270+
- 监听绑定 `0.0.0.0`(IPv4)—— 没有 IPv6 双栈监听。
271+
- `secret`(≥ 16 字节)用于 HMAC 签名 cookie 以及 pwd / TOTP blob 的 AES-GCM 单用户密钥派生。
272+
- 密码:argon2id(m=64 MiB, t=3, p=2),PHC 格式输出,永不明文落盘 / 入 TOML。
273+
- Cookie:HttpOnly + SameSite=Lax。前面套 nginx TLS 后把 `Secure=true` 打开。
274+
-`/upload` 外没有任何远程写 API;改用户名册必须有主机文件系统访问权限。
275+
276+
---
277+
278+
## 从源码构建
279+
280+
```bash
281+
go build ./... # debug 二进制
282+
./build.sh # 带版本戳的发版包
283+
```
284+
285+
`go.mod` 要求 Go 1.24+。依赖纯 Go。
286+
287+
---
288+
289+
## 许可证
290+
291+
[LICENSE](LICENSE)
65.8 KB
Loading

0 commit comments

Comments
 (0)