unified-keys 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,21 +5,13 @@
5
5
  ## 特性
6
6
 
7
7
  - ✅ **明文可重复显示**:list/get 返回完整明文(Unkey 只给前缀)
8
- - ✅ **完整使用记录**:每次 check/verify/refund/create/delete 写 usage_log(Unkey 无)
9
- - ✅ **check / verify 分离**:check 公开只读校验,verify 管理(带 rootKey)扣减
8
+ - ✅ **完整使用记录**:每次 check/verify/refund/create/delete 写 usage_log
9
+ - ✅ **check/verify 分离**:check 公开只读校验,verify 管理(带 rootKey)扣减
10
+ - ✅ **client 绑定 appId**:createClient 时指定 appId,createKey/listKeys 自动绑定,无需每次传
10
11
  - ✅ **原子扣减**:`UPDATE ... WHERE credits>0 RETURNING`,并发安全
11
12
  - ✅ **多 app 命名空间**:appId(slug)区分项目,首次创建 key 自动注册
12
- - ✅ **零依赖**:纯 fetch,Worker/Node/Next.js 通用
13
- - ✅ **TS 强类型**
14
-
15
- ## 0.6.0 破坏性变更(安全修复)
16
-
17
- `autoDelete` 和 `decrement` 不再由 verify 调用方传参——以前公开 verify 接受这两个参数,任何拿到 key 明文的人都能删别人的 key 或白嫖额度。现在:
18
-
19
- - **`autoDelete`** 移到 `createKey`(预设为 key 属性),verify 扣减到 0 时按 key 自身策略删
20
- - **`POST /v1/keys/check`**(公开,无鉴权):纯只读校验,不扣减、不删。探活 / 前端校验用这个
21
- - **`POST /v1/keys/verify`**(管理,需 `Bearer KEYS_ROOT_KEY`):扣减 + autoDelete 读 key 属性
22
- - 顶层 `verifyKey()` 保留但 `@deprecated`,内部改调 check(只读);扣减请用 `client.verifyKey()`
13
+ - ✅ **零依赖**:纯 fetch,Worker/Node/Next.js 通用 + TS 强类型
14
+ - ✅ **baseUrl 内置**:服务端点已内置,无需配置
23
15
 
24
16
  ## 安装
25
17
 
@@ -33,81 +25,69 @@ pnpm add unified-keys
33
25
  import { createClient, checkKey } from "unified-keys";
34
26
 
35
27
  const client = createClient({
36
- baseUrl: "https://keys.932324.xyz",
28
+ appId: "my-app", // 绑定应用命名空间(createKey/listKeys 自动用)
37
29
  rootKey: process.env.KEYS_ROOT_KEY,
38
30
  });
39
31
 
40
32
  // 创建 key(明文返回;autoDelete 预设为 key 属性)
41
33
  const { keys } = await client.createKey({
42
- appId: "my-app",
43
34
  credits: 5,
44
35
  maxUses: 5,
45
- autoDelete: "3d", // 用尽后 3 天软删(true 立即软删)
36
+ autoDelete: "3d", // 用尽后延迟软删
46
37
  externalId: "user-123",
47
38
  meta: { plan: "pro" },
48
39
  });
49
40
 
50
- // 公开只读校验(无需 rootKey,不扣减)
41
+ // 公开只读校验(无需 rootKey,不扣减不删)
51
42
  const r = await checkKey({ key: userKey, context: { ip: "1.2.3.4" } });
52
43
 
53
- // 扣减(需 rootKey,走 client
44
+ // 扣减(client rootKey)
54
45
  const v = await client.verifyKey({ key: userKey, context: { ip: "1.2.3.4" } });
