@tachybase/plugin-password-policy 1.0.6

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.
Files changed (77) hide show
  1. package/README.md +1 -0
  2. package/client.d.ts +2 -0
  3. package/client.js +1 -0
  4. package/dist/client/IPFilterForm.d.ts +1 -0
  5. package/dist/client/PasswordAttemptForm.d.ts +1 -0
  6. package/dist/client/PasswordStrengthSettingsForm.d.ts +2 -0
  7. package/dist/client/SignInFailsTable.d.ts +2 -0
  8. package/dist/client/UserLocksTable.d.ts +2 -0
  9. package/dist/client/collections/signInFails.d.ts +2 -0
  10. package/dist/client/collections/userLocks.d.ts +2 -0
  11. package/dist/client/hooks/usePasswordStrength.d.ts +11 -0
  12. package/dist/client/hooks/usePasswordValidator.d.ts +16 -0
  13. package/dist/client/index.d.ts +5 -0
  14. package/dist/client/index.js +4 -0
  15. package/dist/client/locale.d.ts +6 -0
  16. package/dist/constants.d.ts +11 -0
  17. package/dist/constants.js +44 -0
  18. package/dist/externalVersion.js +10 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +39 -0
  21. package/dist/locale/en-US.json +107 -0
  22. package/dist/locale/zh-CN.json +107 -0
  23. package/dist/node_modules/geoip-lite/LICENSE +50 -0
  24. package/dist/node_modules/geoip-lite/data/city.checksum +1 -0
  25. package/dist/node_modules/geoip-lite/data/country.checksum +1 -0
  26. package/dist/node_modules/geoip-lite/data/geoip-city-names.dat +0 -0
  27. package/dist/node_modules/geoip-lite/data/geoip-city.dat +0 -0
  28. package/dist/node_modules/geoip-lite/data/geoip-city6.dat +0 -0
  29. package/dist/node_modules/geoip-lite/data/geoip-country.dat +0 -0
  30. package/dist/node_modules/geoip-lite/data/geoip-country6.dat +0 -0
  31. package/dist/node_modules/geoip-lite/lib/fsWatcher.js +83 -0
  32. package/dist/node_modules/geoip-lite/lib/geoip.js +1 -0
  33. package/dist/node_modules/geoip-lite/lib/utils.js +98 -0
  34. package/dist/node_modules/geoip-lite/node_modules/.bin/rimraf +17 -0
  35. package/dist/node_modules/geoip-lite/package.json +1 -0
  36. package/dist/node_modules/geoip-lite/scripts/updatedb.js +685 -0
  37. package/dist/node_modules/geoip-lite/test/geo-lookup.js +56 -0
  38. package/dist/node_modules/geoip-lite/test/memory_usage.js +3 -0
  39. package/dist/node_modules/geoip-lite/test/tests.js +197 -0
  40. package/dist/server/actions/IpFilterController.d.ts +7 -0
  41. package/dist/server/actions/IpFilterController.js +124 -0
  42. package/dist/server/actions/PasswordAttemptController.d.ts +7 -0
  43. package/dist/server/actions/PasswordAttemptController.js +123 -0
  44. package/dist/server/actions/PasswordStrengthController.d.ts +7 -0
  45. package/dist/server/actions/PasswordStrengthController.js +123 -0
  46. package/dist/server/actions/SignInFailsController.d.ts +5 -0
  47. package/dist/server/actions/SignInFailsController.js +156 -0
  48. package/dist/server/actions/UserLocksController.d.ts +4 -0
  49. package/dist/server/actions/UserLocksController.js +102 -0
  50. package/dist/server/collections/ipFilter.d.ts +2 -0
  51. package/dist/server/collections/ipFilter.js +51 -0
  52. package/dist/server/collections/passwordAttempt.d.ts +2 -0
  53. package/dist/server/collections/passwordAttempt.js +55 -0
  54. package/dist/server/collections/passwordHistory.d.ts +2 -0
  55. package/dist/server/collections/passwordHistory.js +46 -0
  56. package/dist/server/collections/passwordStrengthConfig.d.ts +2 -0
  57. package/dist/server/collections/passwordStrengthConfig.js +59 -0
  58. package/dist/server/collections/signInFail.d.ts +2 -0
  59. package/dist/server/collections/signInFail.js +56 -0
  60. package/dist/server/collections/userLocks.d.ts +2 -0
  61. package/dist/server/collections/userLocks.js +51 -0
  62. package/dist/server/collections/users.d.ts +2 -0
  63. package/dist/server/collections/users.js +42 -0
  64. package/dist/server/index.d.ts +1 -0
  65. package/dist/server/index.js +33 -0
  66. package/dist/server/plugin.d.ts +5 -0
  67. package/dist/server/plugin.js +129 -0
  68. package/dist/server/services/IPFilterService.d.ts +49 -0
  69. package/dist/server/services/IPFilterService.js +270 -0
  70. package/dist/server/services/PasswordAttemptService.d.ts +75 -0
  71. package/dist/server/services/PasswordAttemptService.js +595 -0
  72. package/dist/server/services/PasswordStrengthService.d.ts +28 -0
  73. package/dist/server/services/PasswordStrengthService.js +313 -0
  74. package/dist/types/geoip-lite.d.js +0 -0
  75. package/package.json +25 -0
  76. package/server.d.ts +2 -0
  77. package/server.js +1 -0
