@tachybase/module-auth 1.6.0 → 1.6.2
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 +117 -117
- package/client.d.ts +2 -2
- package/client.js +65 -65
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/locale/en-US.json +63 -56
- package/dist/locale/ko_KR.json +36 -29
- package/dist/locale/zh-CN.json +63 -56
- package/dist/node_modules/cron/package.json +1 -1
- package/dist/node_modules/ms/package.json +1 -1
- package/dist/server/actions/auth.js +2 -2
- package/dist/server/actions/authenticators.js +2 -2
- package/dist/server/basic-auth.js +8 -3
- package/dist/server/collections/.gitkeep +0 -0
- package/dist/server/locale/en-US.d.ts +0 -8
- package/dist/server/locale/en-US.js +1 -9
- package/dist/server/locale/zh-CN.d.ts +0 -8
- package/dist/server/locale/zh-CN.js +1 -9
- package/dist/server/plugin.js +20 -21
- package/dist/server/token-blacklist.d.ts +1 -1
- package/dist/server/user-status.d.ts +36 -97
- package/dist/server/user-status.js +115 -439
- package/package.json +7 -7
- package/server.d.ts +2 -2
- package/server.js +65 -65
|
@@ -17,79 +17,57 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
18
|
var user_status_exports = {};
|
|
19
19
|
__export(user_status_exports, {
|
|
20
|
-
UserStatusService: () => UserStatusService
|
|
21
|
-
default: () => user_status_default
|
|
20
|
+
UserStatusService: () => UserStatusService
|
|
22
21
|
});
|
|
23
22
|
module.exports = __toCommonJS(user_status_exports);
|
|
24
|
-
var import_server = require("@tego/server");
|
|
25
23
|
var import_preset = require("../preset");
|
|
24
|
+
const localeNamespace = import_preset.namespace;
|
|
26
25
|
class UserStatusService {
|
|
27
|
-
constructor(app) {
|
|
26
|
+
constructor({ cache, app, logger }) {
|
|
27
|
+
this.cache = cache;
|
|
28
28
|
this.app = app;
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
this.
|
|
29
|
+
this.logger = logger;
|
|
30
|
+
this.userCollection = app.db.getCollection("users");
|
|
31
|
+
this.userStatusCollection = app.db.getCollection("userStatuses");
|
|
32
|
+
this.userStatusHistoryCollection = app.db.getCollection("userStatusHistories");
|
|
33
|
+
this.registerStatusChangeInterceptor();
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*/
|
|
36
|
-
getUserStatusCacheKey(userId) {
|
|
37
|
-
return `userStatus:${userId}`;
|
|
35
|
+
get userRepository() {
|
|
36
|
+
return this.userCollection.repository;
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
*/
|
|
42
|
-
async getUserStatusFromCache(userId) {
|
|
43
|
-
try {
|
|
44
|
-
const cacheKey = this.getUserStatusCacheKey(userId);
|
|
45
|
-
const cached = await this.cache.get(cacheKey);
|
|
46
|
-
return cached ? JSON.parse(cached) : null;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
this.logger.error("Error getting user status from cache:", error);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
38
|
+
get userStatusRepository() {
|
|
39
|
+
return this.userStatusCollection.repository;
|
|
51
40
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
async setUserStatusCache(userId, data) {
|
|
56
|
-
try {
|
|
57
|
-
const cacheKey = this.getUserStatusCacheKey(userId);
|
|
58
|
-
await this.cache.set(cacheKey, JSON.stringify(data), 300 * 1e3);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
this.logger.error("Error setting user status cache:", error);
|
|
61
|
-
}
|
|
41
|
+
get userStatusHistoryRepository() {
|
|
42
|
+
return this.userStatusHistoryCollection.repository;
|
|
62
43
|
}
|
|
63
44
|
/**
|
|
64
|
-
*
|
|
45
|
+
* 翻译消息
|
|
65
46
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
} catch (error) {
|
|
71
|
-
this.logger.error("Error clearing user status cache:", error);
|
|
72
|
-
}
|
|
47
|
+
t(key, options) {
|
|
48
|
+
var _a, _b;
|
|
49
|
+
const language = (options == null ? void 0 : options.lng) || ((_a = this.app.i18n) == null ? void 0 : _a.language) || "en-US";
|
|
50
|
+
return ((_b = this.app.i18n) == null ? void 0 : _b.t(key, { ...options, lng: language })) || key;
|
|
73
51
|
}
|
|
74
|
-
/**
|
|
75
|
-
* 检查用户状态是否允许登录
|
|
76
|
-
* @param userId 用户ID
|
|
77
|
-
* @returns 检查结果
|
|
78
|
-
*/
|
|
79
52
|
async checkUserStatus(userId) {
|
|
80
53
|
try {
|
|
81
54
|
let cached = await this.getUserStatusFromCache(userId);
|
|
82
55
|
if (!cached) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
filterByTk: userId,
|
|
56
|
+
const user = await this.userRepository.findOne({
|
|
57
|
+
filter: { id: userId },
|
|
86
58
|
fields: ["id", "status", "statusExpireAt", "previousStatus"]
|
|
87
59
|
});
|
|
88
60
|
if (!user) {
|
|
89
61
|
return {
|
|
90
62
|
allowed: false,
|
|
91
63
|
status: "unknown",
|
|
92
|
-
|
|
64
|
+
statusInfo: {
|
|
65
|
+
title: this.t("User not found. Please sign in again to continue.", { ns: localeNamespace }),
|
|
66
|
+
color: "red",
|
|
67
|
+
allowLogin: false
|
|
68
|
+
},
|
|
69
|
+
errorMessage: this.t("User not found. Please sign in again to continue.", { ns: localeNamespace }),
|
|
70
|
+
isExpired: false
|
|
93
71
|
};
|
|
94
72
|
}
|
|
95
73
|
cached = {
|
|
@@ -103,9 +81,8 @@ class UserStatusService {
|
|
|
103
81
|
}
|
|
104
82
|
if (cached.expireAt && new Date(cached.expireAt) <= /* @__PURE__ */ new Date()) {
|
|
105
83
|
await this.restoreUserStatus(userId);
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
filterByTk: userId,
|
|
84
|
+
const user = await this.userRepository.findOne({
|
|
85
|
+
filter: { id: userId },
|
|
109
86
|
fields: ["status"]
|
|
110
87
|
});
|
|
111
88
|
cached.status = user.status || "active";
|
|
@@ -113,9 +90,8 @@ class UserStatusService {
|
|
|
113
90
|
cached.previousStatus = null;
|
|
114
91
|
await this.setUserStatusCache(userId, cached);
|
|
115
92
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
filterByTk: cached.status
|
|
93
|
+
const statusInfo = await this.userStatusRepository.findOne({
|
|
94
|
+
filter: { key: cached.status }
|
|
119
95
|
});
|
|
120
96
|
if (!statusInfo) {
|
|
121
97
|
this.logger.warn(`Status definition not found: ${cached.status}`);
|
|
@@ -123,36 +99,34 @@ class UserStatusService {
|
|
|
123
99
|
allowed: false,
|
|
124
100
|
status: cached.status,
|
|
125
101
|
statusInfo: {
|
|
126
|
-
title: this.
|
|
102
|
+
title: this.t("Invalid status", { ns: localeNamespace }),
|
|
127
103
|
color: "red",
|
|
128
|
-
allowLogin: false
|
|
129
|
-
loginErrorMessage: this.app.i18n.t("User status is invalid, please contact administrator", {
|
|
130
|
-
ns: import_preset.namespace
|
|
131
|
-
})
|
|
104
|
+
allowLogin: false
|
|
132
105
|
},
|
|
133
|
-
errorMessage: this.
|
|
106
|
+
errorMessage: this.t("User status is invalid, please contact administrator", { ns: localeNamespace }),
|
|
107
|
+
isExpired: true
|
|
134
108
|
};
|
|
135
109
|
}
|
|
136
110
|
const translateMessage = (message) => {
|
|
137
111
|
if (!message) return "";
|
|
138
112
|
const match = message.match(/\{\{t\("([^"]+)"\)\}\}/);
|
|
139
113
|
if (match && match[1]) {
|
|
140
|
-
return this.
|
|
114
|
+
return this.t(match[1], { ns: localeNamespace });
|
|
141
115
|
}
|
|
142
116
|
return message;
|
|
143
117
|
};
|
|
144
118
|
const translatedTitle = translateMessage(statusInfo.title);
|
|
145
|
-
const translatedLoginErrorMessage = translateMessage(statusInfo.loginErrorMessage);
|
|
119
|
+
const translatedLoginErrorMessage = translateMessage(statusInfo.loginErrorMessage) || this.t("User status does not allow login", { ns: localeNamespace });
|
|
146
120
|
return {
|
|
147
121
|
allowed: statusInfo.allowLogin,
|
|
148
122
|
status: cached.status,
|
|
149
123
|
statusInfo: {
|
|
150
124
|
title: translatedTitle,
|
|
151
125
|
color: statusInfo.color,
|
|
152
|
-
allowLogin: statusInfo.allowLogin
|
|
153
|
-
loginErrorMessage: translatedLoginErrorMessage
|
|
126
|
+
allowLogin: statusInfo.allowLogin
|
|
154
127
|
},
|
|
155
|
-
errorMessage:
|
|
128
|
+
errorMessage: statusInfo.allowLogin ? null : translatedLoginErrorMessage || null,
|
|
129
|
+
isExpired: false
|
|
156
130
|
};
|
|
157
131
|
} catch (error) {
|
|
158
132
|
this.logger.error(`Error checking user status for userId=${userId}: ${error}`);
|
|
@@ -160,106 +134,73 @@ class UserStatusService {
|
|
|
160
134
|
allowed: false,
|
|
161
135
|
status: "unknown",
|
|
162
136
|
statusInfo: {
|
|
163
|
-
title: this.
|
|
137
|
+
title: this.t("Unknown status", { ns: localeNamespace }),
|
|
164
138
|
color: "red",
|
|
165
|
-
allowLogin: false
|
|
166
|
-
loginErrorMessage: this.app.i18n.t("System error, please contact administrator", { ns: import_preset.namespace })
|
|
139
|
+
allowLogin: false
|
|
167
140
|
},
|
|
168
|
-
errorMessage: this.
|
|
141
|
+
errorMessage: this.t("System error, please contact administrator", { ns: localeNamespace }),
|
|
142
|
+
isExpired: false
|
|
169
143
|
};
|
|
170
144
|
}
|
|
171
145
|
}
|
|
172
|
-
|
|
173
|
-
* 记录状态变更历史(如果不存在相同记录)
|
|
174
|
-
* @param params 状态变更参数
|
|
175
|
-
*/
|
|
176
|
-
async recordStatusHistoryIfNotExists(params) {
|
|
177
|
-
const { userId, fromStatus, toStatus, reason, expireAt, operationType, createdBy, transaction } = params;
|
|
146
|
+
async setUserStatusCache(userId, data) {
|
|
178
147
|
try {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
const existing = await historyRepo.findOne({
|
|
182
|
-
filter: {
|
|
183
|
-
userId,
|
|
184
|
-
fromStatus,
|
|
185
|
-
toStatus,
|
|
186
|
-
createdAt: {
|
|
187
|
-
$gte: fiveSecondsAgo
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
sort: ["-createdAt"],
|
|
191
|
-
transaction
|
|
192
|
-
});
|
|
193
|
-
if (existing) {
|
|
194
|
-
this.logger.warn(
|
|
195
|
-
`Skipping duplicate history record: userId=${userId}, ${fromStatus} \u2192 ${toStatus}, operationType=${operationType}`
|
|
196
|
-
);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
await historyRepo.create({
|
|
200
|
-
values: {
|
|
201
|
-
userId,
|
|
202
|
-
fromStatus,
|
|
203
|
-
toStatus,
|
|
204
|
-
reason,
|
|
205
|
-
expireAt,
|
|
206
|
-
operationType,
|
|
207
|
-
createdBy
|
|
208
|
-
},
|
|
209
|
-
transaction
|
|
210
|
-
});
|
|
211
|
-
this.logger.debug(
|
|
212
|
-
`History recorded: userId=${userId}, ${fromStatus} \u2192 ${toStatus}, operationType=${operationType}`
|
|
213
|
-
);
|
|
214
|
-
await this.clearUserStatusCache(userId);
|
|
215
|
-
this.logger.debug(`Cache cleared for userId=${userId}`);
|
|
148
|
+
const cacheKey = this.getUserStatusCacheKey(userId);
|
|
149
|
+
await this.cache.set(cacheKey, JSON.stringify(data), 300 * 1e3);
|
|
216
150
|
} catch (error) {
|
|
217
|
-
this.logger.error("
|
|
218
|
-
throw error;
|
|
151
|
+
this.logger.error("Error setting user status cache:", error);
|
|
219
152
|
}
|
|
220
153
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
154
|
+
async getUserStatusFromCache(userId) {
|
|
155
|
+
try {
|
|
156
|
+
const cacheKey = this.getUserStatusCacheKey(userId);
|
|
157
|
+
const cached = await this.cache.get(cacheKey);
|
|
158
|
+
return cached ? JSON.parse(cached) : null;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.logger.error("Error getting user status from cache:", error);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
getUserStatusCacheKey(userId) {
|
|
165
|
+
return `userStatus:${userId}`;
|
|
166
|
+
}
|
|
225
167
|
async restoreUserStatus(userId) {
|
|
226
168
|
try {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
filterByTk: userId,
|
|
169
|
+
const user = await this.userRepository.findOne({
|
|
170
|
+
filter: { id: userId },
|
|
230
171
|
fields: ["id", "status", "statusExpireAt", "previousStatus"]
|
|
231
172
|
});
|
|
232
173
|
if (!user) {
|
|
233
|
-
throw new Error(this.
|
|
174
|
+
throw new Error(this.t("User not found. Please sign in again to continue.", { ns: localeNamespace }));
|
|
234
175
|
}
|
|
235
176
|
if (!user.statusExpireAt || new Date(user.statusExpireAt) > /* @__PURE__ */ new Date()) {
|
|
236
177
|
return;
|
|
237
178
|
}
|
|
238
179
|
const oldStatus = user.status;
|
|
239
180
|
const restoreToStatus = user.previousStatus || "active";
|
|
240
|
-
await this.db.sequelize.transaction(async (transaction) => {
|
|
181
|
+
await this.app.db.sequelize.transaction(async (transaction) => {
|
|
241
182
|
await this.recordStatusHistoryIfNotExists({
|
|
242
183
|
userId,
|
|
243
184
|
fromStatus: oldStatus,
|
|
244
185
|
toStatus: restoreToStatus,
|
|
245
|
-
reason: this.
|
|
186
|
+
reason: this.t("Status expired, auto restored", { ns: localeNamespace }),
|
|
246
187
|
operationType: "auto",
|
|
247
188
|
createdBy: null,
|
|
248
189
|
expireAt: null,
|
|
249
190
|
transaction
|
|
250
191
|
});
|
|
251
|
-
await
|
|
252
|
-
|
|
192
|
+
await this.userRepository.update({
|
|
193
|
+
filter: { id: userId },
|
|
253
194
|
values: {
|
|
254
195
|
status: restoreToStatus,
|
|
255
196
|
statusExpireAt: null,
|
|
256
197
|
previousStatus: null,
|
|
257
|
-
statusReason: this.
|
|
198
|
+
statusReason: this.t("Status expired, auto restored", { ns: localeNamespace })
|
|
258
199
|
},
|
|
259
200
|
transaction
|
|
260
201
|
});
|
|
261
202
|
});
|
|
262
|
-
this.app.emitAsync("user:statusRestored", {
|
|
203
|
+
await this.app.emitAsync("user:statusRestored", {
|
|
263
204
|
userId,
|
|
264
205
|
fromStatus: oldStatus,
|
|
265
206
|
toStatus: restoreToStatus
|
|
@@ -270,321 +211,57 @@ class UserStatusService {
|
|
|
270
211
|
throw error;
|
|
271
212
|
}
|
|
272
213
|
}
|
|
273
|
-
|
|
274
|
-
* 注册新的用户状态(供插件使用)
|
|
275
|
-
* @param config 状态配置
|
|
276
|
-
*/
|
|
277
|
-
async registerStatus(config) {
|
|
214
|
+
async clearUserStatusCache(userId) {
|
|
278
215
|
try {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
filterByTk: config.key
|
|
282
|
-
});
|
|
283
|
-
if (existing) {
|
|
284
|
-
if (existing.packageName === config.packageName) {
|
|
285
|
-
await statusRepo.update({
|
|
286
|
-
filterByTk: config.key,
|
|
287
|
-
values: {
|
|
288
|
-
title: config.title,
|
|
289
|
-
color: config.color || "default",
|
|
290
|
-
allowLogin: config.allowLogin,
|
|
291
|
-
loginErrorMessage: config.loginErrorMessage || null,
|
|
292
|
-
description: config.description || null,
|
|
293
|
-
sort: config.sort || 0,
|
|
294
|
-
config: config.config || {}
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
this.logger.info(`Updated status: ${config.key} by ${config.packageName}`);
|
|
298
|
-
} else {
|
|
299
|
-
this.logger.warn(`Status ${config.key} already registered by ${existing.packageName}, skipping`);
|
|
300
|
-
}
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
await statusRepo.create({
|
|
304
|
-
values: {
|
|
305
|
-
key: config.key,
|
|
306
|
-
title: config.title,
|
|
307
|
-
color: config.color || "default",
|
|
308
|
-
allowLogin: config.allowLogin,
|
|
309
|
-
loginErrorMessage: config.loginErrorMessage || null,
|
|
310
|
-
isSystemDefined: false,
|
|
311
|
-
// 插件注册的状态不是系统内置
|
|
312
|
-
packageName: config.packageName,
|
|
313
|
-
description: config.description || null,
|
|
314
|
-
sort: config.sort || 0,
|
|
315
|
-
config: config.config || {}
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
this.logger.info(`Registered new status: ${config.key} by ${config.packageName}`);
|
|
216
|
+
const cacheKey = this.getUserStatusCacheKey(userId);
|
|
217
|
+
await this.cache.del(cacheKey);
|
|
319
218
|
} catch (error) {
|
|
320
|
-
this.logger.error("Error
|
|
321
|
-
throw error;
|
|
219
|
+
this.logger.error("Error clearing user status cache:", error);
|
|
322
220
|
}
|
|
323
221
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
*/
|
|
327
|
-
async getStatusStatistics() {
|
|
222
|
+
async recordStatusHistoryIfNotExists(params) {
|
|
223
|
+
const { userId, fromStatus, toStatus, reason, expireAt, operationType, createdBy, transaction } = params;
|
|
328
224
|
try {
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
225
|
+
const fiveSecondsAgo = new Date(Date.now() - 5e3);
|
|
226
|
+
const existing = await this.userStatusHistoryRepository.findOne({
|
|
227
|
+
filter: {
|
|
228
|
+
userId,
|
|
229
|
+
fromStatus,
|
|
230
|
+
toStatus,
|
|
231
|
+
createdAt: {
|
|
232
|
+
$gte: fiveSecondsAgo
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
sort: ["-createdAt"],
|
|
236
|
+
transaction
|
|
334
237
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
238
|
+
if (existing) {
|
|
239
|
+
this.logger.warn(
|
|
240
|
+
`Skipping duplicate history record: userId=${userId}, ${fromStatus} \u2192 ${toStatus}, operationType=${operationType}`
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
338
243
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
this.logger.error("Error getting status statistics:", error);
|
|
342
|
-
return {};
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* 注入登录检查
|
|
347
|
-
*/
|
|
348
|
-
injectLoginCheck() {
|
|
349
|
-
const userStatusService = this;
|
|
350
|
-
import_server.BaseAuth.prototype.signNewToken = async function(userId, options) {
|
|
351
|
-
const user = await this.userRepository.findOne({
|
|
352
|
-
filter: { id: userId },
|
|
353
|
-
fields: ["id", "status"]
|
|
354
|
-
});
|
|
355
|
-
const userStatus = (user == null ? void 0 : user.status) || "active";
|
|
356
|
-
const tokenInfo = await this.tokenController.add({ userId });
|
|
357
|
-
const expiresIn = Math.floor((await this.tokenController.getConfig()).tokenExpirationTime / 1e3);
|
|
358
|
-
const token = this.jwt.sign(
|
|
359
|
-
{
|
|
244
|
+
await this.userStatusHistoryRepository.create({
|
|
245
|
+
values: {
|
|
360
246
|
userId,
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
247
|
+
fromStatus,
|
|
248
|
+
toStatus,
|
|
249
|
+
reason,
|
|
250
|
+
expireAt,
|
|
251
|
+
operationType,
|
|
252
|
+
createdBy
|
|
366
253
|
},
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
254
|
+
transaction
|
|
255
|
+
});
|
|
256
|
+
this.logger.debug(
|
|
257
|
+
`History recorded: userId=${userId}, ${fromStatus} \u2192 ${toStatus}, operationType=${operationType}`
|
|
371
258
|
);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
user = await this.validate();
|
|
379
|
-
} catch (err) {
|
|
380
|
-
this.ctx.throw(err.status || 401, err.message, {
|
|
381
|
-
...err
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
if (!user) {
|
|
385
|
-
this.ctx.throw(401, {
|
|
386
|
-
message: this.ctx.t("User not found. Please sign in again to continue.", { ns: import_preset.namespace }),
|
|
387
|
-
code: import_server.AuthErrorCode.NOT_EXIST_USER
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
const statusCheckResult = await userStatusService.checkUserStatus(user.id);
|
|
391
|
-
if (!statusCheckResult.allowed) {
|
|
392
|
-
this.ctx.throw(403, {
|
|
393
|
-
message: this.ctx.t(statusCheckResult.statusInfo.loginErrorMessage, { ns: import_preset.namespace }),
|
|
394
|
-
code: import_server.AuthErrorCode.INVALID_TOKEN
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
const token = await this.signNewToken(user.id);
|
|
398
|
-
return {
|
|
399
|
-
user,
|
|
400
|
-
token
|
|
401
|
-
};
|
|
402
|
-
};
|
|
403
|
-
const originalCheck = import_server.BaseAuth.prototype.check;
|
|
404
|
-
import_server.BaseAuth.prototype.check = async function() {
|
|
405
|
-
const user = await originalCheck.call(this);
|
|
406
|
-
if (!user) {
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
const token = this.ctx.getBearerToken();
|
|
410
|
-
if (!token) {
|
|
411
|
-
return user;
|
|
412
|
-
}
|
|
413
|
-
try {
|
|
414
|
-
const decoded = await this.jwt.decode(token);
|
|
415
|
-
const tokenUserStatus = decoded.userStatus || "active";
|
|
416
|
-
const currentUserStatus = user.status || "active";
|
|
417
|
-
if (tokenUserStatus !== currentUserStatus) {
|
|
418
|
-
userStatusService.logger.warn(
|
|
419
|
-
`[check] Status mismatch, userId=${user.id}, token=${tokenUserStatus}, current=${currentUserStatus}`
|
|
420
|
-
);
|
|
421
|
-
this.ctx.throw(401, {
|
|
422
|
-
message: this.ctx.t("Your account status has changed. Please sign in again.", { ns: import_preset.namespace }),
|
|
423
|
-
code: import_server.AuthErrorCode.INVALID_TOKEN
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
const statusCheckResult = await this.checkUserStatusInContext(user.id);
|
|
427
|
-
if (!statusCheckResult.allowed) {
|
|
428
|
-
userStatusService.logger.warn(`[check] Status not allowed, userId=${user.id}, status=${currentUserStatus}`);
|
|
429
|
-
this.ctx.throw(403, {
|
|
430
|
-
message: this.ctx.t(statusCheckResult.statusInfo.loginErrorMessage, { ns: import_preset.namespace }),
|
|
431
|
-
code: import_server.AuthErrorCode.INVALID_TOKEN
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
userStatusService.logger.debug(`[check] Passed, userId=${user.id}, status=${currentUserStatus}`);
|
|
435
|
-
} catch (err) {
|
|
436
|
-
if (err.status) {
|
|
437
|
-
throw err;
|
|
438
|
-
}
|
|
439
|
-
userStatusService.logger.error("[check] Token validation error:", err);
|
|
440
|
-
throw err;
|
|
441
|
-
}
|
|
442
|
-
return user;
|
|
443
|
-
};
|
|
444
|
-
import_server.BaseAuth.prototype.checkUserStatusInContext = async function(userId) {
|
|
445
|
-
try {
|
|
446
|
-
if (!userStatusService) {
|
|
447
|
-
throw new Error("userStatusService is undefined");
|
|
448
|
-
}
|
|
449
|
-
if (!userStatusService.app) {
|
|
450
|
-
throw new Error("userStatusService.app is undefined");
|
|
451
|
-
}
|
|
452
|
-
if (!userStatusService.app.db) {
|
|
453
|
-
throw new Error("userStatusService.app.db is undefined");
|
|
454
|
-
}
|
|
455
|
-
if (!userStatusService.app.cache) {
|
|
456
|
-
throw new Error("userStatusService.app.cache is undefined");
|
|
457
|
-
}
|
|
458
|
-
const contextDb = userStatusService.app.db;
|
|
459
|
-
const contextCache = userStatusService.app.cache;
|
|
460
|
-
let cached = null;
|
|
461
|
-
try {
|
|
462
|
-
const cacheKey = `userStatus:${userId}`;
|
|
463
|
-
const cachedData = await contextCache.get(cacheKey);
|
|
464
|
-
cached = cachedData ? JSON.parse(cachedData) : null;
|
|
465
|
-
} catch (error) {
|
|
466
|
-
userStatusService.logger.error("Error getting user status from cache:", error);
|
|
467
|
-
cached = null;
|
|
468
|
-
}
|
|
469
|
-
if (!cached) {
|
|
470
|
-
const userRepo = contextDb.getRepository("users");
|
|
471
|
-
const user = await userRepo.findOne({
|
|
472
|
-
filterByTk: userId,
|
|
473
|
-
fields: ["id", "status", "statusExpireAt", "previousStatus"]
|
|
474
|
-
});
|
|
475
|
-
if (!user) {
|
|
476
|
-
return {
|
|
477
|
-
allowed: false,
|
|
478
|
-
status: "unknown",
|
|
479
|
-
errorMessage: userStatusService.app.i18n.t("User not found")
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
cached = {
|
|
483
|
-
userId: user.id,
|
|
484
|
-
status: user.status || "active",
|
|
485
|
-
expireAt: user.statusExpireAt,
|
|
486
|
-
previousStatus: user.previousStatus,
|
|
487
|
-
lastChecked: /* @__PURE__ */ new Date()
|
|
488
|
-
};
|
|
489
|
-
try {
|
|
490
|
-
const cacheKey = `userStatus:${userId}`;
|
|
491
|
-
await contextCache.set(cacheKey, JSON.stringify(cached), 300 * 1e3);
|
|
492
|
-
} catch (error) {
|
|
493
|
-
userStatusService.logger.error("Error setting user status cache:", error);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (cached.expireAt && new Date(cached.expireAt) <= /* @__PURE__ */ new Date()) {
|
|
497
|
-
const userRepo = contextDb.getRepository("users");
|
|
498
|
-
const user = await userRepo.findOne({
|
|
499
|
-
filterByTk: userId,
|
|
500
|
-
fields: ["id", "status", "statusExpireAt", "previousStatus"]
|
|
501
|
-
});
|
|
502
|
-
if (!user) {
|
|
503
|
-
return {
|
|
504
|
-
allowed: false,
|
|
505
|
-
status: "unknown",
|
|
506
|
-
errorMessage: userStatusService.app.i18n.t("User not found")
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
if (!user.statusExpireAt || new Date(user.statusExpireAt) > /* @__PURE__ */ new Date()) {
|
|
510
|
-
return userStatusService.checkUserStatus(userId);
|
|
511
|
-
}
|
|
512
|
-
await userStatusService.restoreUserStatus(userId);
|
|
513
|
-
const restoredUser = await userRepo.findOne({
|
|
514
|
-
filterByTk: userId,
|
|
515
|
-
fields: ["status"]
|
|
516
|
-
});
|
|
517
|
-
cached.status = restoredUser.status || "active";
|
|
518
|
-
cached.expireAt = null;
|
|
519
|
-
cached.previousStatus = null;
|
|
520
|
-
try {
|
|
521
|
-
const cacheKey = `userStatus:${userId}`;
|
|
522
|
-
await contextCache.set(cacheKey, JSON.stringify(cached), 300 * 1e3);
|
|
523
|
-
} catch (error) {
|
|
524
|
-
userStatusService.logger.error("Error setting user status cache:", error);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
const statusRepo = contextDb.getRepository("userStatuses");
|
|
528
|
-
const statusInfo = await statusRepo.findOne({
|
|
529
|
-
filterByTk: cached.status
|
|
530
|
-
});
|
|
531
|
-
if (!statusInfo) {
|
|
532
|
-
userStatusService.logger.warn(`Status definition not found: ${cached.status}`);
|
|
533
|
-
return {
|
|
534
|
-
allowed: false,
|
|
535
|
-
status: cached.status,
|
|
536
|
-
statusInfo: {
|
|
537
|
-
title: userStatusService.app.i18n.t("Invalid status", { ns: import_preset.namespace }),
|
|
538
|
-
color: "red",
|
|
539
|
-
allowLogin: false,
|
|
540
|
-
loginErrorMessage: userStatusService.app.i18n.t("User status is invalid, please contact administrator", {
|
|
541
|
-
ns: import_preset.namespace
|
|
542
|
-
})
|
|
543
|
-
},
|
|
544
|
-
errorMessage: userStatusService.app.i18n.t("User status is invalid, please contact administrator", {
|
|
545
|
-
ns: import_preset.namespace
|
|
546
|
-
})
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
const translateMessage = (message) => {
|
|
550
|
-
if (!message) return "";
|
|
551
|
-
const match = message.match(/\{\{t\("([^"]+)"\)\}\}/);
|
|
552
|
-
if (match && match[1]) {
|
|
553
|
-
return userStatusService.app.i18n.t(match[1], { ns: import_preset.namespace });
|
|
554
|
-
}
|
|
555
|
-
return message;
|
|
556
|
-
};
|
|
557
|
-
const translatedTitle = translateMessage(statusInfo.title);
|
|
558
|
-
const translatedLoginErrorMessage = translateMessage(statusInfo.loginErrorMessage);
|
|
559
|
-
return {
|
|
560
|
-
allowed: statusInfo.allowLogin,
|
|
561
|
-
status: cached.status,
|
|
562
|
-
statusInfo: {
|
|
563
|
-
title: translatedTitle,
|
|
564
|
-
color: statusInfo.color,
|
|
565
|
-
allowLogin: statusInfo.allowLogin,
|
|
566
|
-
loginErrorMessage: translatedLoginErrorMessage
|
|
567
|
-
},
|
|
568
|
-
errorMessage: !statusInfo.allowLogin ? translatedLoginErrorMessage || userStatusService.app.i18n.t("User status does not allow login", { ns: import_preset.namespace }) : void 0
|
|
569
|
-
};
|
|
570
|
-
} catch (error) {
|
|
571
|
-
userStatusService.logger.error(`Error checking user status for userId=${userId}: ${error}`);
|
|
572
|
-
return {
|
|
573
|
-
allowed: false,
|
|
574
|
-
status: "unknown",
|
|
575
|
-
statusInfo: {
|
|
576
|
-
title: userStatusService.app.i18n.t("Unknown status", { ns: import_preset.namespace }),
|
|
577
|
-
color: "red",
|
|
578
|
-
allowLogin: false,
|
|
579
|
-
loginErrorMessage: userStatusService.app.i18n.t("System error, please contact administrator", {
|
|
580
|
-
ns: import_preset.namespace
|
|
581
|
-
})
|
|
582
|
-
},
|
|
583
|
-
errorMessage: userStatusService.app.i18n.t("System error, please contact administrator", { ns: import_preset.namespace })
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
this.logger.info("signIn, signNewToken and check methods injected with UserStatusService integration");
|
|
259
|
+
await this.clearUserStatusCache(userId);
|
|
260
|
+
this.logger.debug(`Cache cleared for userId=${userId}`);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
this.logger.error("Failed to record status history:", error);
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
588
265
|
}
|
|
589
266
|
/**
|
|
590
267
|
* 注册用户状态变更拦截器
|
|
@@ -592,7 +269,7 @@ class UserStatusService {
|
|
|
592
269
|
*/
|
|
593
270
|
registerStatusChangeInterceptor() {
|
|
594
271
|
const userStatusService = this;
|
|
595
|
-
this.db.on("users.beforeUpdate", async (model, options) => {
|
|
272
|
+
this.app.db.on("users.beforeUpdate", async (model, options) => {
|
|
596
273
|
if (model.changed("status")) {
|
|
597
274
|
const oldStatus = model._previousDataValues.status || "active";
|
|
598
275
|
const newStatus = model.status;
|
|
@@ -615,7 +292,7 @@ class UserStatusService {
|
|
|
615
292
|
}
|
|
616
293
|
}
|
|
617
294
|
});
|
|
618
|
-
this.db.on("users.afterUpdate", async (model, options) => {
|
|
295
|
+
this.app.db.on("users.afterUpdate", async (model, options) => {
|
|
619
296
|
var _a, _b, _c, _d, _e;
|
|
620
297
|
const statusChange = (_b = (_a = options == null ? void 0 : options.transaction) == null ? void 0 : _a.__statusChange) == null ? void 0 : _b[model.id];
|
|
621
298
|
if (statusChange) {
|
|
@@ -640,7 +317,6 @@ class UserStatusService {
|
|
|
640
317
|
this.logger.info("User status change interceptor registered");
|
|
641
318
|
}
|
|
642
319
|
}
|
|
643
|
-
var user_status_default = UserStatusService;
|
|
644
320
|
// Annotate the CommonJS export names for ESM import in node:
|
|
645
321
|
0 && (module.exports = {
|
|
646
322
|
UserStatusService
|