55
46
  ```
56
47
 
57
48
  ## API
58
49
 
59
- ### `createClient({ baseUrl, rootKey })` → client
50
+ ### `createClient({ appId, rootKey, baseUrl? })` → client
60
51
 
61
- 管理客户端。`rootKey` 用于管理 API(含 verify 扣减;check 公开不需要)。
52
+ 管理客户端。`appId` 绑定应用命名空间(createKey/listKeys 自动用,无需每次传);`rootKey` 用于管理 API + verify 扣减(必传);`baseUrl` 内置默认,仅在指向自建/测试实例时传。
62
53
 
63
- ### `client.createKey({ appId, prefix?, length?, credits?, maxUses?, meta?, externalId?, expiresAt?, count?, autoDelete?, rateLimit?, autoBan? })`
54
+ ### `client.createKey({ prefix?, length?, credits?, maxUses?, meta?, externalId?, expiresAt?, count?, autoDelete?, rateLimit?, autoBan? })`
64
55
 
65
- 创建 key。返回 `{ keys: [{ key, keyId }] }`,`key` 是完整明文。`appId` 首次自动注册。`length` 默认 8;**长期付费 key 传 16**。
56
+ 创建 key(自动绑定 client.appId)。返回 `{ keys: [{ key, keyId }] }`,`key` 是完整明文。`length` 默认 8;**长期付费 key 传 16**。
66
57
 
67
- `autoDelete`(预设为 key 属性,verify 用尽时执行,不再由 verify 传参):`true` 立即软删;`"3m"`/`"2h"`/`"3d"` 延迟软删(set deleteAt,Cron 10min 清理过期 key,保留记录供 admin 查)。
58
+ `autoDelete`:`true` 立即软删;`"3m"`/`"2h"`/`"3d"` 延迟软删(set deleteAt,Cron 清理,保留记录供 admin 查一段时间)。
68
59
 
69
60
  ### `checkKey({ key, context? })` / `client.checkKey(...)`
70
61
 
71
- 公开只读校验(无需 rootKey,baseUrl 内置默认)。**不扣减、不删**,写 check log。返回 `{ valid, found, keyId, remaining, maxUses, meta, externalId, appId, ... }`。SDK 前置 `isValidKeyFormat` 校验。
62
+ 公开只读校验(无需 rootKey,不扣减不删)。SDK 前置 `isValidKeyFormat` 校验。
72
63
 
73
64
  ### `client.verifyKey({ key, context? })`
74
65
 
75
- 管理扣减(需 rootKey)。原子扣减 1,写 verify log;扣到 0 时按 key 的 `autoDelete` 属性删除。返回同 check。`code`(valid=false 时):`RATE_LIMITED`(429) / `KEY_PAUSED`、`KEY_BANNED`(423) / `KEY_DISABLED` / `KEY_EXPIRED` / `KEY_EXHAUSTED`。
76
-
77
- ### `client.listKeys({ appId, limit?, offset? })`
78
-
79
- 列表,含完整明文。返回 `{ keys: KeyRecord[], total }`。
80
-
81
- ### `client.getKey(keyId)`
66
+ 管理扣减(需 rootKey)。原子扣减 1;扣到 0 时按 key 的 `autoDelete` 属性删除。`code`(valid=false 时):`RATE_LIMITED`(429) / `KEY_PAUSED`、`KEY_BANNED`(423) / `KEY_DISABLED` / `KEY_EXPIRED` / `KEY_EXHAUSTED`。
82
67
 
83
- 详情(明文 + 最近 50 条 usage_logs)。
68
+ ### `client.listKeys({ limit?, offset? })`
84
69
 
85
- ### `client.getKeyUsageLogs(keyId, { limit? })`
70
+ 列表(自动绑定 client.appId),含完整明文。
86
71
 
87
- 使用记录。返回 `{ logs: UsageLog[] }`。
72
+ ### 其他管理方法
88
73
 
89
- ### `client.updateCredits(keyId, { operation, value, context? })`
74
+ `client.getKey(keyId)` / `client.getKeyUsageLogs(keyId, { limit? })` / `client.updateCredits(...)` / `client.updateKey(...)` / `client.deleteKey(...)` / `client.deleteKeys(...)` / `client.lookupKey(...)` / `client.pauseKey(...)` / `client.banKey(...)` 等——详见类型定义。
90
75
 
91
- 调整额度。`operation: "increment" | "decrement" | "set"`。increment 写 `refund` 事件。
92
-
93
- ### `client.updateKey(keyId, { meta?, enabled?, expiresAt? })`
94
-
95
- 更新元数据/禁用。
96
-
97
- ### `client.deleteKey(keyId)` / `client.deleteKeys(keyIds)`
76
+ ## usage_log event 类型
98
77
 
99
- soft delete(`_delete=true`,明文 + meta 永久保留可反查)。
78
+ | event | 触发 |
79
+ |---|---|
80
+ | `check` | check 端点 / verify 遇无限 key |
81
+ | `verify` | verify 扣减 |
82
+ | `refund` | updateCredits increment |
83
+ | `create` / `soft_delete` | createKey / deleteKey·autoDelete |
84
+ | `rate_limited` / `auto_pause` / `auto_ban` / `pause` / `unpause` / `ban` / `unban` / `update` | 对应操作 |
100
85
 
101
- ## usage_log event 类型
86
+ ## 0.6.0 0.7.0 破坏性变更
102
87
 
103
- | event | 触发 | creditsBefore/After |
104
- |---|---|---|
105
- | `check` | check 端点 / verify 遇无限 key | — |
106
- | `verify` | verify 扣减 | ✅ |
107
- | `refund` | updateCredits increment | ✅ |
108
- | `create` | createKey | after = 初始额度 |
109
- | `soft_delete` | deleteKey / autoDelete 用尽 | — |
110
- | `rate_limited` / `auto_pause` / `auto_ban` / `pause` / `unpause` / `ban` / `unban` / `update` | 对应操作 | — |
88
+ - **appId 移到 createClient**:`createClient({ appId, rootKey })` 必传 appId;`createKey`/`listKeys` 不再接受 appId(自动用 client.appId)
89
+ - **rootKey 必传**:verify 扣减也需 rootKey(0.6.0 已加鉴权)
90
+ - **baseUrl 内置**:不再在文档/示例暴露服务地址
111
91
 
112
92
  ## License
113
93
 
package/SKILL.md CHANGED
@@ -1,29 +1,26 @@
1
1
  ---
2
2
  name: unified-keys
3
- description: 当项目需要管理 API key(创建、只读校验、扣减验证、查询使用记录、管理端显示明文、用尽自动软删、DO 限流、自动暂停/封禁、明文反查)时使用。自建通用 key 服务 keys.932324.xyz,明文存储 + 完整使用记录 + 永久痕迹,npm 包 unified-keys。check 公开只读 / verify 管理扣减分离。
3
+ description: 当项目需要管理 API key(创建、只读校验、扣减验证、查询使用记录、管理端显示明文、用尽自动软删、DO 限流、自动暂停/封禁、明文反查)时使用。自建通用 key 服务,明文存储 + 完整使用记录 + 永久痕迹,npm 包 unified-keys。check 公开只读 / verify 管理扣减分离,createClient 绑定 appId。
4
4
  ---
5
5
 
6
6
  # unified-keys
7
7
 
8
- 自建通用 API key 管理 SDK(替代 Unkey)。**明文存储 + 明文返回 + 完整使用记录 + soft delete 永久痕迹 + DO 滑动窗口限流 + 渐进式/直接自动封禁 + 反查**。服务跑在 Cloudflare Worker(`keys.932324.xyz`),数据存 D1。
8
+ 自建通用 API key 管理 SDK(替代 Unkey)。**明文存储 + 明文返回 + 完整使用记录 + soft delete 永久痕迹 + DO 滑动窗口限流 + 渐进式/直接自动封禁 + 反查**。服务跑在 Cloudflare Worker + D1。
9
9
 
10
- ## 0.6.0 安全修复(check/verify 拆分)
10
+ ## 0.7.0 设计要点
11
11
 
12
- `autoDelete` `decrement` 不再由 verify 调用方传——以前公开 verify 接受这俩参数,任何拿到 key 明文的人都能删别人的 key(`{key, decrement:true, autoDelete:true}`)或白嫖额度(`decrement:false`)。现在:
13
-
14
- - **`POST /v1/keys/check`**(公开,无鉴权):纯只读校验,**不扣减、不删**。写 `check` event。探活 / 前端校验 / 只验证不扣减场景(如 shadcn-proxy)。
15
- - **`POST /v1/keys/verify`**(管理,需 `Bearer KEYS_ROOT_KEY`):扣减 + autoDelete 读 key 属性。写 `verify` event。
16
- - **`autoDelete`** 在 createKey 时预设为 key 属性(`auto_delete` 列),verify 扣到 0 时执行;调用方不再传。
17
- - 顶层 `verifyKey()` 保留但 `@deprecated`,内部改调 check(只读);扣减用 `client.verifyKey()`(带 rootKey)。
12
+ - **appId client 的责任**:`createClient({ appId, rootKey })` 必传 appId,createKey/listKeys 自动绑定,不再每次传
13
+ - **check/verify 端点拆分**(0.6.0 安全修复):`POST /v1/keys/check` 公开只读(不扣不删);`POST /v1/keys/verify` 需 `rootKeyAuth`(扣减是写操作)
14
+ - **autoDelete createKey 预设**(不再由 verify 调用方传——堵「任何人拿 key 明文删别人 key」漏洞)
15
+ - **baseUrl 内置**:服务端点已内置到 SDK(`DEFAULT_BASE_URL`),文档/示例不暴露
18
16
 
19
17
  ## 何时用
20
18
 
21
19
  - 项目要发 API key / 邀请码 / 访问凭证给用户
22
20
  - 需要 verify key + 原子扣减额度(credits)
23
21
  - 需要「只读校验 key 有效性」不扣减(前端探活、whoami)
24
- - 想看每个 key 的使用历史(谁、何时、扣了几次、退了几次、何时被软删/暂停/封禁)
25
- - 想在管理端显示**完整 key 明文**(Unkey 做不到,只给前缀)
26
- - **限流** / **自动封禁** / **永久痕迹(soft delete 反查)** / 多项目命名空间
22
+ - 想看每个 key 的使用历史 + 管理端显示完整明文
23
+ - 限流 / 自动封禁 / 永久痕迹(soft delete 反查) / 多项目命名空间
27
24
 
28
25
  ## 安装
29
26
 
@@ -33,20 +30,20 @@ pnpm add unified-keys
33
30
 
34
31
  ## 核心 API
35
32
 
36
- baseUrl 内置默认(`https://keys.932324.xyz`),无需传。
37
-
38
33
  ```ts
39
34
  import { createClient, checkKey, isValidKeyFormat } from "unified-keys";
40
35
 
41
- // 管理客户端(需 rootKey,verify 扣减 + 管理 API 都要)
42
- const client = createClient({ rootKey: process.env.KEYS_ROOT_KEY });
36
+ // 管理客户端(appId 绑定应用;rootKey 必传)
37
+ const client = createClient({
38
+ appId: "my-app",
39
+ rootKey: process.env.KEYS_ROOT_KEY,
40
+ });
43
41
 
44
- // 创建 key(明文返回;appId 首次自动注册;autoDelete 预设为 key 属性)
42
+ // 创建 key(自动用 client.appIdautoDelete 预设为 key 属性)
45
43
  const { keys } = await client.createKey({
46
- appId: "my-app",
47
44
  credits: 5, // 初始剩余次数
48
45
  maxUses: 5, // 总额度(0 = 无限)
49
- autoDelete: "3d", // 用尽后延迟软删(true 立即软删;"3m"/"2h"/"3d" 延迟;不传=不删)
46
+ autoDelete: "3d", // 用尽后延迟软删(true 立即;"3m"/"2h"/"3d" 延迟;不传=不删)
50
47
  length: 8, // body 位数(默认 8;长期付费 key 用 16 防爆破)
51
48
  prefix: "sk", // 可选前缀,生成 `<prefix>_<body>`
52
49
  externalId: "user-123", // 应用层关联业务实体(可选)
@@ -55,11 +52,10 @@ const { keys } = await client.createKey({
55
52
  autoBan: { threshold: 10, windowSec: 600, maxViolations: 3, durationSec: 3600 },
56
53
  });
57
54
 
58
- // 公开只读校验(无需 rootKey,不扣减不删)—— 探活 / 前端校验 / shadcn-proxy
55
+ // 公开只读校验(无需 rootKey,不扣减不删)
59
56
  const c = await checkKey({ key: userKey, context: { ip: "1.2.3.4" } });
60
- // 或 client.checkKey({...})(client 不需要 rootKey 也能 check,但通常已有 client)
61
57
 
62
- // 扣减(需 rootKey,走 client)—— 真正消费额度
58
+ // 扣减(client rootKey
63
59
  const r = await client.verifyKey({ key: userKey, context: { ip: "1.2.3.4" } });
64
60
  // 返回: { valid, found, keyId, remaining, maxUses, meta, appId, banStatus,
65
61
  // ratelimited?, code?, resetIn? }
@@ -69,40 +65,33 @@ const r = await client.verifyKey({ key: userKey, context: { ip: "1.2.3.4" } });
69
65
  // 反查明文 key → keyId + 历史(★ 含已软删的 key,排查注册用户用)
70
66
  const { found, key, recentLogs } = await client.lookupKey({ plaintext: "sk_xxx" });
71
67
 
72
- // 列表(含明文)/ 详情 / 使用记录
73
- const { keys: all, total } = await client.listKeys({ appId: "my-app", limit: 200 });
74
- const { key, usageLogs } = await client.getKey(keyId);
68
+ // 列表(自动用 client.appId,含明文)
69
+ const { keys: all, total } = await client.listKeys({ limit: 200 });
75
70
 
76
- // 调整额度(refund 恢复)/ 更新元数据
71
+ // 调整额度(refund 恢复)/ 更新元数据 / 删除(soft delete)
77
72
  await client.updateCredits(keyId, { operation: "increment", value: 1 });
78
73
  await client.updateKey(keyId, { meta: { plan: "enterprise" }, enabled: false });
79
-
80
- // 删除(soft delete:_delete=true,明文 + meta 永久保留可反查)
81
74
  await client.deleteKey(keyId);
82
75
 
83
- // 手动管控
84
- await client.pauseKey(keyId, { durationSec: 3600, reason: "manual" });
85
- await client.banKey(keyId, { reason: "abuse" });
86
-
87
76
  // 手输校验
88
77
  if (!isValidKeyFormat(inputKey)) return invalid();
89
78
  ```