@@ -0,0 +1,313 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
7
+ var __typeError = (msg) => {
8
+ throw TypeError(msg);
9
+ };
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+ var __decoratorStart = (base) => [, , , __create((base == null ? void 0 : base[__knownSymbol("metadata")]) ?? null)];
26
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
27
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
28
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
29
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
30
+ var __runInitializers = (array, flags, self, value) => {
31
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
32
+ return value;
33
+ };
34
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
35
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
36
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
37
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
38
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
39
+ return __privateGet(this, extra);
40
+ }, set [name](x) {
41
+ return __privateSet(this, extra, x);
42
+ } }, name));
43
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
44
+ for (var i = decorators.length - 1; i >= 0; i--) {
45
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
46
+ if (k) {
47
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
48
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
49
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
50
+ }
51
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
52
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
53
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
54
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
55
+ }
56
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
57
+ };
58
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
59
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
60
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
61
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
62
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
63
+ var PasswordStrengthService_exports = {};
64
+ __export(PasswordStrengthService_exports, {
65
+ PasswordStrengthService: () => PasswordStrengthService
66
+ });
67
+ module.exports = __toCommonJS(PasswordStrengthService_exports);
68
+ var import_utils = require("@tachybase/utils");
69
+ var import_constants = require("../../constants");
70
+ var _logger_dec, _app_dec, _db_dec, _PasswordStrengthService_decorators, _init;
71
+ _PasswordStrengthService_decorators = [(0, import_utils.Service)()], _db_dec = [(0, import_utils.Db)()], _app_dec = [(0, import_utils.App)()], _logger_dec = [(0, import_utils.InjectLog)()];
72
+ class PasswordStrengthService {
73
+ constructor() {
74
+ this.db = __runInitializers(_init, 8, this), __runInitializers(_init, 11, this);
75
+ this.app = __runInitializers(_init, 12, this), __runInitializers(_init, 15, this);
76
+ this.logger = __runInitializers(_init, 16, this), __runInitializers(_init, 19, this);
77
+ this.config = void 0;
78
+ }
79
+ async load() {
80
+ this.addMiddleware();
81
+ this.app.on("afterStart", async () => {
82
+ const config = await this.db.getRepository("passwordStrengthConfig").findOne();
83
+ await this.refreshConfig(config);
84
+ });
85
+ this.db.on("passwordStrengthConfig.afterSave", async (model) => {
86
+ await this.refreshConfig(model);
87
+ });
88
+ }
89
+ async refreshConfig(config) {
90
+ this.config = {
91
+ minLength: config == null ? void 0 : config.get("minLength"),
92
+ strengthLevel: (config == null ? void 0 : config.get("strengthLevel")) || 0,
93
+ notContainUsername: (config == null ? void 0 : config.get("notContainUsername")) || false,
94
+ historyCount: (config == null ? void 0 : config.get("historyCount")) || 0
95
+ };
96
+ this.logger.info("Password strength configuration loaded:", this.config);
97
+ }
98
+ addMiddleware() {
99
+ this.app.resourcer.use(
100
+ async (ctx, next) => {
101
+ var _a, _b, _c;
102
+ const { resourceName, actionName } = ctx.action.params;
103
+ if (resourceName === "users" && (actionName === "create" || actionName === "update")) {
104
+ const values = ctx.action.params.values;
105
+ if (values && values.password) {
106
+ const username = values.username || await this.getUsernameById(ctx, values.id);
107
+ await this.validatePasswordStrength(ctx, values.password, username);
108
+ if (actionName === "update" && values.id && this.config.historyCount > 0) {
109
+ await this.validatePasswordHistory(ctx, values.password, values.id);
110
+ }
111
+ }
112
+ } else if (resourceName === "auth" && actionName === "signUp") {
113
+ const { username, password } = ctx.action.params.values;
114
+ await this.validatePasswordStrength(ctx, password, username);
115
+ } else if (resourceName === "auth" && actionName === "changePassword") {
116
+ const { newPassword } = ctx.action.params.values;
117
+ await this.validatePasswordStrength(ctx, newPassword, (_a = ctx.state.currentUser) == null ? void 0 : _a.username);
118
+ await this.validatePasswordHistory(ctx, newPassword, (_b = ctx.state.currentUser) == null ? void 0 : _b.id);
119
+ }
120
+ await next();
121
+ if (resourceName === "users" && actionName === "update" && ctx.action.params.values.password || resourceName === "auth" && actionName === "changePassword") {
122
+ const userId = ctx.action.params.values.id || ((_c = ctx.state.currentUser) == null ? void 0 : _c.id);
123
+ const password = ctx.action.params.values.password || ctx.action.params.values.newPassword;
124
+ if (userId && password && this.config.historyCount > 0) {
125
+ await this.savePasswordHistory(userId, password);
126
+ }
127
+ }
128
+ },
129
+ {
130
+ tag: "passwordStrengthValidator",
131
+ after: "acl"
132
+ }
133
+ );
134
+ }
135
+ /**
136
+ * 获取用户名通过ID
137
+ */
138
+ async getUsernameById(ctx, userId) {
139
+ if (!userId) return null;
140
+ try {
141
+ const user = await this.db.getRepository("users").findOne({
142
+ filterByTk: userId,
143
+ fields: ["username"]
144
+ });
145
+ return (user == null ? void 0 : user.username) || null;
146
+ } catch (error) {
147
+ this.logger.error(`Failed to get username for user ${userId}:`, error);
148
+ return null;
149
+ }
150
+ }
151
+ /**
152
+ * 验证密码强度
153
+ */
154
+ async validatePasswordStrength(ctx, password, username) {
155
+ try {
156
+ if (this.config.minLength && password.length < this.config.minLength) {
157
+ ctx.throw(
158
+ 400,
159
+ ctx.t("Password must be at least {{length}} characters long", {
160
+ ns: import_constants.NAMESPACE,
161
+ length: this.config.minLength
162
+ })
163
+ );
164
+ }
165
+ if (this.config.notContainUsername && username && password.toLowerCase().includes(username.toLowerCase())) {
166
+ ctx.throw(400, ctx.t("Password cannot contain username", { ns: import_constants.NAMESPACE }));
167
+ }
168
+ if (this.config.strengthLevel > 0) {
169
+ const hasLowerCase = /[a-z]/.test(password);
170
+ const hasUpperCase = /[A-Z]/.test(password);
171
+ const hasDigit = /\d/.test(password);
172
+ const hasSymbol = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
173
+ switch (this.config.strengthLevel) {
174
+ case 1:
175
+ if (!(hasLowerCase || hasUpperCase) || !hasDigit) {
176
+ ctx.throw(400, ctx.t("Password must contain both letters and numbers", { ns: import_constants.NAMESPACE }));
177
+ }
178
+ break;
179
+ case 2:
180
+ if (!(hasLowerCase || hasUpperCase) || !hasDigit || !hasSymbol) {
181
+ ctx.throw(400, ctx.t("Password must contain letters, numbers, and symbols", { ns: import_constants.NAMESPACE }));
182
+ }
183
+ break;
184
+ case 3:
185
+ if (!hasLowerCase || !hasUpperCase || !hasDigit) {
186
+ ctx.throw(
187
+ 400,
188
+ ctx.t("Password must contain numbers, uppercase and lowercase letters", { ns: import_constants.NAMESPACE })
189
+ );
190
+ }
191
+ break;
192
+ case 4:
193
+ if (!hasLowerCase || !hasUpperCase || !hasDigit || !hasSymbol) {
194
+ ctx.throw(
195
+ 400,
196
+ ctx.t("Password must contain numbers, uppercase and lowercase letters, and symbols", { ns: import_constants.NAMESPACE })
197
+ );
198
+ }
199
+ break;
200
+ case 5:
201
+ const typesCount = [hasLowerCase, hasUpperCase, hasDigit, hasSymbol].filter(Boolean).length;
202
+ if (typesCount < 3) {
203
+ ctx.throw(
204
+ 400,
205
+ ctx.t(
206
+ "Password must contain at least 3 of the following: numbers, uppercase letters, lowercase letters, and symbols",
207
+ { ns: import_constants.NAMESPACE }
208
+ )
209
+ );
210
+ }
211
+ break;
212
+ }
213
+ }
214
+ } catch (error) {
215
+ if (error.status === 400) {
216
+ throw error;
217
+ }
218
+ this.logger.error("Failed to validate password strength:", error);
219
+ ctx.throw(400, ctx.t("Failed to validate password strength", { ns: import_constants.NAMESPACE }));
220
+ }
221
+ }
222
+ /**
223
+ * 验证密码历史
224
+ */
225
+ async validatePasswordHistory(ctx, newPassword, userId) {
226
+ if (this.config.historyCount <= 0) {
227
+ return;
228
+ }
229
+ try {
230
+ const passwordHistoryRepo = this.db.getRepository("passwordHistory");
231
+ const historyRecords = await passwordHistoryRepo.find({
232
+ filter: {
233
+ userId
234
+ },
235
+ sort: ["-createdAt"],
236
+ limit: this.config.historyCount
237
+ });
238
+ if (historyRecords.length === 0) {
239
+ return;
240
+ }
241
+ const field = passwordHistoryRepo.collection.getField("password");
242
+ for (const record of historyRecords) {
243
+ const historyPassword = record.get("password");
244
+ const isMatch = await field.verify(newPassword, historyPassword);
245
+ if (isMatch) {
246
+ ctx.throw(
247
+ 400,
248
+ ctx.t("Password has been used recently. Please choose a different password.", { ns: import_constants.NAMESPACE })
249
+ );
250
+ }
251
+ }
252
+ } catch (error) {
253
+ if (error.status === 400) {
254
+ throw error;
255
+ }
256
+ this.logger.error(`Failed to validate password history for user ${userId}:`, error);
257
+ ctx.throw(400, ctx.t("Failed to validate password history", { ns: import_constants.NAMESPACE }));
258
+ }
259
+ }
260
+ /**
261
+ * 保存密码历史
262
+ */
263
+ async savePasswordHistory(userId, password) {
264
+ if (this.config.historyCount <= 0) {
265
+ return;
266
+ }
267
+ try {
268
+ const passwordHistoryRepo = this.db.getRepository("passwordHistory");
269
+ await passwordHistoryRepo.create({
270
+ values: {
271
+ userId,
272
+ password
273
+ }
274
+ });
275
+ const count = await passwordHistoryRepo.count({
276
+ filter: {
277
+ userId
278
+ }
279
+ });
280
+ if (count > this.config.historyCount) {
281
+ const oldestRecords = await passwordHistoryRepo.find({
282
+ filter: {
283
+ userId
284
+ },
285
+ sort: ["createdAt"],
286
+ limit: count - this.config.historyCount
287
+ });
288
+ if (oldestRecords.length > 0) {
289
+ await passwordHistoryRepo.destroy({
290
+ filter: {
291
+ id: {
292
+ $in: oldestRecords.map((record) => record.get("id"))
293
+ }
294
+ }
295
+ });
296
+ }
297
+ }
298
+ this.logger.info(`Saved password history for user ${userId}`);
299
+ } catch (error) {
300
+ this.logger.error(`Failed to save password history for user ${userId}:`, error);
301
+ }
302
+ }
303
+ }
304
+ _init = __decoratorStart(null);
305
+ __decorateElement(_init, 5, "db", _db_dec, PasswordStrengthService);
306
+ __decorateElement(_init, 5, "app", _app_dec, PasswordStrengthService);
307
+ __decorateElement(_init, 5, "logger", _logger_dec, PasswordStrengthService);
308
+ PasswordStrengthService = __decorateElement(_init, 0, "PasswordStrengthService", _PasswordStrengthService_decorators, PasswordStrengthService);
309
+ __runInitializers(_init, 1, PasswordStrengthService);
310
+ // Annotate the CommonJS export names for ESM import in node:
311
+ 0 && (module.exports = {
312
+ PasswordStrengthService
313
+ });
File without changes
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@tachybase/plugin-password-policy",
3
+ "version": "1.0.6",
4
+ "description": "Password policy, including password strength, password attempt limit, password lock time, ip whitelist/blacklist, etc.",
5
+ "keywords": [
6
+ "Security"
7
+ ],
8
+ "main": "dist/server/index.js",
9
+ "dependencies": {
10
+ "@ant-design/icons": "^5.5.2",
11
+ "antd": "5.22.5",
12
+ "geoip-lite": "^1.4.9",
13
+ "ipaddr.js": "^2.1.0"
14
+ },
15
+ "peerDependencies": {
16
+ "@tachybase/actions": "1.0.6",
17
+ "@tachybase/client": "1.0.6",
18
+ "@tachybase/database": "1.0.6",
19
+ "@tachybase/schema": "1.0.6",
20
+ "@tachybase/server": "1.0.6",
21
+ "@tachybase/utils": "1.0.6"
22
+ },
23
+ "description.zh-CN": "密码策略, 包含密码强度、密码尝试次数限制、密码锁定时间、ip黑白名单等",
24
+ "displayName.zh-CN": "密码策略"
25
+ }
package/server.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './dist/server';
2
+ export { default } from './dist/server';
package/server.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/server/index.js');