unified-keys 0.5.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 +46 -39
- package/SKILL.md +108 -97
- package/package.json +1 -1
- package/src/client.ts +58 -13
- package/src/index.ts +11 -1
- package/src/types.ts +116 -17
package/README.md
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
7
|
- ✅ **明文可重复显示**:list/get 返回完整明文(Unkey 只给前缀)
|
|
8
|
-
- ✅ **完整使用记录**:每次 verify/refund/create/delete 写 usage_log
|
|
9
|
-
- ✅ **verify
|
|
8
|
+
- ✅ **完整使用记录**:每次 check/verify/refund/create/delete 写 usage_log
|
|
9
|
+
- ✅ **check/verify 分离**:check 公开只读校验,verify 管理(带 rootKey)扣减
|
|
10
|
+
- ✅ **client 绑定 appId**:createClient 时指定 appId,createKey/listKeys 自动绑定,无需每次传
|
|
11
|
+
- ✅ **原子扣减**:`UPDATE ... WHERE credits>0 RETURNING`,并发安全
|
|
10
12
|
- ✅ **多 app 命名空间**:appId(slug)区分项目,首次创建 key 自动注册
|
|
11
|
-
- ✅ **零依赖**:纯 fetch,Worker/Node/Next.js 通用
|
|
12
|
-
- ✅ **
|
|
13
|
+
- ✅ **零依赖**:纯 fetch,Worker/Node/Next.js 通用 + TS 强类型
|
|
14
|
+
- ✅ **baseUrl 内置**:服务端点已内置,无需配置
|
|
13
15
|
|
|
14
16
|
## 安装
|
|
15
17
|
|
|
@@ -20,67 +22,72 @@ pnpm add unified-keys
|
|
|
20
22
|
## 快速开始
|
|
21
23
|
|
|
22
24
|
```ts
|
|
23
|
-
import { createClient,
|
|
25
|
+
import { createClient, checkKey } from "unified-keys";
|
|
24
26
|
|
|
25
27
|
const client = createClient({
|
|
26
|
-
|
|
28
|
+
appId: "my-app", // 绑定应用命名空间(createKey/listKeys 自动用)
|
|
27
29
|
rootKey: process.env.KEYS_ROOT_KEY,
|
|
28
30
|
});
|
|
29
31
|
|
|
30
|
-
// 创建 key
|
|
32
|
+
// 创建 key(明文返回;autoDelete 预设为 key 属性)
|
|
31
33
|
const { keys } = await client.createKey({
|
|
32
|
-
appId: "my-app",
|
|
33
34
|
credits: 5,
|
|
35
|
+
maxUses: 5,
|
|
36
|
+
autoDelete: "3d", // 用尽后延迟软删
|
|
34
37
|
externalId: "user-123",
|
|
35
38
|
meta: { plan: "pro" },
|
|
36
39
|
});
|
|
37
|
-
console.log(keys[0].key); // 完整明文
|
|
38
40
|
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
41
|
+
// 公开只读校验(无需 rootKey,不扣减不删)
|
|
42
|
+
const r = await checkKey({ key: userKey, context: { ip: "1.2.3.4" } });
|
|
43
|
+
|
|
44
|
+
// 扣减(client 带 rootKey)
|
|
45
|
+
const v = await client.verifyKey({ key: userKey, context: { ip: "1.2.3.4" } });
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
## API
|
|
47
49
|
|
|
48
|
-
### `createClient({
|
|
49
|
-
|
|
50
|
+
### `createClient({ appId, rootKey, baseUrl? })` → client
|
|
51
|
+
|
|
52
|
+
管理客户端。`appId` 绑定应用命名空间(createKey/listKeys 自动用,无需每次传);`rootKey` 用于管理 API + verify 扣减(必传);`baseUrl` 内置默认,仅在指向自建/测试实例时传。
|
|
53
|
+
|
|
54
|
+
### `client.createKey({ prefix?, length?, credits?, maxUses?, meta?, externalId?, expiresAt?, count?, autoDelete?, rateLimit?, autoBan? })`
|
|
55
|
+
|
|
56
|
+
创建 key(自动绑定 client.appId)。返回 `{ keys: [{ key, keyId }] }`,`key` 是完整明文。`length` 默认 8;**长期付费 key 传 16**。
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
创建 key。返回 `{ keys: [{ key, keyId }] }`,`key` 是完整明文(`<prefix>_<base62 length>`)。`appId` 首次自动注册。`length` 默认 8(短期邀请码等);**长期付费 key 传 16**(≈95 bit 防爆破)。
|
|
58
|
+
`autoDelete`:`true` 立即软删;`"3m"`/`"2h"`/`"3d"` 延迟软删(set deleteAt,Cron 清理,保留记录供 admin 查一段时间)。
|
|
53
59
|
|
|
54
|
-
### `
|
|
55
|
-
公开验证(无需 rootKey,baseUrl 内置默认)。`decrement: true` 扣减 + 写 verify log;`false` 只查。`autoDelete`:扣减后额度用尽(remaining=0)自动删——`true` 立即删;`"3m"`/`"2h"`/`"3d"` 延迟删(set deleteAt,Cron 每 10min 清理过期 key,保留记录供 admin 查看一段时间)。SDK 前置 `isValidKeyFormat` 校验,格式不对直接返回不查 DB。
|
|
56
|
-
返回 `{ valid, found, keyId, remaining, maxUses, meta, externalId, appId, enabled, expiresAt, createdAt }`。
|
|
60
|
+
### `checkKey({ key, context? })` / `client.checkKey(...)`
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
列表,含完整明文。返回 `{ keys: KeyRecord[], total }`。
|
|
62
|
+
公开只读校验(无需 rootKey,不扣减不删)。SDK 前置 `isValidKeyFormat` 校验。
|
|
60
63
|
|
|
61
|
-
### `client.
|
|
62
|
-
详情(明文 + 最近 50 条 usage_logs)。
|
|
64
|
+
### `client.verifyKey({ key, context? })`
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
使用记录。返回 `{ logs: UsageLog[] }`。
|
|
66
|
+
管理扣减(需 rootKey)。原子扣减 1;扣到 0 时按 key 的 `autoDelete` 属性删除。`code`(valid=false 时):`RATE_LIMITED`(429) / `KEY_PAUSED`、`KEY_BANNED`(423) / `KEY_DISABLED` / `KEY_EXPIRED` / `KEY_EXHAUSTED`。
|
|
66
67
|
|
|
67
|
-
### `client.
|
|
68
|
-
调整额度。`operation: "increment" | "decrement" | "set"`。increment 写 `refund` 事件。
|
|
68
|
+
### `client.listKeys({ limit?, offset? })`
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
更新元数据/禁用。
|
|
70
|
+
列表(自动绑定 client.appId),含完整明文。
|
|
72
71
|
|
|
73
|
-
###
|
|
72
|
+
### 其他管理方法
|
|
73
|
+
|
|
74
|
+
`client.getKey(keyId)` / `client.getKeyUsageLogs(keyId, { limit? })` / `client.updateCredits(...)` / `client.updateKey(...)` / `client.deleteKey(...)` / `client.deleteKeys(...)` / `client.lookupKey(...)` / `client.pauseKey(...)` / `client.banKey(...)` 等——详见类型定义。
|
|
74
75
|
|
|
75
76
|
## usage_log event 类型
|
|
76
77
|
|
|
77
|
-
| event | 触发 |
|
|
78
|
-
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
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` | 对应操作 |
|
|
85
|
+
|
|
86
|
+
## 0.6.0 → 0.7.0 破坏性变更
|
|
87
|
+
|
|
88
|
+
- **appId 移到 createClient**:`createClient({ appId, rootKey })` 必传 appId;`createKey`/`listKeys` 不再接受 appId(自动用 client.appId)
|
|
89
|
+
- **rootKey 必传**:verify 扣减也需 rootKey(0.6.0 已加鉴权)
|
|
90
|
+
- **baseUrl 内置**:不再在文档/示例暴露服务地址
|
|
84
91
|
|
|
85
92
|
## License
|
|
86
93
|
|
package/SKILL.md
CHANGED
|
@@ -1,160 +1,171 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: unified-keys
|
|
3
|
-
description: 当项目需要管理 API key
|
|
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)。**明文存储 + 明文返回 + 完整使用记录 +
|
|
8
|
+
自建通用 API key 管理 SDK(替代 Unkey)。**明文存储 + 明文返回 + 完整使用记录 + soft delete 永久痕迹 + DO 滑动窗口限流 + 渐进式/直接自动封禁 + 反查**。服务跑在 Cloudflare Worker + D1。
|
|
9
|
+
|
|
10
|
+
## 0.7.0 设计要点
|
|
11
|
+
|
|
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`),文档/示例不暴露
|
|
9
16
|
|
|
10
17
|
## 何时用
|
|
11
18
|
|
|
12
19
|
- 项目要发 API key / 邀请码 / 访问凭证给用户
|
|
13
20
|
- 需要 verify key + 原子扣减额度(credits)
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- 多项目共用一个 key 服务(appId 命名空间隔离)
|
|
21
|
+
- 需要「只读校验 key 有效性」不扣减(前端探活、whoami)
|
|
22
|
+
- 想看每个 key 的使用历史 + 管理端显示完整明文
|
|
23
|
+
- 限流 / 自动封禁 / 永久痕迹(soft delete 反查) / 多项目命名空间
|
|
18
24
|
|
|
19
25
|
## 安装
|
|
20
26
|
|
|
21
27
|
```bash
|
|
22
28
|
pnpm add unified-keys
|
|
23
|
-
# 或 npm i unified-keys
|
|
24
29
|
```
|
|
25
30
|
|
|
26
31
|
## 核心 API
|
|
27
32
|
|
|
28
|
-
**baseUrl 内置默认**(`https://keys.932324.xyz`),无需传。
|
|
29
|
-
|
|
30
33
|
```ts
|
|
31
|
-
import { createClient,
|
|
34
|
+
import { createClient, checkKey, isValidKeyFormat } from "unified-keys";
|
|
32
35
|
|
|
33
|
-
//
|
|
34
|
-
const client = createClient({
|
|
35
|
-
|
|
36
|
-
// 创建 key(明文返回;appId = 项目 slug,首次自动注册)
|
|
37
|
-
const { keys } = await client.createKey({
|
|
36
|
+
// 管理客户端(appId 绑定应用;rootKey 必传)
|
|
37
|
+
const client = createClient({
|
|
38
38
|
appId: "my-app",
|
|
39
|
-
|
|
40
|
-
maxUses: 5, // 总额度(0 = 无限)
|
|
41
|
-
length: 8, // body 位数(默认 8;长期付费 key 用 16 防爆破)
|
|
42
|
-
externalId: "user-123", // 应用层关联业务实体(可选)
|
|
43
|
-
meta: { plan: "pro" }, // 任意元数据(可选)
|
|
44
|
-
count: 1, // 批量创建数
|
|
39
|
+
rootKey: process.env.KEYS_ROOT_KEY,
|
|
45
40
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
|
|
42
|
+
// 创建 key(自动用 client.appId;autoDelete 预设为 key 属性)
|
|
43
|
+
const { keys } = await client.createKey({
|
|
44
|
+
credits: 5, // 初始剩余次数
|
|
45
|
+
maxUses: 5, // 总额度(0 = 无限)
|
|
46
|
+
autoDelete: "3d", // 用尽后延迟软删(true 立即;"3m"/"2h"/"3d" 延迟;不传=不删)
|
|
47
|
+
length: 8, // body 位数(默认 8;长期付费 key 用 16 防爆破)
|
|
48
|
+
prefix: "sk", // 可选前缀,生成 `<prefix>_<body>`
|
|
49
|
+
externalId: "user-123", // 应用层关联业务实体(可选)
|
|
50
|
+
meta: { plan: "pro" }, // 任意元数据(可选)
|
|
51
|
+
rateLimit: { max: 60, windowSec: 60 }, // 限流(max 不传 = 不限速)
|
|
52
|
+
autoBan: { threshold: 10, windowSec: 600, maxViolations: 3, durationSec: 3600 },
|
|
54
53
|
});
|
|
55
|
-
// { valid, found, keyId, remaining, maxUses, meta, externalId, appId, enabled, expiresAt, createdAt }
|
|
56
|
-
// found=false: key 不存在;valid=false found=true: 存在但禁用/过期/额度耗尽
|
|
57
54
|
|
|
58
|
-
//
|
|
59
|
-
const
|
|
55
|
+
// 公开只读校验(无需 rootKey,不扣减不删)
|
|
56
|
+
const c = await checkKey({ key: userKey, context: { ip: "1.2.3.4" } });
|
|
60
57
|
|
|
61
|
-
//
|
|
62
|
-
const { key,
|
|
58
|
+
// 扣减(client 带 rootKey)
|
|
59
|
+
const r = await client.verifyKey({ key: userKey, context: { ip: "1.2.3.4" } });
|
|
60
|
+
// 返回: { valid, found, keyId, remaining, maxUses, meta, appId, banStatus,
|
|
61
|
+
// ratelimited?, code?, resetIn? }
|
|
62
|
+
// code(valid=false 时): RATE_LIMITED(429) / KEY_PAUSED(423) / KEY_BANNED(423)
|
|
63
|
+
// / KEY_DISABLED / KEY_EXPIRED / KEY_EXHAUSTED
|
|
63
64
|
|
|
64
|
-
//
|
|
65
|
-
const {
|
|
66
|
-
// logs: [{ event: "verify"|"refund"|"create"|"delete"|"update"|"disable", creditsBefore, creditsAfter, context, createdAt }]
|
|
65
|
+
// 反查明文 key → keyId + 历史(★ 含已软删的 key,排查注册用户用)
|
|
66
|
+
const { found, key, recentLogs } = await client.lookupKey({ plaintext: "sk_xxx" });
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
await client.
|
|
70
|
-
operation: "increment", // increment | decrement | set
|
|
71
|
-
value: 1,
|
|
72
|
-
context: { reason: "refund" },
|
|
73
|
-
});
|
|
68
|
+
// 列表(自动用 client.appId,含明文)
|
|
69
|
+
const { keys: all, total } = await client.listKeys({ limit: 200 });
|
|
74
70
|
|
|
75
|
-
// 更新元数据 /
|
|
71
|
+
// 调整额度(refund 恢复)/ 更新元数据 / 删除(soft delete)
|
|
72
|
+
await client.updateCredits(keyId, { operation: "increment", value: 1 });
|
|
76
73
|
await client.updateKey(keyId, { meta: { plan: "enterprise" }, enabled: false });
|
|
77
|
-
|
|
78
|
-
// 删除
|
|
79
74
|
await client.deleteKey(keyId);
|
|
80
|
-
await client.deleteKeys([keyId1, keyId2]);
|
|
81
75
|
|
|
82
|
-
//
|
|
76
|
+
// 手输校验
|
|
83
77
|
if (!isValidKeyFormat(inputKey)) return invalid();
|
|
84
78
|
```
|
|
85
79
|
|
|
86
|
-
##
|
|
80
|
+
## check vs verify
|
|
87
81
|
|
|
88
|
-
| |
|
|
82
|
+
| | `checkKey` | `client.verifyKey` |
|
|
89
83
|
|---|---|---|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
| 自托管 | ✅ 自己的 D1 | ❌ SaaS |
|
|
96
|
-
| 原子扣减 | ✅ `UPDATE ... WHERE credits>0 RETURNING` | ✅ |
|
|
84
|
+
| 鉴权 | 公开(无需 rootKey) | 管理(需 rootKey) |
|
|
85
|
+
| 扣减 | ❌ 不扣 | ✅ 原子扣减 1 |
|
|
86
|
+
| autoDelete | ❌ 不删 | ✅ 扣到 0 按 key.autoDelete 删 |
|
|
87
|
+
| event | `check` | `verify`(无限 key 写 `check`) |
|
|
88
|
+
| 场景 | 探活 / 前端校验 / 只验证不扣减 | 真正消费额度 |
|
|
97
89
|
|
|
98
|
-
|
|
90
|
+
**`autoDelete`(createKey 预设,verify 执行)**:`true` 立即 soft delete;`"3m"`/`"2h"`/`"3d"` 延迟(set deleteAt,Cron 每 10min 清理);不传 = 不删(无限/长期 key)。
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
|---|---|---|
|
|
102
|
-
| `decrement` | true | true 扣减 + 写 verify log;false 只查 |
|
|
103
|
-
| `autoDelete` | false | 扣减后 remaining=0 自动删:`true` 立即删;`"3m"`/`"2h"`/`"3d"` 延迟删(set deleteAt,Cron 每 10min 清理过期 key,保留记录供 admin 查看一段时间) |
|
|
104
|
-
| `context` | - | 写入 usage_log({username, ip, source, ...}) |
|
|
92
|
+
**返回 `code`**(valid=false 时):`RATE_LIMITED`(429) / `KEY_PAUSED`、`KEY_BANNED`(423) / `KEY_DISABLED` / `KEY_EXPIRED` / `KEY_EXHAUSTED`。
|
|
105
93
|
|
|
106
|
-
**SDK
|
|
94
|
+
**SDK 对 429/423 不 throw**,返回 body 让调用方读:
|
|
95
|
+
```ts
|
|
96
|
+
const r = await client.verifyKey({ key });
|
|
97
|
+
if (r.ratelimited || r.code === "RATE_LIMITED") return res.status(429).json({ error: "请求过于频繁" });
|
|
98
|
+
if (r.code === "KEY_PAUSED" || r.code === "KEY_BANNED") return res.status(423).json({ error: "Key 已暂停/封禁" });
|
|
99
|
+
if (!r.valid) return res.status(403).json({ error: "Key 无效" });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 特性(区别于 Unkey)
|
|
103
|
+
|
|
104
|
+
| | unified-keys | Unkey |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| 明文显示 | ✅ 列表/详情/反查返回完整明文 | ❌ 只给前缀 |
|
|
107
|
+
| 使用记录 | ✅ 每次 check/verify/refund/create/soft_delete/... 写 log | ❌ 只给 remaining |
|
|
108
|
+
| check/verify 拆分 | ✅ 公开只读 vs 管理扣减 | ❌ 单端点 |
|
|
109
|
+
| 用尽自动删 | ✅ autoDelete(createKey 预设) | ❌ |
|
|
110
|
+
| 永久痕迹 | ✅ soft delete 反查 | ❌ 真删丢痕迹 |
|
|
111
|
+
| 限流 / 自动封禁 | ✅ DO 滑动窗口 / 渐进式·直接 | ❌ |
|
|
112
|
+
| 前置格式校验 | ✅ SDK isValidKeyFormat | ❌ |
|
|
113
|
+
|
|
114
|
+
## 自动封禁(autoBan)
|
|
115
|
+
|
|
116
|
+
**渐进式**(默认):违规累计达 maxViolations 永久 ban,前 N-1 次 pause。
|
|
117
|
+
```jsonc
|
|
118
|
+
{ "threshold": 10, "windowSec": 600, "maxViolations": 3, "durationSec": 3600 }
|
|
119
|
+
```
|
|
120
|
+
**直接**(`mode: "direct"`):单次达阈值即 action。
|
|
121
|
+
```jsonc
|
|
122
|
+
{ "mode": "direct", "action": "ban", "threshold": 10, "windowSec": 600 }
|
|
123
|
+
```
|
|
124
|
+
`unban` 重置违规计数;`unpause` 不重置。
|
|
107
125
|
|
|
108
126
|
## event 类型(usage_log)
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
- `refund`:updateCredits increment 恢复额度
|
|
112
|
-
- `create` / `delete`(含 autoDelete 触发的 exhausted_autodelete)/ `update` / `disable`
|
|
128
|
+
`check`(只读探活 / verify 遇无限 key)/ `verify`(扣减)/ `refund` / `create` / `soft_delete`(含 autoDelete 触发)/ `rate_limited` / `auto_pause` / `auto_ban` / `pause` / `unpause` / `ban` / `unban` / `update`。
|
|
113
129
|
|
|
114
130
|
## 集成示例
|
|
115
131
|
|
|
116
|
-
### Next.js Route Handler
|
|
132
|
+
### Next.js Route Handler(扣减,client 带 rootKey)
|
|
117
133
|
```ts
|
|
118
|
-
import {
|
|
134
|
+
import { createClient } from "unified-keys";
|
|
135
|
+
const client = createClient({ appId: "my-app", rootKey: process.env.KEYS_ROOT_KEY });
|
|
119
136
|
export async function POST(req: Request) {
|
|
120
137
|
const { key } = await req.json();
|
|
121
|
-
const r = await verifyKey({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
context: { ip: req.headers.get("x-forwarded-for") },
|
|
126
|
-
});
|
|
127
|
-
if (!r.valid) return Response.json({ error: "invalid key" }, { status: 403 });
|
|
138
|
+
const r = await client.verifyKey({ key, context: { ip: req.headers.get("x-forwarded-for") } });
|
|
139
|
+
if (r.ratelimited || r.code === "RATE_LIMITED") return Response.json({ error: "请求过于频繁" }, { status: 429 });
|
|
140
|
+
if (r.code === "KEY_PAUSED" || r.code === "KEY_BANNED") return Response.json({ error: "Key 已暂停/封禁" }, { status: 423 });
|
|
141
|
+
if (!r.valid) return Response.json({ error: "Key 无效或已使用" }, { status: 403 });
|
|
128
142
|
// ... 授权逻辑
|
|
129
143
|
}
|
|
130
144
|
```
|
|
131
145
|
|
|
132
|
-
###
|
|
146
|
+
### 公开只读校验(探活 / whoami,无需 rootKey)
|
|
133
147
|
```ts
|
|
134
|
-
import {
|
|
135
|
-
const
|
|
136
|
-
if (!info.found || !info.valid) notFound();
|
|
137
|
-
// info.remaining / info.meta / info.expiresAt
|
|
148
|
+
import { checkKey } from "unified-keys";
|
|
149
|
+
const r = await checkKey({ key }); // 不扣减,前端可直调
|
|
138
150
|
```
|
|
139
151
|
|
|
140
|
-
###
|
|
152
|
+
### admin 反查(明文 key → 注册用户)
|
|
141
153
|
```ts
|
|
142
|
-
|
|
143
|
-
const client = createClient({ rootKey: c.env.KEY_SERVICE_ROOT_KEY });
|
|
144
|
-
await client.createKey({ appId: "my-app", credits: 1 });
|
|
154
|
+
const { found, key, recentLogs } = await client.lookupKey({ plaintext: "sk_xxx" });
|
|
145
155
|
```
|
|
146
156
|
|
|
147
|
-
##
|
|
148
|
-
|
|
149
|
-
- **公开 verify**:`POST https://keys.932324.xyz/v1/keys/verify`(无需鉴权,任何持有 key 的人可调)
|
|
150
|
-
- **管理 API**:`https://keys.932324.xyz/v1/*`(需 `Authorization: Bearer <ROOT_KEY>`)
|
|
151
|
-
- 健康检查:`GET https://keys.932324.xyz/v1/health`
|
|
157
|
+
## 注意事项
|
|
152
158
|
|
|
153
|
-
|
|
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。
|
|
162
|
+
- **soft delete 不丢痕迹**:deleteKey/autoDelete 只 set `_delete=true`,明文 + meta + usage_logs 永久保留,lookup 可反查。
|
|
163
|
+
- **D1 datetime 拼接**:服务端 `datetime('now', '+N seconds')` 必须用 `sql.raw(String(N))`,不能 `${N}` 参数化。
|
|
154
164
|
|
|
155
|
-
##
|
|
165
|
+
## 实现位置(项目内)
|
|
156
166
|
|
|
157
|
-
- 服务端:`apps/key-service`(Hono Worker
|
|
158
|
-
- SDK:`packages/keys`(npm `unified-keys`)
|
|
159
|
-
- schema:`packages/db/src/schemas/key-service.ts
|
|
160
|
-
-
|
|
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
package/src/client.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
BanInput,
|
|
2
3
|
BatchDeleteResult,
|
|
4
|
+
CheckInput,
|
|
5
|
+
CheckResult,
|
|
3
6
|
CreateAppInput,
|
|
4
7
|
CreateAppResult,
|
|
5
8
|
CreateKeyInput,
|
|
@@ -12,6 +15,10 @@ import type {
|
|
|
12
15
|
ListAppsResult,
|
|
13
16
|
ListKeysInput,
|
|
14
17
|
ListKeysResult,
|
|
18
|
+
LookupInput,
|
|
19
|
+
LookupResult,
|
|
20
|
+
PauseInput,
|
|
21
|
+
PauseResult,
|
|
15
22
|
UpdateCreditsInput,
|
|
16
23
|
UpdateCreditsResult,
|
|
17
24
|
UpdateKeyInput,
|
|
@@ -33,8 +40,10 @@ export function isValidKeyFormat(key: string): boolean {
|
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
|
|
43
|
+
const { appId, rootKey } = config;
|
|
36
44
|
const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
37
|
-
|
|
45
|
+
if (!appId) throw new Error("unified-keys: appId is required");
|
|
46
|
+
if (!rootKey) throw new Error("unified-keys: rootKey is required");
|
|
38
47
|
|
|
39
48
|
async function request<T>(
|
|
40
49
|
method: string,
|
|
@@ -65,7 +74,8 @@ export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
|
|
|
65
74
|
`unified-keys ${method} ${path}: ${res.status} ${text.slice(0, 200)}`
|
|
66
75
|
);
|
|
67
76
|
}
|
|
68
|
-
|
|
77
|
+
// 429/423 是 verify 端点的业务码(限流/封禁,body 含 valid/code),返回 body 让调用方读,不 throw
|
|
78
|
+
if (!res.ok && res.status !== 429 && res.status !== 423) {
|
|
69
79
|
const msg = (data as { error?: string }).error ?? `${res.status}`;
|
|
70
80
|
throw new Error(`unified-keys ${method} ${path}: ${msg}`);
|
|
71
81
|
}
|
|
@@ -77,9 +87,11 @@ export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
|
|
|
77
87
|
request<CreateAppResult>("POST", "/v1/apps", { body: input }),
|
|
78
88
|
listApps: () => request<ListAppsResult>("GET", "/v1/apps"),
|
|
79
89
|
createKey: (input: CreateKeyInput) =>
|
|
80
|
-
request<CreateKeyResult>("POST", "/v1/keys", {
|
|
90
|
+
request<CreateKeyResult>("POST", "/v1/keys", {
|
|
91
|
+
body: { ...input, appId },
|
|
92
|
+
}),
|
|
81
93
|
listKeys: (input: ListKeysInput) => {
|
|
82
|
-
const params = new URLSearchParams({ appId
|
|
94
|
+
const params = new URLSearchParams({ appId });
|
|
83
95
|
if (input.limit != null) params.set("limit", String(input.limit));
|
|
84
96
|
if (input.offset != null) params.set("offset", String(input.offset));
|
|
85
97
|
return request<ListKeysResult>("GET", `/v1/keys?${params}`);
|
|
@@ -107,26 +119,50 @@ export function createClient(config: KeyServiceClientConfig): KeyServiceClient {
|
|
|
107
119
|
`/v1/keys/${keyId}/usage-logs?limit=${limit}`
|
|
108
120
|
);
|
|
109
121
|
},
|
|
122
|
+
checkKey: (input: CheckInput) => {
|
|
123
|
+
if (!isValidKeyFormat(input.key)) {
|
|
124
|
+
return Promise.resolve({ valid: false, found: false } as CheckResult);
|
|
125
|
+
}
|
|
126
|
+
return request<CheckResult>("POST", "/v1/keys/check", {
|
|
127
|
+
body: input,
|
|
128
|
+
auth: false,
|
|
129
|
+
});
|
|
130
|
+
},
|
|
110
131
|
verifyKey: (input: VerifyInput) => {
|
|
111
132
|
if (!isValidKeyFormat(input.key)) {
|
|
112
133
|
return Promise.resolve({ valid: false, found: false } as VerifyResult);
|
|
113
134
|
}
|
|
135
|
+
// verify 改为管理端点(需 rootKey,扣减 + autoDelete 读 key 属性)
|
|
114
136
|
return request<VerifyResult>("POST", "/v1/keys/verify", {
|
|
115
137
|
body: input,
|
|
116
|
-
auth:
|
|
138
|
+
auth: true,
|
|
117
139
|
});
|
|
118
140
|
},
|
|
141
|
+
lookupKey: (input: LookupInput) =>
|
|
142
|
+
request<LookupResult>("POST", "/v1/keys/lookup", { body: input }),
|
|
143
|
+
pauseKey: (keyId: string, input?: PauseInput) =>
|
|
144
|
+
request<PauseResult>("POST", `/v1/keys/${keyId}/pause`, {
|
|
145
|
+
body: input ?? {},
|
|
146
|
+
}),
|
|
147
|
+
unpauseKey: (keyId: string) =>
|
|
148
|
+
request<DeleteResult>("POST", `/v1/keys/${keyId}/unpause`, { body: {} }),
|
|
149
|
+
banKey: (keyId: string, input?: BanInput) =>
|
|
150
|
+
request<DeleteResult>("POST", `/v1/keys/${keyId}/ban`, {
|
|
151
|
+
body: input ?? {},
|
|
152
|
+
}),
|
|
153
|
+
unbanKey: (keyId: string) =>
|
|
154
|
+
request<DeleteResult>("POST", `/v1/keys/${keyId}/unban`, { body: {} }),
|
|
119
155
|
};
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
/**
|
|
123
|
-
* 公开
|
|
124
|
-
*
|
|
159
|
+
* 公开 check 端点(只读校验),无需 rootKey,baseUrl 内置默认。
|
|
160
|
+
* 不扣减、不删;适合「探活 / 前端校验 key 有效性 / 只验证不扣减」场景。
|
|
125
161
|
*/
|
|
126
|
-
export async function
|
|
162
|
+
export async function checkKey(input: CheckInput): Promise<CheckResult> {
|
|
127
163
|
if (!isValidKeyFormat(input.key)) return { valid: false, found: false };
|
|
128
164
|
const baseUrl = DEFAULT_BASE_URL;
|
|
129
|
-
const res = await fetch(`${baseUrl}/v1/keys/
|
|
165
|
+
const res = await fetch(`${baseUrl}/v1/keys/check`, {
|
|
130
166
|
method: "POST",
|
|
131
167
|
headers: { "Content-Type": "application/json" },
|
|
132
168
|
body: JSON.stringify(input),
|
|
@@ -136,11 +172,20 @@ export async function verifyKey(input: VerifyInput): Promise<VerifyResult> {
|
|
|
136
172
|
try {
|
|
137
173
|
data = text ? JSON.parse(text) : {};
|
|
138
174
|
} catch {
|
|
139
|
-
throw new Error(`unified-keys
|
|
175
|
+
throw new Error(`unified-keys check: ${res.status} ${text.slice(0, 200)}`);
|
|
140
176
|
}
|
|
141
|
-
if (!res.ok) {
|
|
177
|
+
if (!res.ok && res.status !== 429 && res.status !== 423) {
|
|
142
178
|
const msg = (data as { error?: string }).error ?? `${res.status}`;
|
|
143
|
-
throw new Error(`unified-keys
|
|
179
|
+
throw new Error(`unified-keys check: ${msg}`);
|
|
144
180
|
}
|
|
145
|
-
return data as
|
|
181
|
+
return data as CheckResult;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @deprecated 0.6.0 起 verify 改为管理端点(需 rootKey),公开场景请用 `checkKey`(只读)。
|
|
186
|
+
* 此函数保留向后兼容,内部改为调公开 check 端点(行为:只读校验,不扣减)。
|
|
187
|
+
* 扣减请用 `client.verifyKey`(`createClient({ rootKey })`)。
|
|
188
|
+
*/
|
|
189
|
+
export async function verifyKey(input: VerifyInput): Promise<VerifyResult> {
|
|
190
|
+
return checkKey(input);
|
|
146
191
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
export { createClient, isValidKeyFormat, verifyKey } from "./client";
|
|
1
|
+
export { checkKey, createClient, isValidKeyFormat, verifyKey } from "./client";
|
|
2
2
|
export type {
|
|
3
3
|
AppRecord,
|
|
4
|
+
AutoBanConfig,
|
|
5
|
+
BanInput,
|
|
4
6
|
BatchDeleteResult,
|
|
7
|
+
CheckInput,
|
|
8
|
+
CheckResult,
|
|
5
9
|
CreateAppInput,
|
|
6
10
|
CreateAppResult,
|
|
7
11
|
CreatedKey,
|
|
@@ -16,11 +20,17 @@ export type {
|
|
|
16
20
|
ListAppsResult,
|
|
17
21
|
ListKeysInput,
|
|
18
22
|
ListKeysResult,
|
|
23
|
+
LookupInput,
|
|
24
|
+
LookupResult,
|
|
25
|
+
PauseInput,
|
|
26
|
+
PauseResult,
|
|
27
|
+
RateLimitConfig,
|
|
19
28
|
UpdateCreditsInput,
|
|
20
29
|
UpdateCreditsResult,
|
|
21
30
|
UpdateKeyInput,
|
|
22
31
|
UsageLog,
|
|
23
32
|
UsageLogsResult,
|
|
33
|
+
VerifyErrorCode,
|
|
24
34
|
VerifyInput,
|
|
25
35
|
VerifyResult,
|
|
26
36
|
} from "./types";
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/** unified-keys SDK 类型定义 */
|
|
2
2
|
|
|
3
3
|
export interface KeyServiceClientConfig {
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/** 应用命名空间 slug(createKey/listKeys 自动绑定,无需每次传) */
|
|
5
|
+
appId: string;
|
|
6
|
+
/** 服务端点(内置默认,仅在指向自建/测试实例时传) */
|
|
7
7
|
baseUrl?: string;
|
|
8
|
-
/** 管理 API
|
|
9
|
-
rootKey
|
|
8
|
+
/** 管理 API + verify 扣减都需要(Bearer token) */
|
|
9
|
+
rootKey: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface AppRecord {
|
|
@@ -27,8 +27,38 @@ export interface ListAppsResult {
|
|
|
27
27
|
apps: AppRecord[];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** 限流配置(max 配置后启用 RateLimiterDO 滑动窗口限流)*/
|
|
31
|
+
export interface RateLimitConfig {
|
|
32
|
+
/** 窗口内最大请求数 */
|
|
33
|
+
max: number;
|
|
34
|
+
/** 窗口秒数,默认 60 */
|
|
35
|
+
windowSec?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 自动封禁配置(meta.autoBan)*/
|
|
39
|
+
export interface AutoBanConfig {
|
|
40
|
+
/** direct: 单次触发动作(pause/ban,默认 pause)*/
|
|
41
|
+
action?: "pause" | "ban";
|
|
42
|
+
/** pause 持续秒数,默认 3600(1h)*/
|
|
43
|
+
durationSec?: number;
|
|
44
|
+
/** progressive: 违规达此数 → 永久 ban(默认 3);前 N-1 次 pause */
|
|
45
|
+
maxViolations?: number;
|
|
46
|
+
/** 模式:progressive(渐进式,默认)/ direct(直接,单次达阈值即 action)*/
|
|
47
|
+
mode?: "progressive" | "direct";
|
|
48
|
+
/** 触发阈值(窗口内 rate_limited 次数 = 一次违规)*/
|
|
49
|
+
threshold: number;
|
|
50
|
+
/** 统计窗口秒数,默认 600(10min)*/
|
|
51
|
+
windowSec?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
export interface CreateKeyInput {
|
|
31
|
-
|
|
55
|
+
/** 自动封禁配置(存 meta.autoBan)*/
|
|
56
|
+
autoBan?: AutoBanConfig;
|
|
57
|
+
/**
|
|
58
|
+
* autoDelete(createKey 时预设为 key 属性,verify 扣减到 0 时执行;verify 端点不再接受调用方传参):
|
|
59
|
+
* true / "now" → 立即 soft delete;"3m" / "2h" / "3d" → set deleteAt,Cron 软删(保留记录供 admin 查一段时间)
|
|
60
|
+
*/
|
|
61
|
+
autoDelete?: boolean | string;
|
|
32
62
|
/** 批量创建数量,默认 1 */
|
|
33
63
|
count?: number;
|
|
34
64
|
/** 初始额度(剩余次数),默认 1 */
|
|
@@ -41,10 +71,16 @@ export interface CreateKeyInput {
|
|
|
41
71
|
length?: number;
|
|
42
72
|
/** 总额度(0=无限),默认等于 credits */
|
|
43
73
|
maxUses?: number;
|
|
44
|
-
/**
|
|
74
|
+
/** 应用层任意元数据(count 个 key 共享) */
|
|
45
75
|
meta?: Record<string, unknown>;
|
|
76
|
+
/**
|
|
77
|
+
* 批量创建时每个 key 的独立 meta(长度须 = count);不传则 count 个 key 共享 `meta`。
|
|
78
|
+
*/
|
|
79
|
+
metas?: Record<string, unknown>[];
|
|
46
80
|
/** 可选前缀,生成 `<prefix>_<rand>`;不传则纯 random */
|
|
47
81
|
prefix?: string;
|
|
82
|
+
/** 限流配置(max 配置后启用滑动窗口限流)*/
|
|
83
|
+
rateLimit?: RateLimitConfig;
|
|
48
84
|
}
|
|
49
85
|
|
|
50
86
|
export interface CreatedKey {
|
|
@@ -59,21 +95,29 @@ export interface CreateKeyResult {
|
|
|
59
95
|
|
|
60
96
|
export interface KeyRecord {
|
|
61
97
|
appId: string;
|
|
98
|
+
/** autoDelete 策略(createKey 预设,verify 只读):null=不删 | "true"=用尽立即软删 | "3m"/"2h"/"3d"=延迟软删 */
|
|
99
|
+
autoDelete: string | null;
|
|
100
|
+
banReason: string | null;
|
|
101
|
+
banStatus: "active" | "paused" | "banned";
|
|
62
102
|
createdAt: string;
|
|
63
103
|
creditsRemaining: number;
|
|
104
|
+
/** soft delete 标志(true=已软删,verify/list 过滤但 admin 可查)*/
|
|
105
|
+
deleted: boolean;
|
|
64
106
|
enabled: boolean;
|
|
65
107
|
expiresAt: string | null;
|
|
66
108
|
externalId: string | null;
|
|
67
109
|
keyId: string;
|
|
68
110
|
maxUses: number;
|
|
69
111
|
meta: Record<string, unknown> | null;
|
|
112
|
+
pausedUntil: string | null;
|
|
70
113
|
plaintext: string;
|
|
71
114
|
prefix: string | null;
|
|
115
|
+
rateLimitMax: number | null;
|
|
116
|
+
rateLimitWindowSec: number | null;
|
|
72
117
|
updatedAt: string;
|
|
73
118
|
}
|
|
74
119
|
|
|
75
120
|
export interface ListKeysInput {
|
|
76
|
-
appId: string;
|
|
77
121
|
limit?: number;
|
|
78
122
|
offset?: number;
|
|
79
123
|
}
|
|
@@ -106,7 +150,7 @@ export interface UsageLog {
|
|
|
106
150
|
createdAt: string;
|
|
107
151
|
creditsAfter: number | null;
|
|
108
152
|
creditsBefore: number | null;
|
|
109
|
-
/** verify(扣减) / refund
|
|
153
|
+
/** check(只读) / verify(扣减) / refund / create / soft_delete / rate_limited / auto_pause / auto_ban / pause / unpause / ban / unban / update */
|
|
110
154
|
event: string;
|
|
111
155
|
id: string;
|
|
112
156
|
keyId: string;
|
|
@@ -126,31 +170,52 @@ export interface GetKeyResult {
|
|
|
126
170
|
}
|
|
127
171
|
|
|
128
172
|
export interface VerifyInput {
|
|
129
|
-
/**
|
|
130
|
-
* 扣减后额度用尽(remaining=0)自动删除:true 立即删;
|
|
131
|
-
* "3m"/"2h"/"3d" 等延迟删(set deleteAt,Cron 定期清理,保留记录一段时间)
|
|
132
|
-
*/
|
|
133
|
-
autoDelete?: boolean | string;
|
|
134
173
|
/** 上下文,写入 usage_log(如 {username, source, ip}) */
|
|
135
174
|
context?: Record<string, unknown>;
|
|
136
|
-
/** 默认 true;false 时只查不扣(写 verify log 但不扣减) */
|
|
137
|
-
decrement?: boolean;
|
|
138
175
|
/** 完整明文 key */
|
|
139
176
|
key: string;
|
|
140
177
|
}
|
|
141
178
|
|
|
179
|
+
/** 公开 check(只读校验)入参。check 不扣减、不删,无需 rootKey。 */
|
|
180
|
+
export interface CheckInput {
|
|
181
|
+
/** 上下文,写入 usage_log */
|
|
182
|
+
context?: Record<string, unknown>;
|
|
183
|
+
/** 完整明文 key */
|
|
184
|
+
key: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** check 结果(结构与 VerifyResult 相同)。 */
|
|
188
|
+
export type CheckResult = VerifyResult;
|
|
189
|
+
|
|
190
|
+
/** verify 失败码 */
|
|
191
|
+
export type VerifyErrorCode =
|
|
192
|
+
| "RATE_LIMITED"
|
|
193
|
+
| "KEY_PAUSED"
|
|
194
|
+
| "KEY_BANNED"
|
|
195
|
+
| "KEY_DISABLED"
|
|
196
|
+
| "KEY_EXPIRED"
|
|
197
|
+
| "KEY_EXHAUSTED";
|
|
198
|
+
|
|
142
199
|
export interface VerifyResult {
|
|
143
200
|
appId?: string;
|
|
201
|
+
banStatus?: "active" | "paused" | "banned";
|
|
202
|
+
/** 失败码(valid=false 时)*/
|
|
203
|
+
code?: VerifyErrorCode;
|
|
144
204
|
createdAt?: string;
|
|
145
205
|
enabled?: boolean;
|
|
206
|
+
error?: string;
|
|
146
207
|
expiresAt?: string | null;
|
|
147
208
|
externalId?: string | null;
|
|
148
|
-
/** key
|
|
209
|
+
/** key 是否存在(区别「不存在」与「存在但额度耗尽/封禁」)*/
|
|
149
210
|
found: boolean;
|
|
150
211
|
keyId?: string;
|
|
151
212
|
maxUses?: number;
|
|
152
213
|
meta?: Record<string, unknown> | null;
|
|
214
|
+
/** 是否触发限流(429)*/
|
|
215
|
+
ratelimited?: boolean;
|
|
153
216
|
remaining?: number;
|
|
217
|
+
/** 限流/暂停重置剩余秒数 */
|
|
218
|
+
resetIn?: number;
|
|
154
219
|
valid: boolean;
|
|
155
220
|
}
|
|
156
221
|
|
|
@@ -163,7 +228,37 @@ export interface BatchDeleteResult {
|
|
|
163
228
|
failed: string[];
|
|
164
229
|
}
|
|
165
230
|
|
|
231
|
+
// ===== lookup / pause / ban =====
|
|
232
|
+
|
|
233
|
+
export interface LookupInput {
|
|
234
|
+
plaintext: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface LookupResult {
|
|
238
|
+
found: boolean;
|
|
239
|
+
key?: KeyRecord;
|
|
240
|
+
recentLogs?: UsageLog[];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface PauseInput {
|
|
244
|
+
/** 暂停秒数,默认 3600(1h)*/
|
|
245
|
+
durationSec?: number;
|
|
246
|
+
reason?: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface PauseResult {
|
|
250
|
+
pausedUntil: string | null;
|
|
251
|
+
success: boolean;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface BanInput {
|
|
255
|
+
reason?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
166
258
|
export interface KeyServiceClient {
|
|
259
|
+
banKey(keyId: string, input?: BanInput): Promise<DeleteResult>;
|
|
260
|
+
/** 公开 check(只读校验,不扣减不删,无需 rootKey)。 */
|
|
261
|
+
checkKey(input: CheckInput): Promise<CheckResult>;
|
|
167
262
|
createApp(input: CreateAppInput): Promise<CreateAppResult>;
|
|
168
263
|
createKey(input: CreateKeyInput): Promise<CreateKeyResult>;
|
|
169
264
|
deleteKey(keyId: string): Promise<DeleteResult>;
|
|
@@ -175,6 +270,10 @@ export interface KeyServiceClient {
|
|
|
175
270
|
): Promise<UsageLogsResult>;
|
|
176
271
|
listApps(): Promise<ListAppsResult>;
|
|
177
272
|
listKeys(input: ListKeysInput): Promise<ListKeysResult>;
|
|
273
|
+
lookupKey(input: LookupInput): Promise<LookupResult>;
|
|
274
|
+
pauseKey(keyId: string, input?: PauseInput): Promise<PauseResult>;
|
|
275
|
+
unbanKey(keyId: string): Promise<DeleteResult>;
|
|
276
|
+
unpauseKey(keyId: string): Promise<DeleteResult>;
|
|
178
277
|
updateCredits(
|
|
179
278
|
keyId: string,
|
|
180
279
|
input: UpdateCreditsInput
|