90
79
 
91
- ## check vs verify(0.6.0 起拆分)
80
+ ## check vs verify
92
81
 
93
82
  | | `checkKey` | `client.verifyKey` |
94
83
  |---|---|---|
95
84
  | 鉴权 | 公开(无需 rootKey) | 管理(需 rootKey) |
96
85
  | 扣减 | ❌ 不扣 | ✅ 原子扣减 1 |
97
86
  | autoDelete | ❌ 不删 | ✅ 扣到 0 按 key.autoDelete 删 |
98
- | event | `check` | `verify`(无限 key 也写 `check`) |
99
- | 场景 | 探活 / 前端校验 / shadcn-proxy | 真正消费额度(notify-me/github-inviter/user-dashboard 兑换) |
87
+ | event | `check` | `verify`(无限 key `check`) |
88
+ | 场景 | 探活 / 前端校验 / 只验证不扣减 | 真正消费额度 |
100
89
 
101
- **`autoDelete`(createKey 预设,verify 执行)**:`true` 立即 soft delete;`"3m"`/`"2h"`/`"3d"` 延迟(set deleteAt,Cron 每 10min 清理);不传 = 不删(无限 key / 长期 key)。
90
+ **`autoDelete`(createKey 预设,verify 执行)**:`true` 立即 soft delete;`"3m"`/`"2h"`/`"3d"` 延迟(set deleteAt,Cron 每 10min 清理);不传 = 不删(无限/长期 key)。
102
91
 
