@scorehub/auth-sdk 1.1.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/dist/auth-client.d.ts +152 -0
- package/dist/auth-client.js +297 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/middleware/express.d.ts +19 -0
- package/dist/middleware/express.js +28 -0
- package/dist/middleware/koa.d.ts +29 -0
- package/dist/middleware/koa.js +66 -0
- package/dist/middleware/nestjs.d.ts +24 -0
- package/dist/middleware/nestjs.js +45 -0
- package/package.json +37 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export interface AuthClientOptions {
|
|
2
|
+
/** Auth-Service 地址,如 http://localhost:9100/api/v1 */
|
|
3
|
+
serviceUrl: string;
|
|
4
|
+
/** OAuth Client ID */
|
|
5
|
+
clientId: string;
|
|
6
|
+
/** OAuth Client Secret */
|
|
7
|
+
clientSecret: string;
|
|
8
|
+
/** RS256 公钥 (PEM 格式),用于本地验证 */
|
|
9
|
+
publicKey?: string | Buffer;
|
|
10
|
+
/** JWT issuer (默认 auth-service) */
|
|
11
|
+
issuer?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface TokenPayload {
|
|
14
|
+
sub: string;
|
|
15
|
+
type: 'user' | 'client';
|
|
16
|
+
username?: string;
|
|
17
|
+
roles?: string[];
|
|
18
|
+
clientId?: string;
|
|
19
|
+
scopes: string[];
|
|
20
|
+
jti: string;
|
|
21
|
+
iss: string;
|
|
22
|
+
iat: number;
|
|
23
|
+
exp: number;
|
|
24
|
+
}
|
|
25
|
+
export interface UserInfo {
|
|
26
|
+
sub: string;
|
|
27
|
+
username: string;
|
|
28
|
+
mobile?: string;
|
|
29
|
+
email?: string;
|
|
30
|
+
realname?: string;
|
|
31
|
+
nickname?: string;
|
|
32
|
+
avatar?: string;
|
|
33
|
+
roles: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface TokenPair {
|
|
36
|
+
access_token: string;
|
|
37
|
+
refresh_token: string;
|
|
38
|
+
token_type: string;
|
|
39
|
+
expires_in: number;
|
|
40
|
+
}
|
|
41
|
+
export interface IntrospectResult {
|
|
42
|
+
active: boolean;
|
|
43
|
+
sub?: string;
|
|
44
|
+
type?: string;
|
|
45
|
+
username?: string;
|
|
46
|
+
roles?: string[];
|
|
47
|
+
client_id?: string;
|
|
48
|
+
scope?: string;
|
|
49
|
+
iss?: string;
|
|
50
|
+
exp?: number;
|
|
51
|
+
iat?: number;
|
|
52
|
+
jti?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface LoginResult {
|
|
55
|
+
accessToken: string;
|
|
56
|
+
refreshToken: string;
|
|
57
|
+
expiresIn: number;
|
|
58
|
+
tokenType: string;
|
|
59
|
+
user: {
|
|
60
|
+
id: string;
|
|
61
|
+
username: string;
|
|
62
|
+
mobile: string | null;
|
|
63
|
+
realname: string | null;
|
|
64
|
+
nickname: string | null;
|
|
65
|
+
avatar: string | null;
|
|
66
|
+
roles: string[];
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export interface AuthServiceError {
|
|
70
|
+
code: number;
|
|
71
|
+
message: string;
|
|
72
|
+
}
|
|
73
|
+
export declare class AuthClient {
|
|
74
|
+
private readonly serviceUrl;
|
|
75
|
+
private readonly clientId;
|
|
76
|
+
private readonly clientSecret;
|
|
77
|
+
private readonly publicKey?;
|
|
78
|
+
private readonly issuer;
|
|
79
|
+
private serviceToken?;
|
|
80
|
+
constructor(options: AuthClientOptions);
|
|
81
|
+
/**
|
|
82
|
+
* 本地验证 Access Token (使用 RSA 公钥,零网络延迟)
|
|
83
|
+
*/
|
|
84
|
+
verifyAccessToken(token: string): Promise<TokenPayload>;
|
|
85
|
+
/**
|
|
86
|
+
* 远程 Token 内省 (当没有公钥时使用)
|
|
87
|
+
*/
|
|
88
|
+
introspectToken(token: string): Promise<IntrospectResult>;
|
|
89
|
+
/**
|
|
90
|
+
* 获取服务间调用的 Token (client_credentials)
|
|
91
|
+
*/
|
|
92
|
+
getServiceToken(scopes?: string[]): Promise<string>;
|
|
93
|
+
/**
|
|
94
|
+
* 根据用户 ID 获取用户信息
|
|
95
|
+
*/
|
|
96
|
+
getUserById(userId: string): Promise<UserInfo | null>;
|
|
97
|
+
/**
|
|
98
|
+
* 刷新 Token
|
|
99
|
+
*/
|
|
100
|
+
refreshToken(refreshToken: string): Promise<TokenPair>;
|
|
101
|
+
/**
|
|
102
|
+
* 吊销 Token
|
|
103
|
+
*/
|
|
104
|
+
revokeToken(token: string): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* 账号密码登录
|
|
107
|
+
*/
|
|
108
|
+
loginByPassword(username: string, password: string): Promise<LoginResult>;
|
|
109
|
+
/**
|
|
110
|
+
* 短信验证码登录 (含自动注册)
|
|
111
|
+
*/
|
|
112
|
+
loginBySms(mobile: string, code: string): Promise<LoginResult>;
|
|
113
|
+
/**
|
|
114
|
+
* 校验短信验证码(一次性)
|
|
115
|
+
*/
|
|
116
|
+
verifySmsCode(mobile: string, code: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* 用户注册 (Auth-Service 内部会校验验证码)
|
|
119
|
+
*/
|
|
120
|
+
register(params: {
|
|
121
|
+
username: string;
|
|
122
|
+
password: string;
|
|
123
|
+
mobile: string;
|
|
124
|
+
code: string;
|
|
125
|
+
realname?: string;
|
|
126
|
+
}): Promise<LoginResult>;
|
|
127
|
+
/**
|
|
128
|
+
* 重置密码 (Auth-Service 内部会校验验证码)
|
|
129
|
+
*/
|
|
130
|
+
resetPassword(mobile: string, code: string, newPassword: string): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* 管理端创建用户 (service token 鉴权,无需短信验证码)
|
|
133
|
+
*/
|
|
134
|
+
adminCreateUser(params: {
|
|
135
|
+
username: string;
|
|
136
|
+
password: string;
|
|
137
|
+
mobile: string;
|
|
138
|
+
realname?: string;
|
|
139
|
+
}): Promise<{
|
|
140
|
+
id: string;
|
|
141
|
+
username: string;
|
|
142
|
+
mobile: string;
|
|
143
|
+
}>;
|
|
144
|
+
/**
|
|
145
|
+
* 管理端重置用户密码 (service token 鉴权,无需短信验证码)
|
|
146
|
+
*/
|
|
147
|
+
adminResetPassword(userId: string, newPassword: string): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* 发送短信验证码
|
|
150
|
+
*/
|
|
151
|
+
sendSmsCode(mobile: string, type?: number): Promise<void>;
|
|
152
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AuthClient = void 0;
|
|
37
|
+
const jwt = __importStar(require("jsonwebtoken"));
|
|
38
|
+
class AuthClient {
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.serviceUrl = options.serviceUrl.replace(/\/$/, '');
|
|
41
|
+
this.clientId = options.clientId;
|
|
42
|
+
this.clientSecret = options.clientSecret;
|
|
43
|
+
this.publicKey = options.publicKey;
|
|
44
|
+
this.issuer = options.issuer || 'auth-service';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 本地验证 Access Token (使用 RSA 公钥,零网络延迟)
|
|
48
|
+
*/
|
|
49
|
+
verifyAccessToken(token) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
if (!this.publicKey) {
|
|
52
|
+
reject(new Error('publicKey is required for local token verification'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
jwt.verify(token, this.publicKey, { algorithms: ['RS256'], issuer: this.issuer }, (err, decoded) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
reject(err);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
resolve(decoded);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 远程 Token 内省 (当没有公钥时使用)
|
|
66
|
+
*/
|
|
67
|
+
async introspectToken(token) {
|
|
68
|
+
var _a;
|
|
69
|
+
const response = await fetch(`${this.serviceUrl}/oauth/token/introspect`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
body: JSON.stringify({ token }),
|
|
73
|
+
});
|
|
74
|
+
const result = await response.json();
|
|
75
|
+
// 兼容 ResponseInterceptor 包装格式 { code, data: { active, ... } }
|
|
76
|
+
return ((_a = result.data) !== null && _a !== void 0 ? _a : result);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 获取服务间调用的 Token (client_credentials)
|
|
80
|
+
*/
|
|
81
|
+
async getServiceToken(scopes) {
|
|
82
|
+
var _a;
|
|
83
|
+
// 使用缓存
|
|
84
|
+
if (this.serviceToken && this.serviceToken.expiresAt > Date.now() + 60000) {
|
|
85
|
+
return this.serviceToken.token;
|
|
86
|
+
}
|
|
87
|
+
const response = await fetch(`${this.serviceUrl}/oauth/token`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'Content-Type': 'application/json' },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
grant_type: 'client_credentials',
|
|
92
|
+
client_id: this.clientId,
|
|
93
|
+
client_secret: this.clientSecret,
|
|
94
|
+
scope: scopes === null || scopes === void 0 ? void 0 : scopes.join(' '),
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
const result = await response.json();
|
|
98
|
+
// 兼容 ResponseInterceptor 包装格式 { code, data: { access_token } }
|
|
99
|
+
const tokenData = (_a = result.data) !== null && _a !== void 0 ? _a : result;
|
|
100
|
+
if (!tokenData.access_token) {
|
|
101
|
+
throw new Error(`Failed to get service token: ${JSON.stringify(result)}`);
|
|
102
|
+
}
|
|
103
|
+
this.serviceToken = {
|
|
104
|
+
token: tokenData.access_token,
|
|
105
|
+
expiresAt: Date.now() + tokenData.expires_in * 1000,
|
|
106
|
+
};
|
|
107
|
+
return tokenData.access_token;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 根据用户 ID 获取用户信息
|
|
111
|
+
*/
|
|
112
|
+
async getUserById(userId) {
|
|
113
|
+
const token = await this.getServiceToken();
|
|
114
|
+
const response = await fetch(`${this.serviceUrl}/users/${userId}`, {
|
|
115
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
116
|
+
});
|
|
117
|
+
if (!response.ok)
|
|
118
|
+
return null;
|
|
119
|
+
const result = await response.json();
|
|
120
|
+
return result.data;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 刷新 Token
|
|
124
|
+
*/
|
|
125
|
+
async refreshToken(refreshToken) {
|
|
126
|
+
var _a;
|
|
127
|
+
const response = await fetch(`${this.serviceUrl}/oauth/token`, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'Content-Type': 'application/json' },
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
grant_type: 'refresh_token',
|
|
132
|
+
refresh_token: refreshToken,
|
|
133
|
+
client_id: this.clientId,
|
|
134
|
+
client_secret: this.clientSecret,
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
const result = await response.json();
|
|
138
|
+
const tokenData = (_a = result.data) !== null && _a !== void 0 ? _a : result;
|
|
139
|
+
if (!tokenData.access_token) {
|
|
140
|
+
throw new Error(`Failed to refresh token: ${JSON.stringify(result)}`);
|
|
141
|
+
}
|
|
142
|
+
return tokenData;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 吊销 Token
|
|
146
|
+
*/
|
|
147
|
+
async revokeToken(token) {
|
|
148
|
+
await fetch(`${this.serviceUrl}/oauth/token/revoke`, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: { 'Content-Type': 'application/json' },
|
|
151
|
+
body: JSON.stringify({ token }),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// ========== 登录代理方法 ==========
|
|
155
|
+
/**
|
|
156
|
+
* 账号密码登录
|
|
157
|
+
*/
|
|
158
|
+
async loginByPassword(username, password) {
|
|
159
|
+
const response = await fetch(`${this.serviceUrl}/auth/login/password`, {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: { 'Content-Type': 'application/json' },
|
|
162
|
+
body: JSON.stringify({ username, password, clientId: this.clientId }),
|
|
163
|
+
});
|
|
164
|
+
const result = await response.json();
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const err = new Error(result.message || 'Login failed');
|
|
167
|
+
err.authCode = result.code;
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
return result.data;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 短信验证码登录 (含自动注册)
|
|
174
|
+
*/
|
|
175
|
+
async loginBySms(mobile, code) {
|
|
176
|
+
const response = await fetch(`${this.serviceUrl}/auth/login/sms`, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: { 'Content-Type': 'application/json' },
|
|
179
|
+
body: JSON.stringify({ mobile, code, clientId: this.clientId }),
|
|
180
|
+
});
|
|
181
|
+
const result = await response.json();
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const err = new Error(result.message || 'SMS login failed');
|
|
184
|
+
err.authCode = result.code;
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
return result.data;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 校验短信验证码(一次性)
|
|
191
|
+
*/
|
|
192
|
+
async verifySmsCode(mobile, code) {
|
|
193
|
+
const response = await fetch(`${this.serviceUrl}/sms/verify`, {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { 'Content-Type': 'application/json' },
|
|
196
|
+
body: JSON.stringify({ mobile, code }),
|
|
197
|
+
});
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
const result = await response.json();
|
|
200
|
+
const err = new Error(result.message || 'SMS verify failed');
|
|
201
|
+
err.authCode = result.code;
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 用户注册 (Auth-Service 内部会校验验证码)
|
|
207
|
+
*/
|
|
208
|
+
async register(params) {
|
|
209
|
+
const response = await fetch(`${this.serviceUrl}/auth/register`, {
|
|
210
|
+
method: 'POST',
|
|
211
|
+
headers: { 'Content-Type': 'application/json' },
|
|
212
|
+
body: JSON.stringify({ ...params, clientId: this.clientId }),
|
|
213
|
+
});
|
|
214
|
+
const result = await response.json();
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
const err = new Error(result.message || 'Register failed');
|
|
217
|
+
err.authCode = result.code;
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
return result.data;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 重置密码 (Auth-Service 内部会校验验证码)
|
|
224
|
+
*/
|
|
225
|
+
async resetPassword(mobile, code, newPassword) {
|
|
226
|
+
const response = await fetch(`${this.serviceUrl}/auth/password/reset`, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: { 'Content-Type': 'application/json' },
|
|
229
|
+
body: JSON.stringify({ mobile, code, newPassword }),
|
|
230
|
+
});
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
const result = await response.json();
|
|
233
|
+
const err = new Error(result.message || 'Reset password failed');
|
|
234
|
+
err.authCode = result.code;
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// ========== 管理端方法 (使用 service token,无需短信验证码) ==========
|
|
239
|
+
/**
|
|
240
|
+
* 管理端创建用户 (service token 鉴权,无需短信验证码)
|
|
241
|
+
*/
|
|
242
|
+
async adminCreateUser(params) {
|
|
243
|
+
const token = await this.getServiceToken();
|
|
244
|
+
const response = await fetch(`${this.serviceUrl}/users`, {
|
|
245
|
+
method: 'POST',
|
|
246
|
+
headers: {
|
|
247
|
+
'Content-Type': 'application/json',
|
|
248
|
+
Authorization: `Bearer ${token}`,
|
|
249
|
+
},
|
|
250
|
+
body: JSON.stringify(params),
|
|
251
|
+
});
|
|
252
|
+
const result = await response.json();
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
const err = new Error(result.message || 'Admin create user failed');
|
|
255
|
+
err.authCode = result.code;
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
return result.data;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 管理端重置用户密码 (service token 鉴权,无需短信验证码)
|
|
262
|
+
*/
|
|
263
|
+
async adminResetPassword(userId, newPassword) {
|
|
264
|
+
const token = await this.getServiceToken();
|
|
265
|
+
const response = await fetch(`${this.serviceUrl}/users/${userId}/password`, {
|
|
266
|
+
method: 'PATCH',
|
|
267
|
+
headers: {
|
|
268
|
+
'Content-Type': 'application/json',
|
|
269
|
+
Authorization: `Bearer ${token}`,
|
|
270
|
+
},
|
|
271
|
+
body: JSON.stringify({ newPassword }),
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const result = await response.json();
|
|
275
|
+
const err = new Error(result.message || 'Admin reset password failed');
|
|
276
|
+
err.authCode = result.code;
|
|
277
|
+
throw err;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 发送短信验证码
|
|
282
|
+
*/
|
|
283
|
+
async sendSmsCode(mobile, type) {
|
|
284
|
+
const response = await fetch(`${this.serviceUrl}/sms/send`, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
headers: { 'Content-Type': 'application/json' },
|
|
287
|
+
body: JSON.stringify({ mobile, type }),
|
|
288
|
+
});
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const result = await response.json();
|
|
291
|
+
const err = new Error(result.message || 'SMS send failed');
|
|
292
|
+
err.authCode = result.code;
|
|
293
|
+
throw err;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
exports.AuthClient = AuthClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { AuthClient, AuthClientOptions, TokenPayload, UserInfo, TokenPair, IntrospectResult, LoginResult, AuthServiceError } from './auth-client';
|
|
2
|
+
export { koaAuthMiddleware } from './middleware/koa';
|
|
3
|
+
export { expressAuthMiddleware } from './middleware/express';
|
|
4
|
+
export { AuthGuard, AUTH_CLIENT } from './middleware/nestjs';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUTH_CLIENT = exports.AuthGuard = exports.expressAuthMiddleware = exports.koaAuthMiddleware = exports.AuthClient = void 0;
|
|
4
|
+
var auth_client_1 = require("./auth-client");
|
|
5
|
+
Object.defineProperty(exports, "AuthClient", { enumerable: true, get: function () { return auth_client_1.AuthClient; } });
|
|
6
|
+
var koa_1 = require("./middleware/koa");
|
|
7
|
+
Object.defineProperty(exports, "koaAuthMiddleware", { enumerable: true, get: function () { return koa_1.koaAuthMiddleware; } });
|
|
8
|
+
var express_1 = require("./middleware/express");
|
|
9
|
+
Object.defineProperty(exports, "expressAuthMiddleware", { enumerable: true, get: function () { return express_1.expressAuthMiddleware; } });
|
|
10
|
+
var nestjs_1 = require("./middleware/nestjs");
|
|
11
|
+
Object.defineProperty(exports, "AuthGuard", { enumerable: true, get: function () { return nestjs_1.AuthGuard; } });
|
|
12
|
+
Object.defineProperty(exports, "AUTH_CLIENT", { enumerable: true, get: function () { return nestjs_1.AUTH_CLIENT; } });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AuthClient, TokenPayload } from '../auth-client';
|
|
2
|
+
interface ExpressRequest {
|
|
3
|
+
headers: Record<string, string | undefined>;
|
|
4
|
+
query: Record<string, string | undefined>;
|
|
5
|
+
user?: TokenPayload;
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
interface ExpressResponse {
|
|
9
|
+
status: (code: number) => ExpressResponse;
|
|
10
|
+
json: (body: any) => void;
|
|
11
|
+
}
|
|
12
|
+
interface ExpressMiddlewareOptions {
|
|
13
|
+
exclude?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Express 认证中间件
|
|
17
|
+
*/
|
|
18
|
+
export declare function expressAuthMiddleware(client: AuthClient, options?: ExpressMiddlewareOptions): (req: ExpressRequest, res: ExpressResponse, next: () => void) => Promise<void>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expressAuthMiddleware = expressAuthMiddleware;
|
|
4
|
+
/**
|
|
5
|
+
* Express 认证中间件
|
|
6
|
+
*/
|
|
7
|
+
function expressAuthMiddleware(client, options = {}) {
|
|
8
|
+
const excludePaths = new Set(options.exclude || []);
|
|
9
|
+
return async (req, res, next) => {
|
|
10
|
+
if (excludePaths.has(req.path)) {
|
|
11
|
+
return next();
|
|
12
|
+
}
|
|
13
|
+
const auth = req.headers['authorization'];
|
|
14
|
+
const token = (auth === null || auth === void 0 ? void 0 : auth.startsWith('Bearer '))
|
|
15
|
+
? auth.slice(7)
|
|
16
|
+
: req.headers['x-access-token'] || req.query.token;
|
|
17
|
+
if (!token) {
|
|
18
|
+
return res.status(401).json({ code: 1001, message: 'Unauthorized', data: null });
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
req.user = await client.verifyAccessToken(token);
|
|
22
|
+
next();
|
|
23
|
+
}
|
|
24
|
+
catch (_a) {
|
|
25
|
+
res.status(401).json({ code: 1001, message: 'Token invalid or expired', data: null });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AuthClient } from '../auth-client';
|
|
2
|
+
interface KoaContext {
|
|
3
|
+
headers: Record<string, string | undefined>;
|
|
4
|
+
query: Record<string, string | undefined>;
|
|
5
|
+
state: Record<string, any>;
|
|
6
|
+
status: number;
|
|
7
|
+
body: any;
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
10
|
+
interface KoaMiddlewareOptions {
|
|
11
|
+
/** 排除的路径 (不需要认证) */
|
|
12
|
+
exclude?: string[];
|
|
13
|
+
/** Token 从哪些位置提取 */
|
|
14
|
+
tokenExtractors?: ((ctx: KoaContext) => string | undefined)[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Koa 认证中间件
|
|
18
|
+
* 替换 SaleMember 中的 bootstrap/logics/auth.js
|
|
19
|
+
*
|
|
20
|
+
* 用法:
|
|
21
|
+
* ```
|
|
22
|
+
* const { AuthClient, koaAuthMiddleware } = require('@siketech/auth-sdk');
|
|
23
|
+
* const authClient = new AuthClient({ serviceUrl, clientId, clientSecret, publicKey });
|
|
24
|
+
* app.use(koaAuthMiddleware(authClient, { exclude: ['/health'] }));
|
|
25
|
+
* // 验证后 ctx.state.admin = payload (兼容现有代码)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function koaAuthMiddleware(client: AuthClient, options?: KoaMiddlewareOptions): (ctx: KoaContext, next: () => Promise<void>) => Promise<void>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.koaAuthMiddleware = koaAuthMiddleware;
|
|
4
|
+
const defaultExtractors = [
|
|
5
|
+
(ctx) => {
|
|
6
|
+
const auth = ctx.headers['authorization'];
|
|
7
|
+
if (auth === null || auth === void 0 ? void 0 : auth.startsWith('Bearer '))
|
|
8
|
+
return auth.slice(7);
|
|
9
|
+
return undefined;
|
|
10
|
+
},
|
|
11
|
+
(ctx) => ctx.headers['x-access-token'],
|
|
12
|
+
(ctx) => ctx.headers['access-token'],
|
|
13
|
+
(ctx) => ctx.headers['access_token'],
|
|
14
|
+
(ctx) => ctx.query.token,
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Koa 认证中间件
|
|
18
|
+
* 替换 SaleMember 中的 bootstrap/logics/auth.js
|
|
19
|
+
*
|
|
20
|
+
* 用法:
|
|
21
|
+
* ```
|
|
22
|
+
* const { AuthClient, koaAuthMiddleware } = require('@siketech/auth-sdk');
|
|
23
|
+
* const authClient = new AuthClient({ serviceUrl, clientId, clientSecret, publicKey });
|
|
24
|
+
* app.use(koaAuthMiddleware(authClient, { exclude: ['/health'] }));
|
|
25
|
+
* // 验证后 ctx.state.admin = payload (兼容现有代码)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function koaAuthMiddleware(client, options = {}) {
|
|
29
|
+
const extractors = options.tokenExtractors || defaultExtractors;
|
|
30
|
+
const excludePaths = new Set(options.exclude || []);
|
|
31
|
+
return async (ctx, next) => {
|
|
32
|
+
// 跳过排除路径
|
|
33
|
+
if (excludePaths.has(ctx.path)) {
|
|
34
|
+
return next();
|
|
35
|
+
}
|
|
36
|
+
// 提取 token
|
|
37
|
+
let token;
|
|
38
|
+
for (const extractor of extractors) {
|
|
39
|
+
token = extractor(ctx);
|
|
40
|
+
if (token)
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
if (!token) {
|
|
44
|
+
ctx.status = 401;
|
|
45
|
+
ctx.body = { code: 1001, message: '当前登录身份已失效,请重新登录!', data: null };
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const payload = await client.verifyAccessToken(token);
|
|
50
|
+
// 兼容 SaleMember 现有代码: ctx.state.admin
|
|
51
|
+
ctx.state.admin = {
|
|
52
|
+
_id: payload.sub,
|
|
53
|
+
id: payload.sub,
|
|
54
|
+
username: payload.username,
|
|
55
|
+
roles: payload.roles,
|
|
56
|
+
...payload,
|
|
57
|
+
};
|
|
58
|
+
ctx.state.user = payload;
|
|
59
|
+
return next();
|
|
60
|
+
}
|
|
61
|
+
catch (_a) {
|
|
62
|
+
ctx.status = 401;
|
|
63
|
+
ctx.body = { code: 1001, message: '当前登录身份已失效,请重新登录!', data: null };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AuthClient } from '../auth-client';
|
|
2
|
+
export declare const AUTH_CLIENT = "AUTH_CLIENT";
|
|
3
|
+
/**
|
|
4
|
+
* NestJS Guard
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* ```
|
|
8
|
+
* // app.module.ts
|
|
9
|
+
* {
|
|
10
|
+
* provide: AUTH_CLIENT,
|
|
11
|
+
* useFactory: () => new AuthClient({ serviceUrl, clientId, clientSecret, publicKey }),
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* // controller
|
|
15
|
+
* @UseGuards(AuthGuard)
|
|
16
|
+
* @Controller('xxx')
|
|
17
|
+
* export class XxxController { ... }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare class AuthGuard {
|
|
21
|
+
private readonly client;
|
|
22
|
+
constructor(client: AuthClient);
|
|
23
|
+
canActivate(context: any): Promise<boolean>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthGuard = exports.AUTH_CLIENT = void 0;
|
|
4
|
+
exports.AUTH_CLIENT = 'AUTH_CLIENT';
|
|
5
|
+
/**
|
|
6
|
+
* NestJS Guard
|
|
7
|
+
*
|
|
8
|
+
* 用法:
|
|
9
|
+
* ```
|
|
10
|
+
* // app.module.ts
|
|
11
|
+
* {
|
|
12
|
+
* provide: AUTH_CLIENT,
|
|
13
|
+
* useFactory: () => new AuthClient({ serviceUrl, clientId, clientSecret, publicKey }),
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* // controller
|
|
17
|
+
* @UseGuards(AuthGuard)
|
|
18
|
+
* @Controller('xxx')
|
|
19
|
+
* export class XxxController { ... }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
class AuthGuard {
|
|
23
|
+
constructor(client) {
|
|
24
|
+
this.client = client;
|
|
25
|
+
}
|
|
26
|
+
async canActivate(context) {
|
|
27
|
+
var _a;
|
|
28
|
+
const request = context.switchToHttp().getRequest();
|
|
29
|
+
const auth = request.headers['authorization'];
|
|
30
|
+
const token = (auth === null || auth === void 0 ? void 0 : auth.startsWith('Bearer '))
|
|
31
|
+
? auth.slice(7)
|
|
32
|
+
: request.headers['x-access-token'] || ((_a = request.query) === null || _a === void 0 ? void 0 : _a.token);
|
|
33
|
+
if (!token)
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
const payload = await this.client.verifyAccessToken(token);
|
|
37
|
+
request.user = payload;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (_b) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.AuthGuard = AuthGuard;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scorehub/auth-sdk",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Auth-Service SDK for Node.js applications",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"registry": "https://registry.npmjs.org/",
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"jsonwebtoken": "^9.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/jsonwebtoken": "^9.0.0",
|
|
23
|
+
"typescript": "^5.0.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"koa": ">=2.0.0",
|
|
27
|
+
"express": ">=4.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"koa": {
|
|
31
|
+
"optional": true
|
|
32
|
+
},
|
|
33
|
+
"express": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|