@kne/fastify-signature 1.0.0-alpha.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 +292 -0
- package/index.js +45 -0
- package/libs/controllers/main.js +144 -0
- package/libs/models/secret.js +32 -0
- package/libs/services/main.js +105 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
|
|
2
|
+
# fastify-signature
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### 描述
|
|
6
|
+
|
|
7
|
+
用于实现一个签名和验证算法
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### 安装
|
|
11
|
+
|
|
12
|
+
```shell
|
|
13
|
+
npm i --save @kne/fastify-signature
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### 概述
|
|
18
|
+
|
|
19
|
+
@kne/fastify-signature 是一个用于 Fastify 框架的签名验证插件。它提供了一套完整的 API 密钥管理和签名验证系统,帮助开发者轻松实现
|
|
20
|
+
API 的安全访问控制。
|
|
21
|
+
|
|
22
|
+
### 主要功能
|
|
23
|
+
|
|
24
|
+
1. **密钥管理**
|
|
25
|
+
- 创建和管理 API 密钥(appId 和 secretKey)
|
|
26
|
+
- 支持密钥状态管理(启用/禁用)
|
|
27
|
+
- 提供密钥描述和最后访问时间记录
|
|
28
|
+
|
|
29
|
+
2. **签名验证**
|
|
30
|
+
- 实现请求签名验证机制
|
|
31
|
+
- 支持时间戳验证,防止重放攻击
|
|
32
|
+
- 提供灵活的签名算法配置
|
|
33
|
+
|
|
34
|
+
3. **用户关联**
|
|
35
|
+
- 支持将密钥与特定用户关联
|
|
36
|
+
- 提供用户级别的密钥管理
|
|
37
|
+
|
|
38
|
+
### 技术架构
|
|
39
|
+
|
|
40
|
+
项目采用典型的 MVC 架构:
|
|
41
|
+
|
|
42
|
+
- **Models**: 定义数据模型,包括密钥管理相关的数据结构
|
|
43
|
+
- **Services**: 实现核心业务逻辑,包括密钥管理和签名验证
|
|
44
|
+
- **Controllers**: 处理 HTTP 请求,提供 RESTful API 接口
|
|
45
|
+
|
|
46
|
+
### 依赖项
|
|
47
|
+
|
|
48
|
+
- @kne/fastify-namespace: 用于组织和管理代码结构
|
|
49
|
+
- @kne/fastify-sequelize: 提供数据库操作支持
|
|
50
|
+
|
|
51
|
+
### 数据模型
|
|
52
|
+
|
|
53
|
+
#### Secret Model
|
|
54
|
+
|
|
55
|
+
主要字段:
|
|
56
|
+
|
|
57
|
+
- secretKey: 密钥字符串
|
|
58
|
+
- description: 密钥描述
|
|
59
|
+
- lastVisitedAt: 最后访问时间
|
|
60
|
+
- status: 密钥状态(0:开启,1:禁用)
|
|
61
|
+
- userId: 关联用户ID
|
|
62
|
+
|
|
63
|
+
### 使用场景
|
|
64
|
+
|
|
65
|
+
1. API 安全访问控制
|
|
66
|
+
2. 第三方应用接入认证
|
|
67
|
+
3. 微服务间的安全通信
|
|
68
|
+
4. 开放平台 API 管理
|
|
69
|
+
|
|
70
|
+
### 安全特性
|
|
71
|
+
|
|
72
|
+
1. 密钥管理安全
|
|
73
|
+
- 支持密钥状态控制
|
|
74
|
+
- 记录密钥使用情况
|
|
75
|
+
|
|
76
|
+
2. 请求验证安全
|
|
77
|
+
- 时间戳验证
|
|
78
|
+
- 签名算法保护
|
|
79
|
+
- 防重放攻击
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### 示例
|
|
83
|
+
|
|
84
|
+
#### 示例代码
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### API
|
|
89
|
+
|
|
90
|
+
### generateSignature
|
|
91
|
+
|
|
92
|
+
用于生成API请求签名的工具函数。
|
|
93
|
+
|
|
94
|
+
#### 函数签名
|
|
95
|
+
```javascript
|
|
96
|
+
function generateSignature(appId, appSecret, expireInSeconds)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### 参数说明
|
|
100
|
+
|
|
101
|
+
| 参数名 | 类型 | 必填 | 描述 |
|
|
102
|
+
|-------|------|------|------|
|
|
103
|
+
| appId | string/number | 是 | 应用ID |
|
|
104
|
+
| appSecret | string | 是 | 应用密钥 |
|
|
105
|
+
| expireInSeconds | number | 是 | 签名有效期(秒) |
|
|
106
|
+
|
|
107
|
+
#### 返回值
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
{
|
|
111
|
+
appId: string/number, // 应用ID
|
|
112
|
+
timestamp: number, // 当前时间戳(秒)
|
|
113
|
+
expire: number, // 过期时间戳(秒)
|
|
114
|
+
signature: string // 生成的签名
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### 使用示例
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const generateSignature = require('./generateSignature');
|
|
122
|
+
|
|
123
|
+
// 生成一个有效期为1小时的签名
|
|
124
|
+
const result = generateSignature('your-app-id', 'your-app-secret', 3600);
|
|
125
|
+
|
|
126
|
+
console.log(result);
|
|
127
|
+
// 输出:
|
|
128
|
+
// {
|
|
129
|
+
// appId: 'your-app-id',
|
|
130
|
+
// timestamp: 1234567890,
|
|
131
|
+
// expire: 1234571490,
|
|
132
|
+
// signature: 'generated-signature-string'
|
|
133
|
+
// }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 1. 获取密钥列表
|
|
137
|
+
|
|
138
|
+
| 项目 | 说明 |
|
|
139
|
+
|------|------|
|
|
140
|
+
| 接口路径 | `${prefix}/list` |
|
|
141
|
+
| 请求方法 | GET |
|
|
142
|
+
| 权限要求 | 需要管理员认证 |
|
|
143
|
+
|
|
144
|
+
#### 请求参数
|
|
145
|
+
|
|
146
|
+
| 参数名 | 类型 | 必填 | 默认值 | 描述 |
|
|
147
|
+
|--------|------|------|--------|------|
|
|
148
|
+
| currentPage | number | 否 | 1 | 页码 |
|
|
149
|
+
| perPage | number | 否 | 20 | 每页数量 |
|
|
150
|
+
|
|
151
|
+
#### 响应格式
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"pageData": [
|
|
156
|
+
{
|
|
157
|
+
"id": "number",
|
|
158
|
+
"appId": "number",
|
|
159
|
+
"secretKey": "string (部分隐藏)",
|
|
160
|
+
"description": "string",
|
|
161
|
+
"lastVisitedAt": "datetime",
|
|
162
|
+
"status": "number",
|
|
163
|
+
"createdAt": "datetime",
|
|
164
|
+
"updatedAt": "datetime"
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"totalCount": "number"
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 2. 创建密钥
|
|
172
|
+
|
|
173
|
+
| 项目 | 说明 |
|
|
174
|
+
|------|------|
|
|
175
|
+
| 接口路径 | `${prefix}/create` |
|
|
176
|
+
| 请求方法 | POST |
|
|
177
|
+
| 权限要求 | 需要管理员认证 |
|
|
178
|
+
|
|
179
|
+
#### 请求参数
|
|
180
|
+
|
|
181
|
+
| 参数名 | 类型 | 必填 | 描述 |
|
|
182
|
+
|--------|------|------|------|
|
|
183
|
+
| description | string | 否 | 密钥描述 |
|
|
184
|
+
|
|
185
|
+
#### 响应格式
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"appId": "number",
|
|
190
|
+
"secretKey": "string"
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 3. 删除密钥
|
|
195
|
+
|
|
196
|
+
| 项目 | 说明 |
|
|
197
|
+
|------|------|
|
|
198
|
+
| 接口路径 | `${prefix}/remove` |
|
|
199
|
+
| 请求方法 | POST |
|
|
200
|
+
| 权限要求 | 需要管理员认证 |
|
|
201
|
+
|
|
202
|
+
#### 请求参数
|
|
203
|
+
|
|
204
|
+
| 参数名 | 类型 | 必填 | 描述 |
|
|
205
|
+
|--------|------|------|------|
|
|
206
|
+
| appId | number | 是 | 密钥ID |
|
|
207
|
+
|
|
208
|
+
#### 响应格式
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 4. 更新密钥
|
|
215
|
+
|
|
216
|
+
| 项目 | 说明 |
|
|
217
|
+
|------|------|
|
|
218
|
+
| 接口路径 | `${prefix}/update` |
|
|
219
|
+
| 请求方法 | POST |
|
|
220
|
+
| 权限要求 | 需要管理员认证 |
|
|
221
|
+
|
|
222
|
+
#### 请求参数
|
|
223
|
+
|
|
224
|
+
| 参数名 | 类型 | 必填 | 描述 |
|
|
225
|
+
|--------|------|------|------|
|
|
226
|
+
| appId | number | 是 | 密钥ID |
|
|
227
|
+
| status | number | 否 | 密钥状态 (0: 启用, 1: 禁用) |
|
|
228
|
+
| description | string | 否 | 密钥描述 |
|
|
229
|
+
|
|
230
|
+
#### 响应格式
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 5. 验证密钥
|
|
237
|
+
|
|
238
|
+
| 项目 | 说明 |
|
|
239
|
+
|------|------|
|
|
240
|
+
| 接口路径 | `${prefix}/verify` |
|
|
241
|
+
| 请求方法 | POST |
|
|
242
|
+
| 权限要求 | 无需认证 |
|
|
243
|
+
|
|
244
|
+
#### 请求参数
|
|
245
|
+
|
|
246
|
+
| 参数名 | 类型 | 必填 | 描述 |
|
|
247
|
+
|--------|------|------|------|
|
|
248
|
+
| appId | number | 是 | 密钥ID |
|
|
249
|
+
| timestamp | number | 是 | 时间戳 (秒) |
|
|
250
|
+
| signature | string | 是 | 签名 |
|
|
251
|
+
| expire | number | 是 | 过期时间 (秒) |
|
|
252
|
+
|
|
253
|
+
#### 响应格式
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"result": "boolean",
|
|
258
|
+
"errMsg": "string"
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 签名生成说明
|
|
263
|
+
|
|
264
|
+
1. 签名使用 HMAC-SHA256 算法
|
|
265
|
+
2. 签名字符串格式:`${appId}|${timestamp}|${expire}`
|
|
266
|
+
3. 使用 secretKey 作为密钥进行签名
|
|
267
|
+
4. 签名结果使用 hex 编码
|
|
268
|
+
|
|
269
|
+
## 错误处理
|
|
270
|
+
|
|
271
|
+
所有接口在发生错误时会返回以下格式:
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"error": "string",
|
|
276
|
+
"message": "string"
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
常见错误:
|
|
281
|
+
- Invalid appId: 无效的密钥ID
|
|
282
|
+
- Signature expired: 签名已过期
|
|
283
|
+
- Invalid signature: 签名验证失败
|
|
284
|
+
|
|
285
|
+
## 注意事项
|
|
286
|
+
|
|
287
|
+
1. 所有需要管理员认证的接口必须在请求头中包含有效的认证信息
|
|
288
|
+
2. 时间戳和过期时间使用 Unix 时间戳(秒)
|
|
289
|
+
3. 密钥状态:0 表示启用,1 表示禁用
|
|
290
|
+
4. 列表接口返回的 secretKey 会部分隐藏,只显示前3位和后3位
|
|
291
|
+
5. 使用 generateSignature 函数生成签名时,确保传入正确的参数类型和有效期
|
|
292
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const fp = require('fastify-plugin');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
module.exports = fp(async (fastify, options) => {
|
|
5
|
+
options = Object.assign(
|
|
6
|
+
{},
|
|
7
|
+
{
|
|
8
|
+
name: 'signature',
|
|
9
|
+
defaultExpire: 3 * 60,
|
|
10
|
+
secretLength: 32,
|
|
11
|
+
prefix: '/api/signature',
|
|
12
|
+
dbTableNamePrefix: 't_signature_',
|
|
13
|
+
getAdminAuthenticate: () => {
|
|
14
|
+
if (!fastify.account) {
|
|
15
|
+
throw new Error('fastify-account plugin must be registered before fastify-trtc-conference,or set options.getUserAuthenticate');
|
|
16
|
+
}
|
|
17
|
+
return fastify.account.authenticate.admin;
|
|
18
|
+
},
|
|
19
|
+
getUserInfo: request => {
|
|
20
|
+
return request.userInfo;
|
|
21
|
+
},
|
|
22
|
+
getUserModel: () => {
|
|
23
|
+
if (!fastify.account) {
|
|
24
|
+
throw new Error('fastify-account plugin must be registered before fastify-trtc-conference,or set options.getUserModel');
|
|
25
|
+
}
|
|
26
|
+
return fastify.account.models.user;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
options
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
fastify.register(require('@kne/fastify-namespace'), {
|
|
33
|
+
name: options.name,
|
|
34
|
+
options,
|
|
35
|
+
modules: [
|
|
36
|
+
[
|
|
37
|
+
'models',
|
|
38
|
+
await fastify.sequelize.addModels(path.resolve(__dirname, './libs/models'), {
|
|
39
|
+
prefix: options.dbTableNamePrefix
|
|
40
|
+
})
|
|
41
|
+
],
|
|
42
|
+
['services', path.resolve(__dirname, './libs/services')]
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const fp = require('fastify-plugin');
|
|
2
|
+
|
|
3
|
+
module.exports = fp(async (fastify, options) => {
|
|
4
|
+
const { services } = fastify[options.name];
|
|
5
|
+
const adminAuth = options.getAdminAuthenticate();
|
|
6
|
+
fastify.get(
|
|
7
|
+
`${options.prefix}/list`,
|
|
8
|
+
{
|
|
9
|
+
onRequest: [adminAuth],
|
|
10
|
+
schema: {
|
|
11
|
+
description: '获取密钥列表',
|
|
12
|
+
summary: '获取密钥列表',
|
|
13
|
+
query: {
|
|
14
|
+
currentPage: {
|
|
15
|
+
type: 'number',
|
|
16
|
+
description: '页码',
|
|
17
|
+
default: 1
|
|
18
|
+
},
|
|
19
|
+
perPage: {
|
|
20
|
+
type: 'number',
|
|
21
|
+
description: '每页数量',
|
|
22
|
+
default: 20
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
async request => {
|
|
28
|
+
return services.list(options.getUserInfo(request), request.query);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
fastify.post(
|
|
33
|
+
`${options.prefix}/create`,
|
|
34
|
+
{
|
|
35
|
+
onRequest: [adminAuth],
|
|
36
|
+
schema: {
|
|
37
|
+
description: '创建密钥',
|
|
38
|
+
summary: '创建密钥',
|
|
39
|
+
body: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
description: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: '描述'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
async request => {
|
|
51
|
+
return services.create(options.getUserInfo(request), request.body);
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
fastify.post(
|
|
56
|
+
`${options.prefix}/remove`,
|
|
57
|
+
{
|
|
58
|
+
onRequest: [adminAuth],
|
|
59
|
+
schema: {
|
|
60
|
+
description: '删除密钥',
|
|
61
|
+
summary: '删除密钥',
|
|
62
|
+
body: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
appId: {
|
|
66
|
+
type: 'number',
|
|
67
|
+
description: '密钥ID'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
async request => {
|
|
74
|
+
await services.remove(options.getUserInfo(request), request.body);
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
fastify.post(
|
|
80
|
+
`${options.prefix}/update`,
|
|
81
|
+
{
|
|
82
|
+
onRequest: [adminAuth],
|
|
83
|
+
schema: {
|
|
84
|
+
description: '更新密钥',
|
|
85
|
+
summary: '更新密钥',
|
|
86
|
+
body: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
appId: {
|
|
90
|
+
type: 'number',
|
|
91
|
+
description: '密钥ID'
|
|
92
|
+
},
|
|
93
|
+
status: {
|
|
94
|
+
type: 'number',
|
|
95
|
+
description: '密钥状态'
|
|
96
|
+
},
|
|
97
|
+
description: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: '描述'
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
async request => {
|
|
106
|
+
await services.update(options.getUserInfo(request), request.body);
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
fastify.post(
|
|
112
|
+
`${options.prefix}/verify`,
|
|
113
|
+
{
|
|
114
|
+
schema: {
|
|
115
|
+
description: '验证密钥',
|
|
116
|
+
summary: '验证密钥',
|
|
117
|
+
body: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
appId: {
|
|
121
|
+
type: 'number',
|
|
122
|
+
description: '密钥ID'
|
|
123
|
+
},
|
|
124
|
+
timestamp: {
|
|
125
|
+
type: 'number',
|
|
126
|
+
description: '时间戳'
|
|
127
|
+
},
|
|
128
|
+
signature: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
description: '签名'
|
|
131
|
+
},
|
|
132
|
+
expire: {
|
|
133
|
+
type: 'number',
|
|
134
|
+
description: '过期时间'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
async request => {
|
|
141
|
+
return services.verify(request.body);
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module.exports = ({ DataTypes, options }) => {
|
|
2
|
+
return {
|
|
3
|
+
model: {
|
|
4
|
+
secretKey: {
|
|
5
|
+
type: DataTypes.STRING,
|
|
6
|
+
allowNull: false,
|
|
7
|
+
comment: '密钥'
|
|
8
|
+
},
|
|
9
|
+
description: {
|
|
10
|
+
type: DataTypes.STRING,
|
|
11
|
+
comment: '描述'
|
|
12
|
+
},
|
|
13
|
+
lastVisitedAt: {
|
|
14
|
+
type: DataTypes.DATE,
|
|
15
|
+
comment: '最后访问时间'
|
|
16
|
+
},
|
|
17
|
+
status: {
|
|
18
|
+
type: DataTypes.INTEGER,
|
|
19
|
+
defaultValue: 0,
|
|
20
|
+
comment: '状态: 0开启,1禁用'
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
associate: ({ secret }) => {
|
|
24
|
+
secret.belongsTo(options.getUserModel(), {
|
|
25
|
+
foreignKey: 'userId'
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
options: {
|
|
29
|
+
comment: '密钥'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const fp = require('fastify-plugin');
|
|
2
|
+
|
|
3
|
+
module.exports = fp(async (fastify, options) => {
|
|
4
|
+
const { models, services } = [options.name];
|
|
5
|
+
|
|
6
|
+
const add = async (authenticatePayload, { description }) => {
|
|
7
|
+
const { id: userId } = authenticatePayload;
|
|
8
|
+
const secretKey = crypto.randomBytes(options.secretLength).toString('hex');
|
|
9
|
+
const newSecret = await models.secret.create({ secretKey, description, userId });
|
|
10
|
+
return { appId: newSecret.id, secretKey: newSecret.secretKey };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const verify = async ({ appId, timestamp, expire, signature }) => {
|
|
14
|
+
const secret = await models.secret.findByPk(appId);
|
|
15
|
+
if (!secret) {
|
|
16
|
+
throw new Error('Invalid appId');
|
|
17
|
+
}
|
|
18
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
19
|
+
|
|
20
|
+
// 检查是否过期
|
|
21
|
+
if (currentTime > expire) {
|
|
22
|
+
return {
|
|
23
|
+
result: false,
|
|
24
|
+
message: 'Signature expired'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const dataToVerify = `${appId}|${timestamp}|${expire}`;
|
|
29
|
+
const hmac = crypto.createHmac('sha256', secret.secretKey);
|
|
30
|
+
hmac.update(dataToVerify);
|
|
31
|
+
const expectedSignature = hmac.digest('hex');
|
|
32
|
+
|
|
33
|
+
if (expectedSignature !== signature) {
|
|
34
|
+
return {
|
|
35
|
+
result: false,
|
|
36
|
+
message: 'Invalid signature'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await secret.update({ lastVisitedAt: Date.now() });
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
result: true,
|
|
44
|
+
message: 'signature valid'
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const list = async (authenticatePayload, { perPage, currentPage }) => {
|
|
49
|
+
const { id: userId } = authenticatePayload;
|
|
50
|
+
const { count, rows } = await models.secret.findAndCountAll({
|
|
51
|
+
where: {
|
|
52
|
+
userId
|
|
53
|
+
},
|
|
54
|
+
limit: perPage,
|
|
55
|
+
offset: (currentPage - 1) * perPage
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
pageData: rows.map(item => {
|
|
59
|
+
return {
|
|
60
|
+
id: item.id,
|
|
61
|
+
appId: item.id,
|
|
62
|
+
secretKey: item.secretKey.substring(0, 3) + '*'.repeat(item.secretKey.length - 6) + item.secretKey.substring(item.secretKey.length - 3),
|
|
63
|
+
description: item.description,
|
|
64
|
+
lastVisitedAt: item.lastVisitedAt,
|
|
65
|
+
status: item.status,
|
|
66
|
+
createdAt: item.createdAt,
|
|
67
|
+
updatedAt: item.updatedAt
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
70
|
+
totalCount: count
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const remove = async (authenticatePayload, { appId }) => {
|
|
75
|
+
const { id: userId } = authenticatePayload;
|
|
76
|
+
const secret = await models.secret.findByPk(appId);
|
|
77
|
+
if (!secret) {
|
|
78
|
+
throw new Error('Invalid appId');
|
|
79
|
+
}
|
|
80
|
+
if (secret.userId !== userId) {
|
|
81
|
+
throw new Error('Invalid appId');
|
|
82
|
+
}
|
|
83
|
+
await secret.destroy();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const update = async (authenticatePayload, { appId, status, description }) => {
|
|
87
|
+
const { id: userId } = authenticatePayload;
|
|
88
|
+
const secret = await models.secret.findByPk(appId);
|
|
89
|
+
if (!secret) {
|
|
90
|
+
throw new Error('Invalid appId');
|
|
91
|
+
}
|
|
92
|
+
if (secret.userId !== userId) {
|
|
93
|
+
throw new Error('Invalid appId');
|
|
94
|
+
}
|
|
95
|
+
await secret.update({ status, description });
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
Object.assign(fastify[options.name].services, {
|
|
99
|
+
add,
|
|
100
|
+
verify,
|
|
101
|
+
list,
|
|
102
|
+
remove,
|
|
103
|
+
update
|
|
104
|
+
});
|
|
105
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kne/fastify-signature",
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
|
+
"description": "用于实现一个签名和验证算法",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"init": "husky",
|
|
8
|
+
"dev": "nodemon ./index.js",
|
|
9
|
+
"start": "node ./index.js",
|
|
10
|
+
"build:md": "npx @kne/md-doc",
|
|
11
|
+
"start:md": "npx @kne/md-doc --watch",
|
|
12
|
+
"prettier": "prettier --config .prettierrc --write '{libs/**/*,index}.{js,jsx,ts,tsx,json,css,scss}'",
|
|
13
|
+
"lint-staged": "npx lint-staged"
|
|
14
|
+
},
|
|
15
|
+
"lint-staged": {
|
|
16
|
+
"**/*.md": [
|
|
17
|
+
"git add"
|
|
18
|
+
],
|
|
19
|
+
"{libs/**/*,index}.{js,jsx,ts,tsx,json,css,scss}": [
|
|
20
|
+
"prettier --config .prettierrc --write",
|
|
21
|
+
"git add"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"index.js",
|
|
26
|
+
"libs"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/kne-union/fastify-signature.git"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [],
|
|
33
|
+
"author": "linzp",
|
|
34
|
+
"license": "ISC",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/kne-union/fastify-signature/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/kne-union/fastify-signature#readme",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@kne/fastify-namespace": "*",
|
|
41
|
+
"@kne/fastify-sequelize": "*",
|
|
42
|
+
"fastify-plugin": ">=5"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@fastify/env": "^4.4.0",
|
|
46
|
+
"@kne/fastify-sequelize": "^2.0.1",
|
|
47
|
+
"fastify": "^5.3.2",
|
|
48
|
+
"fastify-plugin": "^5.0.1",
|
|
49
|
+
"husky": "^9.0.11",
|
|
50
|
+
"nodemon": "^3.1.3",
|
|
51
|
+
"prettier": "^3.2.5",
|
|
52
|
+
"sqlite3": "^5.1.7"
|
|
53
|
+
}
|
|
54
|
+
}
|