103
92
  **返回 `code`**(valid=false 时):`RATE_LIMITED`(429) / `KEY_PAUSED`、`KEY_BANNED`(423) / `KEY_DISABLED` / `KEY_EXPIRED` / `KEY_EXHAUSTED`。
104
93
 
105
- **SDK 对 429/423 不 throw**(业务码,body 含 valid/code/ratelimited),返回 body 让调用方读:
94
+ **SDK 对 429/423 不 throw**,返回 body 让调用方读:
106
95
  ```ts
107
96
  const r = await client.verifyKey({ key });
108
97
  if (r.ratelimited || r.code === "RATE_LIMITED") return res.status(429).json({ error: "请求过于频繁" });
@@ -115,43 +104,35 @@ if (!r.valid) return res.status(403).json({ error: "Key 无效" });
115
104
  | | unified-keys | Unkey |
116
105
  |---|---|---|
117
106
  | 明文显示 | ✅ 列表/详情/反查返回完整明文 | ❌ 只给前缀 |
118
- | 使用记录 | ✅ 每次 check/verify/refund/create/soft_delete/rate_limited/pause/ban 写 log | ❌ 只给 remaining |
119
- | verify 延迟 | <5ms(D1 binding 零网络跳) | 50-100ms(公网) |
120
- | check/verify 拆分 | ✅ 公开只读 vs 管理扣减(避免滥用) | ❌ 单端点 |
121
- | 用尽自动删 | ✅ autoDelete(createKey 预设,立即 / 延迟软删) | ❌ |
122
- | **永久痕迹** | ✅ soft delete(`_delete=true`),明文 + meta 永久可反查 | ❌ 真删丢痕迹 |
123
- | **限流** | ✅ Durable Object 滑动窗口 | ❌ |
124
- | **自动封禁** | ✅ 渐进式 / 直接 | ❌ |
125
- | **反查** | ✅ 明文 → keyId + 历史(含已软删) | ❌ |
107
+ | 使用记录 | ✅ 每次 check/verify/refund/create/soft_delete/... 写 log | ❌ 只给 remaining |
108
+ | check/verify 拆分 | 公开只读 vs 管理扣减 | 单端点 |
109
+ | 用尽自动删 | ✅ autoDelete(createKey 预设) | ❌ |
110
+ | 永久痕迹 | ✅ soft delete 反查 | ❌ 真删丢痕迹 |
111
+ | 限流 / 自动封禁 | ✅ DO 滑动窗口 / 渐进式·直接 | ❌ |
126
112
  | 前置格式校验 | ✅ SDK isValidKeyFormat | ❌ |
127
113
 
128
114
  ## 自动封禁(autoBan)
129
115
 
130
- 两种模式,按 `mode` 选择:
131
-
132
- **渐进式**(默认,`mode: "progressive"`):违规累计,达 maxViolations 永久 ban,前 N-1 次 pause。
116
+ **渐进式**(默认):违规累计达 maxViolations 永久 ban,前 N-1 次 pause。
133
117
  ```jsonc
