@tachybase/module-auth 1.3.25 → 1.5.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.
@@ -2,4 +2,4 @@ import { Application } from '@tachybase/client';
2
2
  import type { AxiosResponse } from 'axios';
3
3
  export declare function authCheckMiddleware({ app }: {
4
4
  app: Application;
5
- }): ((res: AxiosResponse) => AxiosResponse<any, any>)[];
5
+ }): ((res: AxiosResponse) => AxiosResponse<any, any, {}>)[];
@@ -1,3 +1,3 @@
1
1
  export declare const NAMESPACE = "auth";
2
- export declare function useAuthTranslation(): import("react-i18next").UseTranslationResponse<[string, string], undefined>;
2
+ export declare function useAuthTranslation(): import("react-i18next").UseTranslationResponse<readonly ["auth", "core"], undefined>;
3
3
  export declare const tval: (key: string) => string;
@@ -1,14 +1,14 @@
1
1
  module.exports = {
2
2
  "react": "18.3.1",
3
- "@tachybase/client": "1.3.25",
3
+ "@tachybase/client": "1.5.0",
4
4
  "react-router-dom": "6.28.1",
5
5
  "@tego/client": "1.3.52",
6
- "axios": "1.7.7",
6
+ "axios": "1.13.0",
7
7
  "lodash": "4.17.21",
8
8
  "@tego/server": "1.3.52",
9
9
  "@tachybase/test": "1.3.52",
10
10
  "antd": "5.22.5",
11
11
  "@tachybase/schema": "1.3.52",
12
- "react-i18next": "15.2.0",
12
+ "react-i18next": "16.2.1",
13
13
  "@ant-design/icons": "5.6.1"
14
14
  };
@@ -45,6 +45,9 @@
45
45
  "User can bind or unbind the sign in type": "User can bind or unbind the sign in type",
46
46
  "User not found. Please sign in again to continue.": "User not found. Please sign in again to continue.",
47
47
  "Username/Email": "Username/Email",
48
+ "Your account has been disabled, please contact administrator if you have any questions": "Your account has been disabled, please contact administrator if you have any questions",
49
+ "Your account has been locked due to multiple failed login attempts. Please try again later.": "Your account has been locked due to multiple failed login attempts. Please try again later.",
50
+ "Your account is under review, please wait for administrator approval": "Your account is under review, please wait for administrator approval",
48
51
  "Your session has expired. Please sign in again.": "Your session has expired. Please sign in again.",
49
52
  "gitea": "Gitea",
50
53
  "sms": "SMS",
@@ -22,5 +22,8 @@
22
22
  "The phone number has been registered, please login directly": "전화번호가 이미 등록되어 있습니다. 직접 로그인하세요.",
23
23
  "The phone number is not registered, please register first": "전화번호가 등록되어 있지 않습니다. 먼저 등록하세요.",
24
24
  "The username or email is incorrect, please re-enter": "사용자 이름 또는 이메일이 잘못되었습니다. 다시 입력하세요.",
25
- "Username/Email": "사용자 이름/이메일"
25
+ "Username/Email": "사용자 이름/이메일",
26
+ "Your account has been disabled, please contact administrator if you have any questions": "Your account has been disabled, please contact administrator if you have any questions",
27
+ "Your account has been locked due to multiple failed login attempts. Please try again later.": "Your account has been locked due to multiple failed login attempts. Please try again later.",
28
+ "Your account is under review, please wait for administrator approval": "Your account is under review, please wait for administrator approval"
26
29
  }
@@ -45,6 +45,9 @@
45
45
  "User not found. Please sign in again to continue.": "用户不存在。请重新登录以继续。",
46
46
  "Username/Email": "用户名/邮箱",
47
47
  "WeChat": "微信",
48
+ "Your account has been disabled, please contact administrator if you have any questions": "您的账户已被停用,如有疑问请联系管理员",
49
+ "Your account has been locked due to multiple failed login attempts. Please try again later.": "您的账户因多次登录失败已被锁定,请稍后再试",
50
+ "Your account is under review, please wait for administrator approval": "您的账户正在审核中,请等待管理员审核通过",
48
51
  "Your session has expired. Please sign in again.": "您的会话已过期,请重新登录。",
49
52
  "gitea": "Gitea 验证登录",
50
53
  "sms": "短信验证",