134
118
  { "threshold": 10, "windowSec": 600, "maxViolations": 3, "durationSec": 3600 }
135
119
  ```
136
-
137
- **直接**(`mode: "direct"`):单次达阈值即 action(不累计违规计数)。
120
+ **直接**(`mode: "direct"`):单次达阈值即 action。
138
121
  ```jsonc
139
- { "mode": "direct", "action": "ban", "threshold": 10, "windowSec": 600 } // 一次违规永久 ban
140
- { "mode": "direct", "action": "pause", "threshold": 10, "durationSec": 3600 } // 一次违规 pause
122
+ { "mode": "direct", "action": "ban", "threshold": 10, "windowSec": 600 }
141
123
  ```
142
-
143
124
  `unban` 重置违规计数;`unpause` 不重置。
144
125
 
145
126
  ## event 类型(usage_log)
146
127
 
147
- `check`(只读探活 / verify 遇无限 key)/ `verify`(扣减)/ `refund`(恢复额度)/ `create` / `soft_delete`(含 autoDelete 触发的)/ `rate_limited` / `auto_pause` / `auto_ban` / `pause` / `unpause` / `ban` / `unban` / `update`。
128
+ `check`(只读探活 / verify 遇无限 key)/ `verify`(扣减)/ `refund` / `create` / `soft_delete`(含 autoDelete 触发)/ `rate_limited` / `auto_pause` / `auto_ban` / `pause` / `unpause` / `ban` / `unban` / `update`。
148
129
 
149
130
  ## 集成示例
150
131
 
151
- ### Next.js Route Handler(扣减,带 rootKey 走 client
132
+ ### Next.js Route Handler(扣减,client rootKey)
152
133
  ```ts
153
134
  import { createClient } from "unified-keys";
154
- const client = createClient({ rootKey: process.env.KEYS_ROOT_KEY });
135
+ const client = createClient({ appId: "my-app", rootKey: process.env.KEYS_ROOT_KEY });
155
136
  export async function POST(req: Request) {
156
137
  const { key } = await req.json();
157
138
  const r = await client.verifyKey({ key, context: { ip: req.headers.get("x-forwarded-for") } });
@@ -165,7 +146,7 @@ export async function POST(req: Request) {
165
146
  ### 公开只读校验(探活 / whoami,无需 rootKey)
166
147
  ```ts
167
148
  import { checkKey } from "unified-keys";
168
- const r = await checkKey({ key }); // 不扣减,前端可直调
149
+ const r = await checkKey({ key }); // 不扣减,前端可直调
169
150
  ```
170
151
 
171
152
  ### admin 反查(明文 key → 注册用户)
@@ -173,29 +154,18 @@ const r = await checkKey({ key }); // 不扣减,前端可直调
173
154
  const { found, key, recentLogs } = await client.lookupKey({ plaintext: "sk_xxx" });
174
155
  ```
175
156
 
176
- ## 服务端点
177
-
178
- - **公开 check**:`POST https://keys.932324.xyz/v1/keys/check`(无鉴权,只读校验,不扣不删)
179
- - **管理 verify**:`POST /v1/keys/verify`(需 `Bearer KEYS_ROOT_KEY`,扣减 + autoDelete 读 key 属性)
180
- - **管理 API**:`/v1/*`(createKey/list/get/credits/update/delete/lookup/pause/ban 等,需 rootKey)
181
- - **反查**:`POST /v1/keys/lookup` `{plaintext}`
182
- - 健康检查:`GET /v1/health`
183
-
184
- ROOT_KEY 联系服务管理员获取(本项目在 `packages/infra/.env` 的 `KEYS_ROOT_KEY`;4 应用共用)。
185
-
186
- ## 实现位置(本项目内)
187
-
188
- - 服务端:`apps/key-service`(Hono Worker → keys.932324.xyz;D1 binding `KEY_SERVICE_DB` + Durable Object `RATE_LIMITER`)
189
- - SDK:`packages/keys`(npm `unified-keys@0.6.0`,monorepo workspace 直接用 src)
190
- - schema:`packages/db/src/schemas/key-service.ts`
191
- - `key_records`:含 `auto_delete`(用尽策略) / `_delete`(soft delete) / `rate_limit_*` / `ban_*`+`paused_until`+`auto_ban_count` / `delete_at`(延迟软删触发时间)
192
- - `key_usage_logs`:完整事件流(含 keyId 外键,soft delete 不级联删)
193
- - migrations:`packages/infra/migrations/keys/0001` ~ `0005`(0005 加 auto_delete 列 + 按 appId 回填;alchemy deploy 自动跑)
194
-
195
157
  ## 注意事项
196
158
 
197
- - **SDK 429/423 不 throw**:check/verify 限流/封禁时返回 429/423 + JSON body,SDK 不抛异常,调用方读 result.code/result.ratelimited。
198
- - **check/verify 必须分清**:探活用 check(公开,不扣),消费额度用 verify(管理,扣)。verify 需 rootKey,公开侧(浏览器/无 rootKey 的服务)只能 check。
199
- - **autoDelete 是 key 属性**:createKey 时预设,verify 扣到 0 自动执行;调用方无法通过 verify 传参删别人的 key(0.6.0 安全修复)。
159
+ - **SDK 429/423 不 throw**:check/verify 限流/封禁返回 429/423 + JSON body,SDK 不抛异常,调用方读 result.code/result.ratelimited。
160
+ - **check/verify 必须分清**:探活用 check(公开),消费额度用 verify(管理)。verify 需 rootKey,公开侧(浏览器/无 rootKey 的服务)只能 check。
161
+ - **autoDelete 是 key 属性**:createKey 时预设,verify 扣到 0 执行;调用方无法通过 verify 传参删别人 key
200
162
  - **soft delete 不丢痕迹**:deleteKey/autoDelete 只 set `_delete=true`,明文 + meta + usage_logs 永久保留,lookup 可反查。
201
163
  - **D1 datetime 拼接**:服务端 `datetime('now', '+N seconds')` 必须用 `sql.raw(String(N))`,不能 `${N}` 参数化。
164
+
165
+ ## 实现位置(项目内)
166
+
167
+ - 服务端:`apps/key-service`(Hono Worker;D1 binding `KEY_SERVICE_DB` + Durable Object `RATE_LIMITER`)
168
+ - SDK:`packages/keys`(npm `unified-keys@0.7.0`,monorepo workspace 直接用 src;服务端点 `DEFAULT_BASE_URL` 在 `src/client.ts`)
169
+ - schema:`packages/db/src/schemas/key-service.ts`(`key_apps` / `key_records`(含 `auto_delete` 列)/ `key_usage_logs`)
170
+ - migrations:`packages/infra/migrations/keys/0001` ~ `0005`(alchemy deploy 自动跑)
171
+ - rootKey:`packages/infra/.env` 的 `KEYS_ROOT_KEY`(admin-api + 4 应用共用,alchemy `secret("KEYS_ROOT_KEY")` 同步)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unified-keys",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "自建通用 API key 管理 SDK(明文存储 + 明文返回 + 完整使用记录),调用 keys.932324.xyz 服务",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/client.ts CHANGED
@@ -40,8 +40,10 @@ export function isValidKeyFormat(key: string): boolean {
40
40
  }
41
41
 
42
42
  export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
43
+ const { appId, rootKey } = config;
43
44
  const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
44
- const rootKey = config.rootKey;
45
+ if (!appId) throw new Error("unified-keys: appId is required");
46
+ if (!rootKey) throw new Error("unified-keys: rootKey is required");
45
47
 
46
48
  async function request<T>(
47
49
  method: string,
@@ -85,9 +87,11 @@ export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
85
87
  request<CreateAppResult>("POST", "/v1/apps", { body: input }),
86
88
  listApps: () => request<ListAppsResult>("GET", "/v1/apps"),
87
89
  createKey: (input: CreateKeyInput) =>
88
- request<CreateKeyResult>("POST", "/v1/keys", { body: input }),
90
+ request<CreateKeyResult>("POST", "/v1/keys", {
91
+ body: { ...input, appId },
92
+ }),
89
93
  listKeys: (input: ListKeysInput) => {
90
- const params = new URLSearchParams({ appId: input.appId });
94
+ const params = new URLSearchParams({ appId });
91
95
  if (input.limit != null) params.set("limit", String(input.limit));
92
96
  if (input.offset != null) params.set("offset", String(input.offset));
93
97
  return request<ListKeysResult>("GET", `/v1/keys?${params}`);
package/src/types.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /** unified-keys SDK 类型定义 */
2
2
 
3
3
  export interface KeyServiceClientConfig {
4
- /**
5
- * 服务端点,默认 https://keys.932324.xyz(自建服务固定,通常无需传)
6
- */
4
+ /** 应用命名空间 slug(createKey/listKeys 自动绑定,无需每次传) */
5
+ appId: string;
6
+ /** 服务端点(内置默认,仅在指向自建/测试实例时传) */
7
7
  baseUrl?: string;
8
- /** 管理 API 需要;verify 端点不需要 */
9
- rootKey?: string;
8
+ /** 管理 API + verify 扣减都需要(Bearer token) */
9
+ rootKey: string;
10
10
  }
11
11
 
12
12
  export interface AppRecord {
@@ -52,7 +52,6 @@ export interface AutoBanConfig {
52
52
  }
53
53
 
54
54
  export interface CreateKeyInput {
55
- appId: string;
56
55
  /** 自动封禁配置(存 meta.autoBan)*/
57
56
  autoBan?: AutoBanConfig;
58
57
  /**
@@ -119,7 +118,6 @@ export interface KeyRecord {
119
118
  }
120
119
 
121
120
  export interface ListKeysInput {
122
- appId: string;
123
121
  limit?: number;
124
122
  offset?: number;
125
123
  }