@@ -1 +1 @@
1
- {"name":"cron","description":"Cron jobs for your node","version":"3.3.1","author":"Nick Campbell <nicholas.j.campbell@gmail.com> (https://github.com/ncb000gt)","bugs":{"url":"https://github.com/kelektiv/node-cron/issues"},"repository":{"type":"git","url":"https://github.com/kelektiv/node-cron.git"},"main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc -b tsconfig.build.json","lint:eslint":"eslint src/ tests/","lint:prettier":"prettier ./**/*.{json,md,yml} --check","lint":"npm run lint:eslint && npm run lint:prettier","lint:fix":"npm run lint:eslint -- --fix && npm run lint:prettier -- --write","test":"jest --coverage","test:watch":"jest --watch --coverage","test:fuzz":"jest --testMatch='**/*.fuzz.ts' --coverage=false --testTimeout=120000","prepare":"husky"},"dependencies":{"@types/luxon":"~3.4.0","luxon":"~3.5.0"},"devDependencies":{"@commitlint/cli":"19.6.0","@eslint/js":"^9.14.0","@fast-check/jest":"2.0.3","@insurgent/commitlint-config":"20.0.0","@insurgent/conventional-changelog-preset":"10.0.0","@semantic-release/changelog":"6.0.3","@semantic-release/commit-analyzer":"13.0.0","@semantic-release/git":"10.0.1","@semantic-release/github":"11.0.1","@semantic-release/npm":"12.0.1","@semantic-release/release-notes-generator":"14.0.1","@types/jest":"29.5.14","@types/node":"20.17.9","@types/sinon":"17.0.3","chai":"4.5.0","eslint":"8.57.1","eslint-config-prettier":"9.1.0","eslint-plugin-jest":"27.9.0","eslint-plugin-prettier":"5.2.1","husky":"9.1.7","jest":"29.7.0","lint-staged":"15.2.10","prettier":"3.3.3","semantic-release":"24.2.0","sinon":"17.0.2","ts-jest":"29.2.5","typescript":"5.7.2","typescript-eslint":"^7.2.0"},"keywords":["cron","node cron","node-cron","schedule","scheduler","cronjob","cron job"],"license":"MIT","contributors":["Brandon der Blätter <https://interlucid.com/contact/> (https://github.com/intcreator)","Pierre Cavin <me@sherlox.io> (https://github.com/sheerlox)","Romain Beauxis <toots@rastageeks.org> (https://github.com/toots)","James Padolsey <> (https://github.com/jamespadolsey)","Finn Herpich <fh@three-heads.de> (https://github.com/ErrorProne)","Clifton Cunningham <clifton.cunningham@gmail.com> (https://github.com/cliftonc)","Eric Abouaf <eric.abouaf@gmail.com> (https://github.com/neyric)","humanchimp <morphcham@gmail.com> (https://github.com/humanchimp)","Craig Condon <craig@spiceapps.com> (https://github.com/spiceapps)","Dan Bear <daniel@hulu.com> (https://github.com/danhbear)","Vadim Baryshev <vadimbaryshev@gmail.com> (https://github.com/baryshev)","Leandro Ferrari <lfthomaz@gmail.com> (https://github.com/lfthomaz)","Gregg Zigler <greggzigler@gmail.com> (https://github.com/greggzigler)","Jordan Abderrachid <jabderrachid@gmail.com> (https://github.com/jordanabderrachid)","Masakazu Matsushita <matsukaz@gmail.com> (matsukaz)","Christopher Lunt <me@kirisu.co.uk> (https://github.com/kirisu)"],"files":["dist/**/*.js","dist/**/*.d.ts","CHANGELOG.md","LICENSE","README.md"],"lint-staged":{"*.ts":"eslint src/ tests/ --fix","*.{json,md,yml}":"prettier ./**/*.{json,md,yml} --check --write"},"_lastModified":"2025-09-02T09:50:19.880Z"}
1
+ {"name":"cron","description":"Cron jobs for your node","version":"3.3.1","author":"Nick Campbell <nicholas.j.campbell@gmail.com> (https://github.com/ncb000gt)","bugs":{"url":"https://github.com/kelektiv/node-cron/issues"},"repository":{"type":"git","url":"https://github.com/kelektiv/node-cron.git"},"main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc -b tsconfig.build.json","lint:eslint":"eslint src/ tests/","lint:prettier":"prettier ./**/*.{json,md,yml} --check","lint":"npm run lint:eslint && npm run lint:prettier","lint:fix":"npm run lint:eslint -- --fix && npm run lint:prettier -- --write","test":"jest --coverage","test:watch":"jest --watch --coverage","test:fuzz":"jest --testMatch='**/*.fuzz.ts' --coverage=false --testTimeout=120000","prepare":"husky"},"dependencies":{"@types/luxon":"~3.4.0","luxon":"~3.5.0"},"devDependencies":{"@commitlint/cli":"19.6.0","@eslint/js":"^9.14.0","@fast-check/jest":"2.0.3","@insurgent/commitlint-config":"20.0.0","@insurgent/conventional-changelog-preset":"10.0.0","@semantic-release/changelog":"6.0.3","@semantic-release/commit-analyzer":"13.0.0","@semantic-release/git":"10.0.1","@semantic-release/github":"11.0.1","@semantic-release/npm":"12.0.1","@semantic-release/release-notes-generator":"14.0.1","@types/jest":"29.5.14","@types/node":"20.17.9","@types/sinon":"17.0.3","chai":"4.5.0","eslint":"8.57.1","eslint-config-prettier":"9.1.0","eslint-plugin-jest":"27.9.0","eslint-plugin-prettier":"5.2.1","husky":"9.1.7","jest":"29.7.0","lint-staged":"15.2.10","prettier":"3.3.3","semantic-release":"24.2.0","sinon":"17.0.2","ts-jest":"29.2.5","typescript":"5.7.2","typescript-eslint":"^7.2.0"},"keywords":["cron","node cron","node-cron","schedule","scheduler","cronjob","cron job"],"license":"MIT","contributors":["Brandon der Blätter <https://interlucid.com/contact/> (https://github.com/intcreator)","Pierre Cavin <me@sherlox.io> (https://github.com/sheerlox)","Romain Beauxis <toots@rastageeks.org> (https://github.com/toots)","James Padolsey <> (https://github.com/jamespadolsey)","Finn Herpich <fh@three-heads.de> (https://github.com/ErrorProne)","Clifton Cunningham <clifton.cunningham@gmail.com> (https://github.com/cliftonc)","Eric Abouaf <eric.abouaf@gmail.com> (https://github.com/neyric)","humanchimp <morphcham@gmail.com> (https://github.com/humanchimp)","Craig Condon <craig@spiceapps.com> (https://github.com/spiceapps)","Dan Bear <daniel@hulu.com> (https://github.com/danhbear)","Vadim Baryshev <vadimbaryshev@gmail.com> (https://github.com/baryshev)","Leandro Ferrari <lfthomaz@gmail.com> (https://github.com/lfthomaz)","Gregg Zigler <greggzigler@gmail.com> (https://github.com/greggzigler)","Jordan Abderrachid <jabderrachid@gmail.com> (https://github.com/jordanabderrachid)","Masakazu Matsushita <matsukaz@gmail.com> (matsukaz)","Christopher Lunt <me@kirisu.co.uk> (https://github.com/kirisu)"],"files":["dist/**/*.js","dist/**/*.d.ts","CHANGELOG.md","LICENSE","README.md"],"lint-staged":{"*.ts":"eslint src/ tests/ --fix","*.{json,md,yml}":"prettier ./**/*.{json,md,yml} --check --write"},"_lastModified":"2025-11-11T06:38:16.568Z"}
@@ -1 +1 @@
1
- {"name":"ms","version":"2.1.3","description":"Tiny millisecond conversion utility","repository":"vercel/ms","main":"./index","files":["index.js"],"scripts":{"precommit":"lint-staged","lint":"eslint lib/* bin/*","test":"mocha tests.js"},"eslintConfig":{"extends":"eslint:recommended","env":{"node":true,"es6":true}},"lint-staged":{"*.js":["npm run lint","prettier --single-quote --write","git add"]},"license":"MIT","devDependencies":{"eslint":"4.18.2","expect.js":"0.3.1","husky":"0.14.3","lint-staged":"5.0.0","mocha":"4.0.1","prettier":"2.0.5"},"_lastModified":"2025-09-02T09:50:19.983Z"}
1
+ {"name":"ms","version":"2.1.3","description":"Tiny millisecond conversion utility","repository":"vercel/ms","main":"./index","files":["index.js"],"scripts":{"precommit":"lint-staged","lint":"eslint lib/* bin/*","test":"mocha tests.js"},"eslintConfig":{"extends":"eslint:recommended","env":{"node":true,"es6":true}},"lint-staged":{"*.js":["npm run lint","prettier --single-quote --write","git add"]},"license":"MIT","devDependencies":{"eslint":"4.18.2","expect.js":"0.3.1","husky":"0.14.3","lint-staged":"5.0.0","mocha":"4.0.1","prettier":"2.0.5"},"_lastModified":"2025-11-11T06:38:16.668Z"}
@@ -23,6 +23,9 @@ module.exports = __toCommonJS(issued_tokens_exports);
23
23
  var import_server = require("@tego/server");
24
24
  var import_constants = require("../../constants");
25
25
  var issued_tokens_default = (0, import_server.defineCollection)({
26
+ dumpRules: {
27
+ group: "required"
28
+ },
26
29
  name: import_constants.issuedTokensCollectionName,
27
30
  autoGenId: false,
28
31
  createdAt: true,
@@ -23,7 +23,7 @@ module.exports = __toCommonJS(token_blacklist_exports);
23
23
  var import_server = require("@tego/server");
24
24
  var token_blacklist_default = (0, import_server.defineCollection)({
25
25
  dumpRules: {
26
- group: "log"
26
+ group: "user"
27
27
  },
28
28
  shared: true,
29
29
  name: "tokenBlacklist",
@@ -23,6 +23,9 @@ module.exports = __toCommonJS(token_poilcy_config_exports);
23
23
  var import_server = require("@tego/server");
24
24
  var import_constants = require("../../constants");
25
25
  var token_poilcy_config_default = (0, import_server.defineCollection)({
26
+ dumpRules: {
27
+ group: "required"
28
+ },
26
29
  name: import_constants.tokenPolicyCollectionName,
27
30
  autoGenId: false,
28
31
  createdAt: true,
@@ -6,5 +6,13 @@ declare const _default: {
6
6
  'The phone number has been registered, please login directly': string;
7
7
  'The phone number is not registered, please register first': string;
8
8
  'The username, email or password is incorrect, please re-enter': string;
9
+ 'Invalid status': string;
10
+ 'User status is invalid, please contact administrator': string;
11
+ 'User status does not allow login': string;
12
+ 'Your account is under review, please wait for administrator approval': string;
13
+ 'Your account has been disabled, please contact administrator if you have any questions': string;
14
+ 'Unknown status': string;
15
+ 'System error, please contact administrator': string;
16
+ 'Your account status has changed. Please sign in again.': string;
9
17
  };
10
18
  export default _default;
@@ -27,5 +27,13 @@ var en_US_default = {
27
27
  "Not a valid cellphone number, please re-enter": "Not a valid cellphone number, please re-enter",
28
28
  "The phone number has been registered, please login directly": "The phone number has been registered, please login directly",
29
29
  "The phone number is not registered, please register first": "The phone number is not registered, please register first",
30
- "The username, email or password is incorrect, please re-enter": "The username, email or password is incorrect, please re-enter"
30
+ "The username, email or password is incorrect, please re-enter": "The username, email or password is incorrect, please re-enter",
31
+ "Invalid status": "Invalid status",
32
+ "User status is invalid, please contact administrator": "User status is invalid, please contact administrator",
33
+ "User status does not allow login": "User status does not allow login",
34
+ "Your account is under review, please wait for administrator approval": "Your account is under review, please wait for administrator approval",
35
+ "Your account has been disabled, please contact administrator if you have any questions": "Your account has been disabled, please contact administrator if you have any questions",
36
+ "Unknown status": "Unknown status",
37
+ "System error, please contact administrator": "System error, please contact administrator",
38
+ "Your account status has changed. Please sign in again.": "Your account status has changed. Please sign in again."
31
39
  };
@@ -8,5 +8,13 @@ declare const _default: {
8
8
  'Please enter your username or email': string;
9
9
  'Please enter a valid username': string;
10
10
  'The username, email or password is incorrect, please re-enter': string;
11
+ 'Invalid status': string;
12
+ 'User status is invalid, please contact administrator': string;
13
+ 'User status does not allow login': string;
14
+ 'Your account is under review, please wait for administrator approval': string;
15
+ 'Your account has been disabled, please contact administrator if you have any questions': string;
16
+ 'Unknown status': string;
17
+ 'System error, please contact administrator': string;
18
+ 'Your account status has changed. Please sign in again.': string;
11
19
  };
12
20
  export default _default;
@@ -29,5 +29,13 @@ var zh_CN_default = {
29
29
  "Please keep and enable at least one authenticator": "\u8BF7\u81F3\u5C11\u4FDD\u7559\u5E76\u542F\u7528\u4E00\u4E2A\u8BA4\u8BC1\u5668",
30
30
  "Please enter your username or email": "\u8BF7\u8F93\u5165\u7528\u6237\u540D\u6216\u90AE\u7BB1",
31
31
  "Please enter a valid username": "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u7528\u6237\u540D",
32
- "The username, email or password is incorrect, please re-enter": "\u7528\u6237\u540D\u90AE\u7BB1\u6216\u8005\u5BC6\u7801\u6709\u8BEF,\u8BF7\u91CD\u65B0\u8F93\u5165"
32
+ "The username, email or password is incorrect, please re-enter": "\u7528\u6237\u540D\u90AE\u7BB1\u6216\u8005\u5BC6\u7801\u6709\u8BEF,\u8BF7\u91CD\u65B0\u8F93\u5165",
33
+ "Invalid status": "\u72B6\u6001\u5F02\u5E38",
34
+ "User status is invalid, please contact administrator": "\u7528\u6237\u72B6\u6001\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
35
+ "User status does not allow login": "\u7528\u6237\u72B6\u6001\u4E0D\u5141\u8BB8\u767B\u5F55",
36
+ "Your account is under review, please wait for administrator approval": "\u60A8\u7684\u8D26\u53F7\u6B63\u5728\u5BA1\u6838\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u7BA1\u7406\u5458\u6279\u51C6",
37
+ "Your account has been disabled, please contact administrator if you have any questions": "\u60A8\u7684\u8D26\u53F7\u5DF2\u88AB\u7981\u7528\uFF0C\u5982\u6709\u7591\u95EE\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
38
+ "Unknown status": "\u672A\u77E5\u72B6\u6001",
39
+ "System error, please contact administrator": "\u7CFB\u7EDF\u9519\u8BEF\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458",
40
+ "Your account status has changed. Please sign in again.": "\u60A8\u7684\u8D26\u53F7\u72B6\u6001\u5DF2\u53D8\u66F4\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55"
33
41
  };
@@ -1,6 +1,8 @@
1
1
  import { Cache, InstallOptions, Plugin } from '@tego/server';
2
+ import { UserStatusService } from './user-status';
2
3
  export declare class PluginAuthServer extends Plugin {
3
4
  cache: Cache;
5
+ userStatusService: UserStatusService;
4
6
  afterAdd(): void;
5
7
  beforeLoad(): Promise<void>;
6
8
  load(): Promise<void>;
@@ -42,6 +42,7 @@ var import_authenticator = require("./model/authenticator");
42
42
  var import_storer = require("./storer");
43
43
  var import_token_blacklist = require("./token-blacklist");
44
44
  var import_token_controller = require("./token-controller");
45
+ var import_user_status = require("./user-status");
45
46
  class PluginAuthServer extends import_server.Plugin {
46
47
  afterAdd() {
47
48
  this.app.on("afterLoad", async () => {
@@ -71,6 +72,10 @@ class PluginAuthServer extends import_server.Plugin {
71
72
  this.app.db.registerModels({ AuthModel: import_authenticator.AuthModel });
72
73
  }
73
74
  async load() {
75
+ this.userStatusService = new import_user_status.UserStatusService(this.app);
76
+ this.app.userStatusService = this.userStatusService;
77
+ this.userStatusService.injectLoginCheck();
78
+ this.userStatusService.registerStatusChangeInterceptor();
74
79
  this.cache = await this.app.cacheManager.createCache({
75
80
  name: "auth",
76
81
  prefix: "auth",
@@ -0,0 +1,106 @@
1
+ import { Application } from '@tego/server';
2
+ /**
3
+ * 用户状态检查结果
4
+ */
5
+ export interface UserStatusCheckResult {
6
+ allowed: boolean;
7
+ status: string;
8
+ statusInfo?: {
9
+ title: string;
10
+ color: string;
11
+ allowLogin: boolean;
12
+ loginErrorMessage?: string;
13
+ };
14
+ errorMessage?: string;
15
+ isExpired?: boolean;
16
+ }
17
+ /**
18
+ * 修改用户状态的选项
19
+ */
20
+ export interface ChangeUserStatusOptions {
21
+ expireAt?: Date;
22
+ reason?: string;
23
+ operationType: 'manual' | 'auto' | 'system';
24
+ operatorId?: number;
25
+ }
26
+ /**
27
+ * 状态注册配置
28
+ */
29
+ export interface StatusConfig {
30
+ key: string;
31
+ title: string;
32
+ color?: string;
33
+ allowLogin: boolean;
34
+ loginErrorMessage?: string;
35
+ packageName: string;
36
+ description?: string;
37
+ sort?: number;
38
+ config?: Record<string, any>;
39
+ }
40
+ declare module '@tego/server' {
41
+ interface BaseAuth {
42
+ checkUserStatusInContext?(userId: number): Promise<UserStatusCheckResult>;
43
+ }
44
+ }
45
+ /**
46
+ * 用户状态管理服务
47
+ * 负责用户状态的检查、变更、恢复等核心业务逻辑
48
+ */
49
+ export declare class UserStatusService {
50
+ private db;
51
+ private app;
52
+ private cache;
53
+ private logger;
54
+ constructor(app: Application);
55
+ /**
56
+ * 获取用户状态缓存键
57
+ */
58
+ private getUserStatusCacheKey;
59
+ /**
60
+ * 从缓存获取用户状态
61
+ */
62
+ private getUserStatusFromCache;
63
+ /**
64
+ * 设置用户状态缓存
65
+ */
66
+ private setUserStatusCache;
67
+ /**
68
+ * 清除用户状态缓存
69
+ */
70
+ private clearUserStatusCache;
71
+ /**
72
+ * 检查用户状态是否允许登录
73
+ * @param userId 用户ID
74
+ * @returns 检查结果
75
+ */
76
+ checkUserStatus(userId: number): Promise<UserStatusCheckResult>;
77
+ /**
78
+ * 记录状态变更历史(如果不存在相同记录)
79
+ * @param params 状态变更参数
80
+ */
81
+ private recordStatusHistoryIfNotExists;
82
+ /**
83
+ * 恢复过期的用户状态
84
+ * @param userId 用户ID
85
+ */
86
+ restoreUserStatus(userId: number): Promise<void>;
87
+ /**
88
+ * 注册新的用户状态(供插件使用)
89
+ * @param config 状态配置
90
+ */
91
+ registerStatus(config: StatusConfig): Promise<void>;
92
+ /**
93
+ * 获取每个状态的用户数量统计
94
+ */
95
+ getStatusStatistics(): Promise<Record<string, number>>;
96
+ /**
97
+ * 注入登录检查
98
+ */
99
+ injectLoginCheck(): void;
100
+ /**
101
+ * 注册用户状态变更拦截器
102
+ * 拦截 users:update 请求,自动记录状态变更历史
103
+ */
104
+ registerStatusChangeInterceptor(): void;
105
+ }
106
+ export default UserStatusService;
@@ -0,0 +1,654 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var user_status_exports = {};
19
+ __export(user_status_exports, {
20
+ UserStatusService: () => UserStatusService,
21
+ default: () => user_status_default
22
+ });
23
+ module.exports = __toCommonJS(user_status_exports);
24
+ var import_server = require("@tego/server");
25
+ var import_preset = require("../preset");
26
+ class UserStatusService {
27
+ constructor(app) {
28
+ this.app = app;
29
+ this.db = app.db;
30
+ this.cache = app.cache;
31
+ this.logger = app.logger;
32
+ }
33
+ /**
34
+ * 获取用户状态缓存键
35
+ */
36
+ getUserStatusCacheKey(userId) {
37
+ return `userStatus:${userId}`;
38
+ }
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
+ }
51
+ }
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
+ }
62
+ }
63
+ /**
64
+ * 清除用户状态缓存
65
+ */
66
+ async clearUserStatusCache(userId) {
67
+ try {
68
+ const cacheKey = this.getUserStatusCacheKey(userId);
69
+ await this.cache.del(cacheKey);
70
+ } catch (error) {
71
+ this.logger.error("Error clearing user status cache:", error);
72
+ }
73
+ }
74
+ /**
75
+ * 检查用户状态是否允许登录
76
+ * @param userId 用户ID
77
+ * @returns 检查结果
78
+ */
79
+ async checkUserStatus(userId) {
80
+ try {
81
+ let cached = await this.getUserStatusFromCache(userId);
82
+ if (!cached) {
83
+ const userRepo = this.db.getRepository("users");
84
+ const user = await userRepo.findOne({
85
+ filterByTk: userId,
86
+ fields: ["id", "status", "statusExpireAt", "previousStatus"]
87
+ });
88
+ if (!user) {
89
+ return {
90
+ allowed: false,
91
+ status: "unknown",
92
+ errorMessage: this.app.i18n.t("User not found")
93
+ };
94
+ }
95
+ cached = {
96
+ userId: user.id,
97
+ status: user.status || "active",
98
+ expireAt: user.statusExpireAt,
99
+ previousStatus: user.previousStatus,
100
+ lastChecked: /* @__PURE__ */ new Date()
101
+ };
102
+ await this.setUserStatusCache(userId, cached);
103
+ }
104
+ if (cached.expireAt && new Date(cached.expireAt) <= /* @__PURE__ */ new Date()) {
105
+ await this.restoreUserStatus(userId);
106
+ const userRepo = this.db.getRepository("users");
107
+ const user = await userRepo.findOne({
108
+ filterByTk: userId,
109
+ fields: ["status"]
110
+ });
111
+ cached.status = user.status || "active";
112
+ cached.expireAt = null;
113
+ cached.previousStatus = null;
114
+ await this.setUserStatusCache(userId, cached);
115
+ }
116
+ const statusRepo = this.db.getRepository("userStatuses");
117
+ const statusInfo = await statusRepo.findOne({
118
+ filterByTk: cached.status
119
+ });
120
+ if (!statusInfo) {
121
+ this.logger.warn(`Status definition not found: ${cached.status}`);
122
+ return {
123
+ allowed: false,
124
+ status: cached.status,
125
+ statusInfo: {
126
+ title: this.app.i18n.t("Invalid status", { ns: import_preset.namespace }),
127
+ color: "red",
128
+ allowLogin: false,
129
+ loginErrorMessage: this.app.i18n.t("User status is invalid, please contact administrator", {
130
+ ns: import_preset.namespace
131
+ })
132
+ },
133
+ errorMessage: this.app.i18n.t("User status is invalid, please contact administrator", { ns: import_preset.namespace })
134
+ };
135
+ }
136
+ const translateMessage = (message) => {
137
+ if (!message) return "";
138
+ const match = message.match(/\{\{t\("([^"]+)"\)\}\}/);
139
+ if (match && match[1]) {
140
+ return this.app.i18n.t(match[1], { ns: import_preset.namespace });
141
+ }
142
+ return message;
143
+ };
144
+ const translatedTitle = translateMessage(statusInfo.title);
145
+ const translatedLoginErrorMessage = translateMessage(statusInfo.loginErrorMessage);
146
+ return {
147
+ allowed: statusInfo.allowLogin,
148
+ status: cached.status,
149
+ statusInfo: {
150
+ title: translatedTitle,
151
+ color: statusInfo.color,
152
+ allowLogin: statusInfo.allowLogin,
153
+ loginErrorMessage: translatedLoginErrorMessage
154
+ },
155
+ errorMessage: !statusInfo.allowLogin ? translatedLoginErrorMessage || this.app.i18n.t("User status does not allow login", { ns: import_preset.namespace }) : void 0
156
+ };
157
+ } catch (error) {
158
+ this.logger.error(`Error checking user status for userId=${userId}: ${error}`);
159
+ return {
160
+ allowed: false,
161
+ status: "unknown",
162
+ statusInfo: {
163
+ title: this.app.i18n.t("Unknown status", { ns: import_preset.namespace }),
164
+ color: "red",
165
+ allowLogin: false,
166
+ loginErrorMessage: this.app.i18n.t("System error, please contact administrator", { ns: import_preset.namespace })
167
+ },
168
+ errorMessage: this.app.i18n.t("System error, please contact administrator", { ns: import_preset.namespace })
169
+ };
170
+ }
171
+ }
172
+ /**
173
+ * 记录状态变更历史(如果不存在相同记录)
174
+ * @param params 状态变更参数
175
+ */
176
+ async recordStatusHistoryIfNotExists(params) {
177
+ const { userId, fromStatus, toStatus, reason, expireAt, operationType, createdBy, transaction } = params;
178
+ try {
179
+ const historyRepo = this.db.getRepository("userStatusHistories");
180
+ const fiveSecondsAgo = new Date(Date.now() - 5e3);
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}`);
216
+ } catch (error) {
217
+ this.logger.error("Failed to record status history:", error);
218
+ throw error;
219
+ }
220
+ }
221
+ /**
222
+ * 恢复过期的用户状态
223
+ * @param userId 用户ID
224
+ */
225
+ async restoreUserStatus(userId) {
226
+ try {
227
+ const userRepo = this.db.getRepository("users");
228
+ const user = await userRepo.findOne({
229
+ filterByTk: userId,
230
+ fields: ["id", "status", "statusExpireAt", "previousStatus"]
231
+ });
232
+ if (!user) {
233
+ throw new Error(this.app.i18n.t("User not found"));
234
+ }
235
+ if (!user.statusExpireAt || new Date(user.statusExpireAt) > /* @__PURE__ */ new Date()) {
236
+ return;
237
+ }
238
+ const oldStatus = user.status;
239
+ const restoreToStatus = user.previousStatus || "active";
240
+ await this.db.sequelize.transaction(async (transaction) => {
241
+ await this.recordStatusHistoryIfNotExists({
242
+ userId,
243
+ fromStatus: oldStatus,
244
+ toStatus: restoreToStatus,
245
+ reason: this.app.i18n.t("Status expired, auto restored"),
246
+ operationType: "auto",
247
+ createdBy: null,
248
+ expireAt: null,
249
+ transaction
250
+ });
251
+ await userRepo.update({
252
+ filterByTk: userId,
253
+ values: {
254
+ status: restoreToStatus,
255
+ statusExpireAt: null,
256
+ previousStatus: null,
257
+ statusReason: this.app.i18n.t("Status expired, auto restored")
258
+ },
259
+ transaction
260
+ });
261
+ });
262
+ this.app.emitAsync("user:statusRestored", {
263
+ userId,
264
+ fromStatus: oldStatus,
265
+ toStatus: restoreToStatus
266
+ });
267
+ this.logger.info(`User status auto restored: userId=${userId}, ${oldStatus} \u2192 ${restoreToStatus}`);
268
+ } catch (error) {
269
+ this.logger.error("Error restoring user status:", error);
270
+ throw error;
271
+ }
272
+ }
273
+ /**
274
+ * 注册新的用户状态(供插件使用)
275
+ * @param config 状态配置
276
+ */
277
+ async registerStatus(config) {
278
+ try {
279
+ const statusRepo = this.db.getRepository("userStatuses");
280
+ const existing = await statusRepo.findOne({
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}`);
319
+ } catch (error) {
320
+ this.logger.error("Error registering status:", error);
321
+ throw error;
322
+ }
323
+ }
324
+ /**
325
+ * 获取每个状态的用户数量统计
326
+ */
327
+ async getStatusStatistics() {
328
+ try {
329
+ const userRepo = this.db.getRepository("users");
330
+ const result = await userRepo.find({
331
+ attributes: ["status", [this.db.sequelize.fn("COUNT", "id"), "count"]],
332
+ group: ["status"],
333
+ raw: true
334
+ });
335
+ const statistics = {};
336
+ for (const row of result) {
337
+ statistics[row.status] = parseInt(row.count, 10);
338
+ }
339
+ return statistics;
340
+ } catch (error) {
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
+ {
360
+ userId,
361
+ userStatus,
362
+ // 将用户状态写入 JWT payload
363
+ temp: true,
364
+ iat: Math.floor(tokenInfo.issuedTime / 1e3),
365
+ signInTime: tokenInfo.signInTime
366
+ },
367
+ {
368
+ jwtid: tokenInfo.jti,
369
+ expiresIn
370
+ }
371
+ );
372
+ userStatusService.logger.debug(`[signNewToken] userId=${userId}, userStatus=${userStatus}, jti=${tokenInfo.jti}`);
373
+ return token;
374
+ };
375
+ import_server.BaseAuth.prototype.signIn = async function() {
376
+ let user;
377
+ try {
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;
416
+ const currentUserStatus = user.status || "active";
417
+ if (!tokenUserStatus) {
418
+ userStatusService.logger.warn(`[check] Token missing userStatus, userId=${user.id}, forcing re-login`);
419
+ this.ctx.throw(401, {
420
+ message: this.ctx.t("Your account status has changed. Please sign in again.", { ns: import_preset.namespace }),
421
+ code: import_server.AuthErrorCode.INVALID_TOKEN
422
+ });
423
+ }
424
+ if (tokenUserStatus !== currentUserStatus) {
425
+ userStatusService.logger.warn(
426
+ `[check] Status mismatch, userId=${user.id}, token=${tokenUserStatus}, current=${currentUserStatus}`
427
+ );
428
+ this.ctx.throw(401, {
429
+ message: this.ctx.t("Your account status has changed. Please sign in again.", { ns: import_preset.namespace }),
430
+ code: import_server.AuthErrorCode.INVALID_TOKEN
431
+ });
432
+ }
433
+ const statusCheckResult = await this.checkUserStatusInContext(user.id);
434
+ if (!statusCheckResult.allowed) {
435
+ userStatusService.logger.warn(`[check] Status not allowed, userId=${user.id}, status=${currentUserStatus}`);
436
+ this.ctx.throw(403, {
437
+ message: this.ctx.t(statusCheckResult.statusInfo.loginErrorMessage, { ns: import_preset.namespace }),
438
+ code: import_server.AuthErrorCode.INVALID_TOKEN
439
+ });
440
+ }
441
+ userStatusService.logger.debug(`[check] Passed, userId=${user.id}, status=${currentUserStatus}`);
442
+ } catch (err) {
443
+ if (err.status) {
444
+ throw err;
445
+ }
446
+ userStatusService.logger.error("[check] Token validation error:", err);
447
+ throw err;
448
+ }
449
+ return user;
450
+ };
451
+ import_server.BaseAuth.prototype.checkUserStatusInContext = async function(userId) {
452
+ try {
453
+ if (!userStatusService) {
454
+ throw new Error("userStatusService is undefined");
455
+ }
456
+ if (!userStatusService.app) {
457
+ throw new Error("userStatusService.app is undefined");
458
+ }
459
+ if (!userStatusService.app.db) {
460
+ throw new Error("userStatusService.app.db is undefined");
461
+ }
462
+ if (!userStatusService.app.cache) {
463
+ throw new Error("userStatusService.app.cache is undefined");
464
+ }
465
+ const contextDb = userStatusService.app.db;
466
+ const contextCache = userStatusService.app.cache;
467
+ let cached = null;
468
+ try {
469
+ const cacheKey = `userStatus:${userId}`;
470
+ const cachedData = await contextCache.get(cacheKey);
471
+ cached = cachedData ? JSON.parse(cachedData) : null;
472
+ } catch (error) {
473
+ userStatusService.logger.error("Error getting user status from cache:", error);
474
+ cached = null;
475
+ }
476
+ if (!cached) {
477
+ const userRepo = contextDb.getRepository("users");
478
+ const user = await userRepo.findOne({
479
+ filterByTk: userId,
480
+ fields: ["id", "status", "statusExpireAt", "previousStatus"]
481
+ });
482
+ if (!user) {
483
+ return {
484
+ allowed: false,
485
+ status: "unknown",
486
+ errorMessage: userStatusService.app.i18n.t("User not found")
487
+ };
488
+ }
489
+ cached = {
490
+ userId: user.id,
491
+ status: user.status || "active",
492
+ expireAt: user.statusExpireAt,
493
+ previousStatus: user.previousStatus,
494
+ lastChecked: /* @__PURE__ */ new Date()
495
+ };
496
+ try {
497
+ const cacheKey = `userStatus:${userId}`;
498
+ await contextCache.set(cacheKey, JSON.stringify(cached), 300 * 1e3);
499
+ } catch (error) {
500
+ userStatusService.logger.error("Error setting user status cache:", error);
501
+ }
502
+ }
503
+ if (cached.expireAt && new Date(cached.expireAt) <= /* @__PURE__ */ new Date()) {
504
+ const userRepo = contextDb.getRepository("users");
505
+ const user = await userRepo.findOne({
506
+ filterByTk: userId,
507
+ fields: ["id", "status", "statusExpireAt", "previousStatus"]
508
+ });
509
+ if (!user) {
510
+ return {
511
+ allowed: false,
512
+ status: "unknown",
513
+ errorMessage: userStatusService.app.i18n.t("User not found")
514
+ };
515
+ }
516
+ if (!user.statusExpireAt || new Date(user.statusExpireAt) > /* @__PURE__ */ new Date()) {
517
+ return userStatusService.checkUserStatus(userId);
518
+ }
519
+ await userStatusService.restoreUserStatus(userId);
520
+ const restoredUser = await userRepo.findOne({
521
+ filterByTk: userId,
522
+ fields: ["status"]
523
+ });
524
+ cached.status = restoredUser.status || "active";
525
+ cached.expireAt = null;
526
+ cached.previousStatus = null;
527
+ try {
528
+ const cacheKey = `userStatus:${userId}`;
529
+ await contextCache.set(cacheKey, JSON.stringify(cached), 300 * 1e3);
530
+ } catch (error) {
531
+ userStatusService.logger.error("Error setting user status cache:", error);
532
+ }
533
+ }
534
+ const statusRepo = contextDb.getRepository("userStatuses");
535
+ const statusInfo = await statusRepo.findOne({
536
+ filterByTk: cached.status
537
+ });
538
+ if (!statusInfo) {
539
+ userStatusService.logger.warn(`Status definition not found: ${cached.status}`);
540
+ return {
541
+ allowed: false,
542
+ status: cached.status,
543
+ statusInfo: {
544
+ title: userStatusService.app.i18n.t("Invalid status", { ns: import_preset.namespace }),
545
+ color: "red",
546
+ allowLogin: false,
547
+ loginErrorMessage: userStatusService.app.i18n.t("User status is invalid, please contact administrator", {
548
+ ns: import_preset.namespace
549
+ })
550
+ },
551
+ errorMessage: userStatusService.app.i18n.t("User status is invalid, please contact administrator", {
552
+ ns: import_preset.namespace
553
+ })
554
+ };
555
+ }
556
+ const translateMessage = (message) => {
557
+ if (!message) return "";
558
+ const match = message.match(/\{\{t\("([^"]+)"\)\}\}/);
559
+ if (match && match[1]) {
560
+ return userStatusService.app.i18n.t(match[1], { ns: import_preset.namespace });
561
+ }
562
+ return message;
563
+ };
564
+ const translatedTitle = translateMessage(statusInfo.title);
565
+ const translatedLoginErrorMessage = translateMessage(statusInfo.loginErrorMessage);
566
+ return {
567
+ allowed: statusInfo.allowLogin,
568
+ status: cached.status,
569
+ statusInfo: {
570
+ title: translatedTitle,
571
+ color: statusInfo.color,
572
+ allowLogin: statusInfo.allowLogin,
573
+ loginErrorMessage: translatedLoginErrorMessage
574
+ },
575
+ errorMessage: !statusInfo.allowLogin ? translatedLoginErrorMessage || userStatusService.app.i18n.t("User status does not allow login", { ns: import_preset.namespace }) : void 0
576
+ };
577
+ } catch (error) {
578
+ userStatusService.logger.error(`Error checking user status for userId=${userId}: ${error}`);
579
+ return {
580
+ allowed: false,
581
+ status: "unknown",
582
+ statusInfo: {
583
+ title: userStatusService.app.i18n.t("Unknown status", { ns: import_preset.namespace }),
584
+ color: "red",
585
+ allowLogin: false,
586
+ loginErrorMessage: userStatusService.app.i18n.t("System error, please contact administrator", {
587
+ ns: import_preset.namespace
588
+ })
589
+ },
590
+ errorMessage: userStatusService.app.i18n.t("System error, please contact administrator", { ns: import_preset.namespace })
591
+ };
592
+ }
593
+ };
594
+ this.logger.info("signIn, signNewToken and check methods injected with UserStatusService integration");
595
+ }
596
+ /**
597
+ * 注册用户状态变更拦截器
598
+ * 拦截 users:update 请求,自动记录状态变更历史
599
+ */
600
+ registerStatusChangeInterceptor() {
601
+ const userStatusService = this;
602
+ this.db.on("users.beforeUpdate", async (model, options) => {
603
+ if (model.changed("status")) {
604
+ const oldStatus = model._previousDataValues.status || "active";
605
+ const newStatus = model.status;
606
+ if (oldStatus !== newStatus) {
607
+ model.set("previousStatus", oldStatus);
608
+ if (!options.transaction) {
609
+ options.transaction = {};
610
+ }
611
+ if (!options.transaction.__statusChange) {
612
+ options.transaction.__statusChange = {};
613
+ }
614
+ options.transaction.__statusChange[model.id] = {
615
+ userId: model.id,
616
+ fromStatus: oldStatus,
617
+ toStatus: newStatus,
618
+ reason: model.statusReason || null,
619
+ expireAt: model.statusExpireAt || null
620
+ };
621
+ userStatusService.logger.debug(`Status change: userId=${model.id}, ${oldStatus} \u2192 ${newStatus}`);
622
+ }
623
+ }
624
+ });
625
+ this.db.on("users.afterUpdate", async (model, options) => {
626
+ var _a, _b, _c, _d, _e;
627
+ const statusChange = (_b = (_a = options == null ? void 0 : options.transaction) == null ? void 0 : _a.__statusChange) == null ? void 0 : _b[model.id];
628
+ if (statusChange) {
629
+ try {
630
+ const currentUserId = (_e = (_d = (_c = options == null ? void 0 : options.context) == null ? void 0 : _c.state) == null ? void 0 : _d.currentUser) == null ? void 0 : _e.id;
631
+ await userStatusService.recordStatusHistoryIfNotExists({
632
+ userId: statusChange.userId,
633
+ fromStatus: statusChange.fromStatus,
634
+ toStatus: statusChange.toStatus,
635
+ reason: statusChange.reason,
636
+ expireAt: statusChange.expireAt,
637
+ operationType: "manual",
638
+ // 通过 API 修改的都是手动操作
639
+ createdBy: currentUserId,
640
+ transaction: options.transaction
641
+ });
642
+ } catch (error) {
643
+ userStatusService.logger.error("Failed to record status history:", error);
644
+ }
645
+ }
646
+ });
647
+ this.logger.info("User status change interceptor registered");
648
+ }
649
+ }
650
+ var user_status_default = UserStatusService;
651
+ // Annotate the CommonJS export names for ESM import in node:
652
+ 0 && (module.exports = {
653
+ UserStatusService
654
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tachybase/module-auth",
3
3
  "displayName": "Authentication",
4
- "version": "1.3.25",
4
+ "version": "1.5.0",
5
5
  "description": "User authentication management, including password, SMS, and support for Single Sign-On (SSO) protocols, with extensibility.",
6
6
  "keywords": [
7
7
  "Authentication",
@@ -16,14 +16,14 @@
16
16
  "@tego/client": "*",
17
17
  "@tego/server": "*",
18
18
  "antd": "5.22.5",
19
- "axios": "1.7.7",
19
+ "axios": "1.13.0",
20
20
  "cron": "^3.3.1",
21
21
  "lodash": "4.17.21",
22
22
  "ms": "^2.1.3",
23
23
  "react": "18.3.1",
24
- "react-i18next": "15.2.0",
24
+ "react-i18next": "16.2.1",
25
25
  "react-router-dom": "6.28.1",
26
- "@tachybase/client": "1.3.25"
26
+ "@tachybase/client": "1.5.0"
27
27
  },
28
28
  "description.zh-CN": "用户认证管理,包括基础的密码认证、短信认证、SSO 协议的认证等,可扩展。",
29
29
  "displayName.zh-CN": "用户认证",