@drax/identity-back 3.13.0 → 3.15.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.
Files changed (150) hide show
  1. package/dist/config/PasswordPolicyConfig.js +14 -0
  2. package/dist/controllers/UserApiKeyController.js +8 -119
  3. package/dist/controllers/UserController.js +10 -0
  4. package/dist/factory/PasswordPolicyResolverFactory.js +9 -0
  5. package/dist/factory/PasswordPolicyServiceFactory.js +27 -0
  6. package/dist/factory/UserPasswordHistoryServiceFactory.js +25 -0
  7. package/dist/factory/UserServiceFactory.js +3 -1
  8. package/dist/graphql/resolvers/user-api-key.resolvers.js +2 -3
  9. package/dist/index.js +20 -8
  10. package/dist/interfaces/IUserPasswordHistory.js +1 -0
  11. package/dist/interfaces/IUserPasswordHistoryRepository.js +1 -0
  12. package/dist/middleware/apiKeyMiddleware.js +1 -1
  13. package/dist/middleware/rbacMiddleware.js +1 -1
  14. package/dist/models/UserPasswordHistoryModel.js +30 -0
  15. package/dist/permissions/UserApiKeyPermissions.js +1 -2
  16. package/dist/policies/defaultPasswordPolicy.js +12 -0
  17. package/dist/repository/mongo/UserPasswordHistoryMongoRepository.js +20 -0
  18. package/dist/repository/sqlite/UserPasswordHistorySqliteRepository.js +38 -0
  19. package/dist/resolver/PasswordPolicyResolver.js +27 -0
  20. package/dist/routes/UserRoutes.js +10 -0
  21. package/dist/schemas/PasswordPolicySchema.js +18 -0
  22. package/dist/schemas/RegisterSchema.js +1 -3
  23. package/dist/schemas/UserSchema.js +1 -3
  24. package/dist/security/constants/defaultPasswordPolicy.js +12 -0
  25. package/dist/security/interfaces/IPasswordPolicy.js +1 -0
  26. package/dist/security/interfaces/IPasswordPolicyProjectContext.js +1 -0
  27. package/dist/security/schemas/PasswordPolicySchema.js +18 -0
  28. package/dist/security/services/PasswordPolicyResolver.js +21 -0
  29. package/dist/security/services/PasswordPolicyService.js +147 -0
  30. package/dist/security/utils/PasswordPolicySchemaFactory.js +36 -0
  31. package/dist/security/utils/getPasswordEnvPolicy.js +19 -0
  32. package/dist/services/PasswordPolicyService.js +147 -0
  33. package/dist/services/UserPasswordHistoryService.js +18 -0
  34. package/dist/services/UserService.js +34 -9
  35. package/dist/setup/LoadIdentityConfigFromEnv.js +10 -0
  36. package/dist/setup/SetProjectPasswordPolicy.js +7 -0
  37. package/dist/utils/PasswordPolicySchemaFactory.js +36 -0
  38. package/dist/utils/getPasswordEnvPolicy.js +19 -0
  39. package/docs/password-policy.md +33 -0
  40. package/package.json +6 -6
  41. package/src/config/PasswordPolicyConfig.ts +14 -0
  42. package/src/controllers/UserApiKeyController.ts +15 -129
  43. package/src/controllers/UserController.ts +10 -1
  44. package/src/factory/PasswordPolicyResolverFactory.ts +14 -0
  45. package/src/factory/PasswordPolicyServiceFactory.ts +38 -0
  46. package/src/factory/UserPasswordHistoryServiceFactory.ts +31 -0
  47. package/src/factory/UserServiceFactory.ts +7 -1
  48. package/src/graphql/resolvers/user-api-key.resolvers.ts +2 -3
  49. package/src/index.ts +28 -3
  50. package/src/interfaces/IUserPasswordHistory.ts +21 -0
  51. package/src/interfaces/IUserPasswordHistoryRepository.ts +8 -0
  52. package/src/middleware/apiKeyMiddleware.ts +1 -1
  53. package/src/middleware/rbacMiddleware.ts +1 -1
  54. package/src/models/UserPasswordHistoryModel.ts +42 -0
  55. package/src/permissions/UserApiKeyPermissions.ts +1 -2
  56. package/src/policies/defaultPasswordPolicy.ts +17 -0
  57. package/src/repository/mongo/UserPasswordHistoryMongoRepository.ts +25 -0
  58. package/src/repository/sqlite/UserPasswordHistorySqliteRepository.ts +47 -0
  59. package/src/resolver/PasswordPolicyResolver.ts +33 -0
  60. package/src/routes/UserRoutes.ts +11 -0
  61. package/src/schemas/PasswordPolicySchema.ts +29 -0
  62. package/src/schemas/RegisterSchema.ts +1 -3
  63. package/src/schemas/UserSchema.ts +1 -3
  64. package/src/services/PasswordPolicyService.ts +184 -0
  65. package/src/services/UserPasswordHistoryService.ts +23 -0
  66. package/src/services/UserService.ts +38 -9
  67. package/src/setup/LoadIdentityConfigFromEnv.ts +11 -0
  68. package/src/setup/SetProjectPasswordPolicy.ts +12 -0
  69. package/src/utils/PasswordPolicySchemaFactory.ts +47 -0
  70. package/src/utils/getPasswordEnvPolicy.ts +25 -0
  71. package/test/data-obj/users/root-mongo-user.ts +1 -1
  72. package/test/data-obj/users/root-sqlite-user.ts +1 -1
  73. package/test/endpoints/data/users-data.ts +3 -3
  74. package/test/endpoints/password-policy-route.test.ts +33 -0
  75. package/test/endpoints/user-route.test.ts +17 -4
  76. package/test/security/password-policy-resolver.test.ts +55 -0
  77. package/test/security/password-policy-schema-factory.test.ts +40 -0
  78. package/test/services/user-service.test.ts +218 -31
  79. package/test/setup/TestSetup.ts +22 -4
  80. package/test/setup/data/basic-user.ts +1 -1
  81. package/test/setup/data/root-user.ts +1 -1
  82. package/tsconfig.tsbuildinfo +1 -1
  83. package/types/config/PasswordPolicyConfig.d.ts +14 -0
  84. package/types/config/PasswordPolicyConfig.d.ts.map +1 -0
  85. package/types/controllers/UserApiKeyController.d.ts +10 -6
  86. package/types/controllers/UserApiKeyController.d.ts.map +1 -1
  87. package/types/controllers/UserController.d.ts +1 -0
  88. package/types/controllers/UserController.d.ts.map +1 -1
  89. package/types/factory/PasswordPolicyResolverFactory.d.ts +4 -0
  90. package/types/factory/PasswordPolicyResolverFactory.d.ts.map +1 -0
  91. package/types/factory/PasswordPolicyServiceFactory.d.ts +4 -0
  92. package/types/factory/PasswordPolicyServiceFactory.d.ts.map +1 -0
  93. package/types/factory/UserPasswordHistoryServiceFactory.d.ts +4 -0
  94. package/types/factory/UserPasswordHistoryServiceFactory.d.ts.map +1 -0
  95. package/types/factory/UserServiceFactory.d.ts.map +1 -1
  96. package/types/graphql/resolvers/user-api-key.resolvers.d.ts.map +1 -1
  97. package/types/index.d.ts +15 -2
  98. package/types/index.d.ts.map +1 -1
  99. package/types/interfaces/IUserPasswordHistory.d.ts +17 -0
  100. package/types/interfaces/IUserPasswordHistory.d.ts.map +1 -0
  101. package/types/interfaces/IUserPasswordHistoryRepository.d.ts +7 -0
  102. package/types/interfaces/IUserPasswordHistoryRepository.d.ts.map +1 -0
  103. package/types/models/UserPasswordHistoryModel.d.ts +15 -0
  104. package/types/models/UserPasswordHistoryModel.d.ts.map +1 -0
  105. package/types/permissions/UserApiKeyPermissions.d.ts +1 -2
  106. package/types/permissions/UserApiKeyPermissions.d.ts.map +1 -1
  107. package/types/permissions/index.d.ts +0 -2
  108. package/types/permissions/index.d.ts.map +1 -1
  109. package/types/policies/defaultPasswordPolicy.d.ts +4 -0
  110. package/types/policies/defaultPasswordPolicy.d.ts.map +1 -0
  111. package/types/repository/mongo/UserPasswordHistoryMongoRepository.d.ts +10 -0
  112. package/types/repository/mongo/UserPasswordHistoryMongoRepository.d.ts.map +1 -0
  113. package/types/repository/sqlite/UserPasswordHistorySqliteRepository.d.ts +25 -0
  114. package/types/repository/sqlite/UserPasswordHistorySqliteRepository.d.ts.map +1 -0
  115. package/types/resolver/PasswordPolicyResolver.d.ts +10 -0
  116. package/types/resolver/PasswordPolicyResolver.d.ts.map +1 -0
  117. package/types/routes/UserRoutes.d.ts.map +1 -1
  118. package/types/schemas/PasswordPolicySchema.d.ts +25 -0
  119. package/types/schemas/PasswordPolicySchema.d.ts.map +1 -0
  120. package/types/schemas/RegisterSchema.d.ts.map +1 -1
  121. package/types/schemas/UserSchema.d.ts.map +1 -1
  122. package/types/security/constants/defaultPasswordPolicy.d.ts +4 -0
  123. package/types/security/constants/defaultPasswordPolicy.d.ts.map +1 -0
  124. package/types/security/interfaces/IPasswordPolicy.d.ts +13 -0
  125. package/types/security/interfaces/IPasswordPolicy.d.ts.map +1 -0
  126. package/types/security/interfaces/IPasswordPolicyProjectContext.d.ts +6 -0
  127. package/types/security/interfaces/IPasswordPolicyProjectContext.d.ts.map +1 -0
  128. package/types/security/schemas/PasswordPolicySchema.d.ts +25 -0
  129. package/types/security/schemas/PasswordPolicySchema.d.ts.map +1 -0
  130. package/types/security/services/PasswordPolicyResolver.d.ts +9 -0
  131. package/types/security/services/PasswordPolicyResolver.d.ts.map +1 -0
  132. package/types/security/services/PasswordPolicyService.d.ts +35 -0
  133. package/types/security/services/PasswordPolicyService.d.ts.map +1 -0
  134. package/types/security/utils/PasswordPolicySchemaFactory.d.ts +9 -0
  135. package/types/security/utils/PasswordPolicySchemaFactory.d.ts.map +1 -0
  136. package/types/security/utils/getPasswordEnvPolicy.d.ts +5 -0
  137. package/types/security/utils/getPasswordEnvPolicy.d.ts.map +1 -0
  138. package/types/services/PasswordPolicyService.d.ts +34 -0
  139. package/types/services/PasswordPolicyService.d.ts.map +1 -0
  140. package/types/services/UserPasswordHistoryService.d.ts +10 -0
  141. package/types/services/UserPasswordHistoryService.d.ts.map +1 -0
  142. package/types/services/UserService.d.ts +5 -1
  143. package/types/services/UserService.d.ts.map +1 -1
  144. package/types/setup/LoadIdentityConfigFromEnv.d.ts.map +1 -1
  145. package/types/setup/SetProjectPasswordPolicy.d.ts +5 -0
  146. package/types/setup/SetProjectPasswordPolicy.d.ts.map +1 -0
  147. package/types/utils/PasswordPolicySchemaFactory.d.ts +9 -0
  148. package/types/utils/PasswordPolicySchemaFactory.d.ts.map +1 -0
  149. package/types/utils/getPasswordEnvPolicy.d.ts +5 -0
  150. package/types/utils/getPasswordEnvPolicy.d.ts.map +1 -0
@@ -0,0 +1,14 @@
1
+ var PasswordPolicyConfig;
2
+ (function (PasswordPolicyConfig) {
3
+ PasswordPolicyConfig["MinLength"] = "PASSWORD_POLICY_MIN_LENGTH";
4
+ PasswordPolicyConfig["MaxLength"] = "PASSWORD_POLICY_MAX_LENGTH";
5
+ PasswordPolicyConfig["RequireUppercase"] = "PASSWORD_POLICY_REQUIRE_UPPERCASE";
6
+ PasswordPolicyConfig["RequireLowercase"] = "PASSWORD_POLICY_REQUIRE_LOWERCASE";
7
+ PasswordPolicyConfig["RequireNumber"] = "PASSWORD_POLICY_REQUIRE_NUMBER";
8
+ PasswordPolicyConfig["RequireSpecialChar"] = "PASSWORD_POLICY_REQUIRE_SPECIAL_CHAR";
9
+ PasswordPolicyConfig["DisallowSpaces"] = "PASSWORD_POLICY_DISALLOW_SPACES";
10
+ PasswordPolicyConfig["PreventReuse"] = "PASSWORD_POLICY_PREVENT_REUSE";
11
+ PasswordPolicyConfig["ExpirationDays"] = "PASSWORD_POLICY_EXPIRATION_DAYS";
12
+ })(PasswordPolicyConfig || (PasswordPolicyConfig = {}));
13
+ export default PasswordPolicyConfig;
14
+ export { PasswordPolicyConfig };
@@ -1,129 +1,18 @@
1
1
  import { AbstractFastifyController } from "@drax/crud-back";
2
- import { ValidationError, UnauthorizedError } from "@drax/common-back";
3
2
  import UserApiKeyServiceFactory from "../factory/UserApiKeyServiceFactory.js";
4
3
  import UserApiKeyPermissions from "../permissions/UserApiKeyPermissions.js";
5
4
  class UserApiKeyController extends AbstractFastifyController {
6
5
  constructor() {
7
6
  super(UserApiKeyServiceFactory(), UserApiKeyPermissions);
7
+ this.userField = 'user';
8
+ this.userFilter = true;
9
+ this.userSetter = true;
10
+ this.userAssert = true;
8
11
  }
9
- async paginate(request, reply) {
10
- try {
11
- request.rbac.assertAuthenticated();
12
- request.rbac.assertOrPermissions([
13
- UserApiKeyPermissions.View,
14
- UserApiKeyPermissions.ViewMy
15
- ]);
16
- const filters = [];
17
- if (!request.rbac.hasPermission(UserApiKeyPermissions.View)) {
18
- filters.push({ field: "user", operator: "eq", value: request.rbac.userId });
19
- }
20
- const page = request.query.page;
21
- const limit = request.query.limit;
22
- const orderBy = request.query.orderBy;
23
- const order = request.query.order;
24
- const search = request.query.search;
25
- const userApiKeyService = UserApiKeyServiceFactory();
26
- let paginateResult = await userApiKeyService.paginate({ page, limit, orderBy, order, search, filters });
27
- return paginateResult;
28
- }
29
- catch (e) {
30
- console.log("/api/user-api-keys", e);
31
- if (e instanceof ValidationError) {
32
- reply.statusCode = e.statusCode;
33
- reply.send({ error: e.message, inputErrors: e.errors });
34
- }
35
- else if (e instanceof UnauthorizedError) {
36
- reply.statusCode = e.statusCode;
37
- reply.send({ error: e.message });
38
- }
39
- else {
40
- reply.statusCode = 500;
41
- reply.send({ error: 'error.server' });
42
- }
43
- }
44
- }
45
- async create(request, reply) {
46
- try {
47
- request.rbac.assertOrPermissions([UserApiKeyPermissions.Create, UserApiKeyPermissions.CreateMy]);
48
- const payload = request.body;
49
- if (!request.rbac.hasPermission(UserApiKeyPermissions.Create) || !payload.user) {
50
- payload.user = request.rbac.userId;
51
- }
52
- payload.createdBy = request.rbac.userId;
53
- const userApiKeyService = UserApiKeyServiceFactory();
54
- let userApiKey = await userApiKeyService.create(payload);
55
- return userApiKey;
56
- }
57
- catch (e) {
58
- if (e instanceof ValidationError) {
59
- reply.statusCode = e.statusCode;
60
- reply.send({ error: e.message, inputErrors: e.errors });
61
- }
62
- else if (e instanceof UnauthorizedError) {
63
- reply.statusCode = e.statusCode;
64
- reply.send({ error: e.message });
65
- }
66
- else {
67
- reply.statusCode = 500;
68
- reply.send({ error: 'error.server' });
69
- }
70
- }
71
- }
72
- async update(request, reply) {
73
- try {
74
- request.rbac.assertPermission(UserApiKeyPermissions.Update);
75
- const id = request.params.id;
76
- const payload = request.body;
77
- const userApiKeyService = UserApiKeyServiceFactory();
78
- let userApiKey = await userApiKeyService.update(id, payload);
79
- return userApiKey;
80
- }
81
- catch (e) {
82
- if (e instanceof ValidationError) {
83
- reply.statusCode = e.statusCode;
84
- reply.send({ error: e.message, inputErrors: e.errors });
85
- }
86
- if (e instanceof UnauthorizedError) {
87
- reply.statusCode = e.statusCode;
88
- reply.send({ error: e.message });
89
- }
90
- else if (e instanceof UnauthorizedError) {
91
- reply.statusCode = e.statusCode;
92
- reply.send({ error: e.message });
93
- }
94
- else {
95
- reply.statusCode = 500;
96
- reply.send({ error: 'error.server' });
97
- }
98
- }
99
- }
100
- async delete(request, reply) {
101
- try {
102
- request.rbac.assertPermission(UserApiKeyPermissions.Delete);
103
- const id = request.params.id;
104
- const userApiKeyService = UserApiKeyServiceFactory();
105
- let r = await userApiKeyService.delete(id);
106
- if (r) {
107
- reply.send({ message: 'Deleted successfully' });
108
- }
109
- else {
110
- reply.statusCode(400).send({ message: 'Not deleted' });
111
- }
112
- }
113
- catch (e) {
114
- if (e instanceof ValidationError) {
115
- reply.statusCode = e.statusCode;
116
- reply.send({ error: e.message, inputErrors: e.errors });
117
- }
118
- else if (e instanceof UnauthorizedError) {
119
- reply.statusCode = e.statusCode;
120
- reply.send({ error: e.message });
121
- }
122
- else {
123
- reply.statusCode = 500;
124
- reply.send({ error: 'error.server' });
125
- }
126
- }
12
+ async preCreate(request, payload) {
13
+ request.rbac.assertAuthenticated();
14
+ payload.createdBy = request.rbac.userId;
15
+ return payload;
127
16
  }
128
17
  }
129
18
  export default UserApiKeyController;
@@ -9,6 +9,7 @@ import { join } from "path";
9
9
  import { IdentityConfig } from "../config/IdentityConfig.js";
10
10
  import UserEmailService from "../services/UserEmailService.js";
11
11
  import TenantServiceFactory from "../factory/TenantServiceFactory.js";
12
+ import PasswordPolicyServiceFactory from "../factory/PasswordPolicyServiceFactory.js";
12
13
  const BASE_FILE_DIR = DraxConfig.getOrLoad(CommonConfig.FileDir) || 'files';
13
14
  const AVATAR_DIR = DraxConfig.getOrLoad(IdentityConfig.AvatarDir) || 'avatar';
14
15
  const BASE_URL = DraxConfig.getOrLoad(CommonConfig.BaseUrl) ? DraxConfig.get(CommonConfig.BaseUrl).replace(/\/$/, '') : '';
@@ -72,6 +73,15 @@ class UserController extends AbstractFastifyController {
72
73
  reply.send({ error: 'error.server' });
73
74
  }
74
75
  }
76
+ async passwordPolicy(request, reply) {
77
+ try {
78
+ const passwordPolicyService = PasswordPolicyServiceFactory();
79
+ return await passwordPolicyService.getFinalPolicy();
80
+ }
81
+ catch (e) {
82
+ this.handleError(e, reply);
83
+ }
84
+ }
75
85
  async me(request, reply) {
76
86
  try {
77
87
  if (request.authUser) {
@@ -0,0 +1,9 @@
1
+ import PasswordPolicyResolver from "../resolver/PasswordPolicyResolver.js";
2
+ let passwordPolicyResolver;
3
+ const PasswordPolicyResolverFactory = () => {
4
+ if (!passwordPolicyResolver) {
5
+ passwordPolicyResolver = new PasswordPolicyResolver();
6
+ }
7
+ return passwordPolicyResolver;
8
+ };
9
+ export default PasswordPolicyResolverFactory;
@@ -0,0 +1,27 @@
1
+ import PasswordPolicyService from "../services/PasswordPolicyService.js";
2
+ import PasswordPolicyResolverFactory from "./PasswordPolicyResolverFactory.js";
3
+ import UserPasswordHistoryServiceFactory from "./UserPasswordHistoryServiceFactory.js";
4
+ import UserMongoRepository from "../repository/mongo/UserMongoRepository.js";
5
+ import UserSqliteRepository from "../repository/sqlite/UserSqliteRepository.js";
6
+ import { COMMON, CommonConfig, DraxConfig } from "@drax/common-back";
7
+ let passwordPolicyService;
8
+ const PasswordPolicyServiceFactory = (verbose = false) => {
9
+ if (!passwordPolicyService) {
10
+ let userRepository;
11
+ switch (DraxConfig.getOrLoad(CommonConfig.DbEngine)) {
12
+ case COMMON.DB_ENGINES.MONGODB:
13
+ userRepository = new UserMongoRepository();
14
+ break;
15
+ case COMMON.DB_ENGINES.SQLITE:
16
+ userRepository = new UserSqliteRepository(DraxConfig.getOrLoad(CommonConfig.SqliteDbFile), verbose);
17
+ userRepository.build();
18
+ break;
19
+ default:
20
+ throw new Error("DraxConfig.DB_ENGINE must be one of " + Object.values(COMMON.DB_ENGINES).join(", "));
21
+ }
22
+ const passwordPolicyResolver = PasswordPolicyResolverFactory();
23
+ passwordPolicyService = new PasswordPolicyService(passwordPolicyResolver, userRepository, UserPasswordHistoryServiceFactory(verbose));
24
+ }
25
+ return passwordPolicyService;
26
+ };
27
+ export default PasswordPolicyServiceFactory;
@@ -0,0 +1,25 @@
1
+ import { COMMON, CommonConfig, DraxConfig } from "@drax/common-back";
2
+ import UserPasswordHistoryMongoRepository from "../repository/mongo/UserPasswordHistoryMongoRepository.js";
3
+ import UserPasswordHistorySqliteRepository from "../repository/sqlite/UserPasswordHistorySqliteRepository.js";
4
+ import UserPasswordHistoryService from "../services/UserPasswordHistoryService.js";
5
+ let userPasswordHistoryService;
6
+ const UserPasswordHistoryServiceFactory = (verbose = false) => {
7
+ if (!userPasswordHistoryService) {
8
+ let repository;
9
+ switch (DraxConfig.getOrLoad(CommonConfig.DbEngine)) {
10
+ case COMMON.DB_ENGINES.MONGODB:
11
+ repository = new UserPasswordHistoryMongoRepository();
12
+ break;
13
+ case COMMON.DB_ENGINES.SQLITE:
14
+ const sqliteRepository = new UserPasswordHistorySqliteRepository(DraxConfig.getOrLoad(CommonConfig.SqliteDbFile), verbose);
15
+ sqliteRepository.build();
16
+ repository = sqliteRepository;
17
+ break;
18
+ default:
19
+ throw new Error("DraxConfig.DB_ENGINE must be one of " + Object.values(COMMON.DB_ENGINES).join(", "));
20
+ }
21
+ userPasswordHistoryService = new UserPasswordHistoryService(repository);
22
+ }
23
+ return userPasswordHistoryService;
24
+ };
25
+ export default UserPasswordHistoryServiceFactory;
@@ -2,6 +2,8 @@ import UserMongoRepository from "../repository/mongo/UserMongoRepository.js";
2
2
  import UserService from "../services/UserService.js";
3
3
  import UserSqliteRepository from "../repository/sqlite/UserSqliteRepository.js";
4
4
  import { COMMON, CommonConfig, DraxConfig } from "@drax/common-back";
5
+ import PasswordPolicyServiceFactory from "./PasswordPolicyServiceFactory.js";
6
+ import UserPasswordHistoryServiceFactory from "./UserPasswordHistoryServiceFactory.js";
5
7
  let userService;
6
8
  const UserServiceFactory = (verbose = false) => {
7
9
  if (!userService) {
@@ -18,7 +20,7 @@ const UserServiceFactory = (verbose = false) => {
18
20
  default:
19
21
  throw new Error("DraxConfig.DB_ENGINE must be one of " + Object.values(COMMON.DB_ENGINES).join(", "));
20
22
  }
21
- userService = new UserService(userRepository);
23
+ userService = new UserService(userRepository, PasswordPolicyServiceFactory(verbose), UserPasswordHistoryServiceFactory(verbose));
22
24
  }
23
25
  return userService;
24
26
  };
@@ -9,13 +9,12 @@ export default {
9
9
  try {
10
10
  rbac.assertAuthenticated();
11
11
  rbac.assertOrPermissions([
12
- UserApiKeyPermissions.View,
13
- UserApiKeyPermissions.ViewMy
12
+ UserApiKeyPermissions.View
14
13
  ]);
15
14
  if (!Array.isArray(options.filters)) {
16
15
  options.filters = [];
17
16
  }
18
- if (!rbac.hasPermission(UserApiKeyPermissions.View)) {
17
+ if (!rbac.hasPermission(UserApiKeyPermissions.ViewAll)) {
19
18
  options.filters.push({ field: "user", operator: "eq", value: rbac.userId });
20
19
  }
21
20
  const userApiKeyService = UserApiKeyServiceFactory();
package/dist/index.js CHANGED
@@ -5,6 +5,8 @@ import TenantServiceFactory from "./factory/TenantServiceFactory.js";
5
5
  import UserApiKeyServiceFactory from "./factory/UserApiKeyServiceFactory.js";
6
6
  import UserLoginFailServiceFactory from "./factory/UserLoginFailServiceFactory.js";
7
7
  import UserSessionServiceFactory from "./factory/UserSessionServiceFactory.js";
8
+ import PasswordPolicyServiceFactory from "./factory/PasswordPolicyServiceFactory.js";
9
+ import UserPasswordHistoryServiceFactory from "./factory/UserPasswordHistoryServiceFactory.js";
8
10
  import RoleService from "./services/RoleService.js";
9
11
  import UserService from "./services/UserService.js";
10
12
  import TenantService from "./services/TenantService.js";
@@ -12,6 +14,9 @@ import PermissionService from "./services/PermissionService.js";
12
14
  import UserApiKeyService from "./services/UserApiKeyService.js";
13
15
  import UserSessionService from "./services/UserSessionService.js";
14
16
  import UserLoginFailService from "./services/UserLoginFailService.js";
17
+ import UserPasswordHistoryService from "./services/UserPasswordHistoryService.js";
18
+ import PasswordPolicyService from "./services/PasswordPolicyService.js";
19
+ import PasswordPolicyResolver from "./resolver/PasswordPolicyResolver.js";
15
20
  import Rbac from "./rbac/Rbac.js";
16
21
  import { UserRoutes } from "./routes/UserRoutes.js";
17
22
  import { RoleRoutes } from "./routes/RoleRoutes.js";
@@ -24,6 +29,7 @@ import { jwtMiddleware } from "./middleware/jwtMiddleware.js";
24
29
  import { rbacMiddleware } from "./middleware/rbacMiddleware.js";
25
30
  import { apiKeyMiddleware } from "./middleware/apiKeyMiddleware.js";
26
31
  import IdentityConfig from "./config/IdentityConfig.js";
32
+ import PasswordPolicyConfig from "./config/PasswordPolicyConfig.js";
27
33
  import BadCredentialsError from "./errors/BadCredentialsError.js";
28
34
  import CreateUserIfNotExist from "./setup/CreateUserIfNotExist.js";
29
35
  import CreateTenantIfNotExist from "./setup/CreateTenantIfNotExist.js";
@@ -31,24 +37,28 @@ import CreateOrUpdateRole from "./setup/CreateOrUpdateRole.js";
31
37
  import LoadPermissions from "./setup/LoadPermissions.js";
32
38
  import LoadIdentityConfigFromEnv from "./setup/LoadIdentityConfigFromEnv.js";
33
39
  import RecoveryUserPassword from "./setup/RecoveryUserPassword.js";
40
+ import SetProjectPasswordPolicy from "./setup/SetProjectPasswordPolicy.js";
34
41
  import { RoleModel, RoleMongoSchema } from "./models/RoleModel.js";
35
42
  import { TenantModel, TenantMongoSchema } from "./models/TenantModel.js";
36
43
  import { UserModel, UserMongoSchema } from "./models/UserModel.js";
37
44
  import { UserApiKeyModel, UserApiKeyMongoSchema } from "./models/UserApiKeyModel.js";
38
45
  import { UserSessionModel, UserSessionMongoSchema } from "./models/UserSessionModel.js";
39
46
  import { UserLoginFailModel, UserLoginFailMongoSchema } from "./models/UserLoginFailModel.js";
47
+ import { UserPasswordHistoryModel, UserPasswordHistoryMongoSchema } from "./models/UserPasswordHistoryModel.js";
40
48
  import RoleMongoRepository from "./repository/mongo/RoleMongoRepository.js";
41
49
  import TenantMongoRepository from "./repository/mongo/TenantMongoRepository.js";
42
50
  import UserMongoRepository from "./repository/mongo/UserMongoRepository.js";
43
51
  import UserApiKeyMongoRepository from "./repository/mongo/UserApiKeyMongoRepository.js";
44
52
  import UserSessionMongoRepository from "./repository/mongo/UserSessionMongoRepository.js";
45
53
  import UserLoginFailMongoRepository from "./repository/mongo/UserLoginFailMongoRepository.js";
54
+ import UserPasswordHistoryMongoRepository from "./repository/mongo/UserPasswordHistoryMongoRepository.js";
46
55
  import RoleSqliteRepository from "./repository/sqlite/RoleSqliteRepository.js";
47
56
  import TenantSqliteRepository from "./repository/sqlite/TenantSqliteRepository.js";
48
57
  import UserSqliteRepository from "./repository/sqlite/UserSqliteRepository.js";
49
58
  import UserApiKeySqliteRepository from "./repository/sqlite/UserApiKeySqliteRepository.js";
50
59
  import UserLoginFailSqliteRepository from "./repository/sqlite/UserLoginFailSqliteRepository.js";
51
60
  import UserSessionSqliteRepository from "./repository/sqlite/UserSessionSqliteRepository.js";
61
+ import UserPasswordHistorySqliteRepository from "./repository/sqlite/UserPasswordHistorySqliteRepository.js";
52
62
  import { RolePermissions } from "./permissions/RolePermissions.js";
53
63
  import { TenantPermissions } from "./permissions/TenantPermissions.js";
54
64
  import { UserPermissions } from "./permissions/UserPermissions.js";
@@ -61,6 +71,8 @@ import { RoleSchema, RoleBaseSchema } from "./schemas/RoleSchema.js";
61
71
  import { UserApiKeySchema, UserApiKeyBaseSchema } from "./schemas/UserApiKeySchema.js";
62
72
  import { UserLoginFailBaseSchema } from "./schemas/UserLoginFailSchema.js";
63
73
  import { UserSessionBaseSchema } from "./schemas/UserSessionSchema.js";
74
+ import { defaultPasswordPolicy } from "./policies/defaultPasswordPolicy.js";
75
+ import { PasswordPolicySchema } from "./schemas/PasswordPolicySchema.js";
64
76
  const graphqlMergeResult = await GraphqlMerge();
65
77
  const identityTypeDefs = await graphqlMergeResult.typeDefs;
66
78
  const identityResolvers = await graphqlMergeResult.resolvers;
@@ -68,9 +80,9 @@ export {
68
80
  //Schemas
69
81
  UserSchema, UserBaseSchema, TenantSchema, TenantBaseSchema, RoleSchema, RoleBaseSchema, UserApiKeyBaseSchema, UserApiKeySchema, UserLoginFailBaseSchema, UserSessionBaseSchema,
70
82
  //Service
71
- UserService, RoleService, TenantService, UserApiKeyService, UserSessionService, UserLoginFailService, PermissionService, Rbac,
83
+ UserService, RoleService, TenantService, UserApiKeyService, UserSessionService, UserLoginFailService, UserPasswordHistoryService, PasswordPolicyService, PasswordPolicyResolver, PermissionService, Rbac,
72
84
  //Factories
73
- UserServiceFactory, RoleServiceFactory, TenantServiceFactory, UserApiKeyServiceFactory, UserSessionServiceFactory, UserLoginFailServiceFactory,
85
+ UserServiceFactory, RoleServiceFactory, TenantServiceFactory, UserApiKeyServiceFactory, UserSessionServiceFactory, UserLoginFailServiceFactory, UserPasswordHistoryServiceFactory, PasswordPolicyServiceFactory,
74
86
  //GQL
75
87
  identityTypeDefs, identityResolvers,
76
88
  //API REST
@@ -80,14 +92,14 @@ jwtMiddleware, rbacMiddleware, apiKeyMiddleware,
80
92
  //Permissions
81
93
  RolePermissions, TenantPermissions, UserPermissions, UserApiKeyPermissions, UserSessionPermissions, UserLoginFailPermissions,
82
94
  //Mongo Repositories
83
- RoleMongoRepository, TenantMongoRepository, UserMongoRepository, UserApiKeyMongoRepository, UserSessionMongoRepository, UserLoginFailMongoRepository,
95
+ RoleMongoRepository, TenantMongoRepository, UserMongoRepository, UserApiKeyMongoRepository, UserSessionMongoRepository, UserLoginFailMongoRepository, UserPasswordHistoryMongoRepository,
84
96
  //Mongo Models
85
- RoleModel, TenantModel, UserModel, UserApiKeyModel, UserSessionModel, UserLoginFailModel, RoleMongoSchema, TenantMongoSchema, UserMongoSchema, UserApiKeyMongoSchema, UserSessionMongoSchema, UserLoginFailMongoSchema,
97
+ RoleModel, TenantModel, UserModel, UserApiKeyModel, UserSessionModel, UserLoginFailModel, UserPasswordHistoryModel, RoleMongoSchema, TenantMongoSchema, UserMongoSchema, UserApiKeyMongoSchema, UserSessionMongoSchema, UserLoginFailMongoSchema, UserPasswordHistoryMongoSchema,
86
98
  //Sqlite Repositories
87
- RoleSqliteRepository, TenantSqliteRepository, UserSqliteRepository, UserApiKeySqliteRepository, UserLoginFailSqliteRepository, UserSessionSqliteRepository,
99
+ RoleSqliteRepository, TenantSqliteRepository, UserSqliteRepository, UserApiKeySqliteRepository, UserLoginFailSqliteRepository, UserSessionSqliteRepository, UserPasswordHistorySqliteRepository,
88
100
  //Config
89
- IdentityConfig,
101
+ IdentityConfig, PasswordPolicyConfig,
90
102
  //Errors
91
- BadCredentialsError,
103
+ BadCredentialsError, defaultPasswordPolicy, PasswordPolicySchema,
92
104
  //Setup
93
- LoadIdentityConfigFromEnv, LoadPermissions, CreateOrUpdateRole, CreateUserIfNotExist, CreateTenantIfNotExist, RecoveryUserPassword };
105
+ LoadIdentityConfigFromEnv, LoadPermissions, CreateOrUpdateRole, CreateUserIfNotExist, CreateTenantIfNotExist, RecoveryUserPassword, SetProjectPasswordPolicy };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -3,7 +3,7 @@ import UserApiKeyServiceFactory from "../factory/UserApiKeyServiceFactory.js";
3
3
  import IdentityConfig from "../config/IdentityConfig.js";
4
4
  const verifyIp = DraxConfig.getOrLoad(IdentityConfig.VerifyIP, 'boolean', true);
5
5
  const cacheTTL = DraxConfig.getOrLoad(IdentityConfig.ApiKeyCacheTTL, 'number', 10000);
6
- const draxCache = new DraxCache(cacheTTL);
6
+ const draxCache = new DraxCache({ ttl: cacheTTL, namespace: 'identity:api-key' });
7
7
  async function userApiKeyLoader(k) {
8
8
  const userApiKeyService = UserApiKeyServiceFactory();
9
9
  const userApiKey = await userApiKeyService.findBySecret(k);
@@ -3,7 +3,7 @@ import RoleServiceFactory from "../factory/RoleServiceFactory.js";
3
3
  import Rbac from "../rbac/Rbac.js";
4
4
  import IdentityConfig from "../config/IdentityConfig.js";
5
5
  const cacheTTL = DraxConfig.getOrLoad(IdentityConfig.RbacCacheTTL, 'number', 10000);
6
- const draxCache = new DraxCache(cacheTTL);
6
+ const draxCache = new DraxCache({ ttl: cacheTTL, namespace: 'identity:role' });
7
7
  async function roleLoader(k) {
8
8
  const roleService = RoleServiceFactory();
9
9
  const role = await roleService.findById(k);
@@ -0,0 +1,30 @@
1
+ import { mongoose } from "@drax/common-back";
2
+ import uniqueValidator from "mongoose-unique-validator";
3
+ import mongoosePaginate from "mongoose-paginate-v2";
4
+ const UserPasswordHistoryMongoSchema = new mongoose.Schema({
5
+ user: { type: String, required: true, index: true },
6
+ passwordHash: { type: String, required: true, index: false }
7
+ }, { timestamps: true });
8
+ UserPasswordHistoryMongoSchema.plugin(uniqueValidator, { message: "validation.unique" });
9
+ UserPasswordHistoryMongoSchema.plugin(mongoosePaginate);
10
+ UserPasswordHistoryMongoSchema.virtual("id").get(function () {
11
+ return this._id.toString();
12
+ });
13
+ UserPasswordHistoryMongoSchema.set("toJSON", { getters: true, virtuals: true });
14
+ UserPasswordHistoryMongoSchema.set("toObject", { getters: true, virtuals: true });
15
+ const MODEL_NAME = "UserPasswordHistory";
16
+ const COLLECTION_NAME = "user_password_history";
17
+ let UserPasswordHistoryModel;
18
+ try {
19
+ UserPasswordHistoryModel = mongoose.model(MODEL_NAME, UserPasswordHistoryMongoSchema, COLLECTION_NAME);
20
+ }
21
+ catch (e) {
22
+ if (e.name === "OverwriteModelError") {
23
+ UserPasswordHistoryModel = mongoose.model(MODEL_NAME);
24
+ }
25
+ else {
26
+ throw e;
27
+ }
28
+ }
29
+ export { UserPasswordHistoryMongoSchema, UserPasswordHistoryModel };
30
+ export default UserPasswordHistoryModel;
@@ -1,11 +1,10 @@
1
1
  var UserApiKeyPermissions;
2
2
  (function (UserApiKeyPermissions) {
3
3
  UserApiKeyPermissions["Create"] = "userApiKey:create";
4
- UserApiKeyPermissions["CreateMy"] = "userApiKey:createMy";
5
4
  UserApiKeyPermissions["Update"] = "userApiKey:update";
6
5
  UserApiKeyPermissions["Delete"] = "userApiKey:delete";
7
6
  UserApiKeyPermissions["View"] = "userApiKey:view";
8
- UserApiKeyPermissions["ViewMy"] = "userApiKey:viewMy";
7
+ UserApiKeyPermissions["ViewAll"] = "userApiKey:viewAll";
9
8
  UserApiKeyPermissions["Manage"] = "userApiKey:manage";
10
9
  })(UserApiKeyPermissions || (UserApiKeyPermissions = {}));
11
10
  export default UserApiKeyPermissions;
@@ -0,0 +1,12 @@
1
+ const defaultPasswordPolicy = {
2
+ minLength: 8,
3
+ maxLength: 64,
4
+ requireUppercase: true,
5
+ requireLowercase: true,
6
+ requireNumber: true,
7
+ requireSpecialChar: false,
8
+ disallowSpaces: true,
9
+ preventReuse: 3,
10
+ expirationDays: null
11
+ };
12
+ export { defaultPasswordPolicy };
@@ -0,0 +1,20 @@
1
+ import { AbstractMongoRepository } from "@drax/crud-back";
2
+ import { UserPasswordHistoryModel } from "../../models/UserPasswordHistoryModel.js";
3
+ class UserPasswordHistoryMongoRepository extends AbstractMongoRepository {
4
+ constructor() {
5
+ super();
6
+ this._model = UserPasswordHistoryModel;
7
+ this._searchFields = ["user"];
8
+ this._populateFields = ["user"];
9
+ }
10
+ async findLatestByUserId(userId, limit) {
11
+ return UserPasswordHistoryModel
12
+ .find({ user: userId })
13
+ .sort({ createdAt: -1 })
14
+ .limit(limit)
15
+ .lean(true)
16
+ .exec();
17
+ }
18
+ }
19
+ export default UserPasswordHistoryMongoRepository;
20
+ export { UserPasswordHistoryMongoRepository };
@@ -0,0 +1,38 @@
1
+ import { AbstractSqliteRepository } from "@drax/crud-back";
2
+ class UserPasswordHistorySqliteRepository extends AbstractSqliteRepository {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.tableName = "user_password_history";
6
+ this.searchFields = [];
7
+ this.booleanFields = [];
8
+ this.identifier = "_id";
9
+ this.populateFields = [
10
+ { field: "user", table: "users", identifier: "_id" }
11
+ ];
12
+ this.tableFields = [
13
+ { name: "user", type: "TEXT", unique: false, primary: false },
14
+ { name: "passwordHash", type: "TEXT", unique: false, primary: false },
15
+ { name: "createdAt", type: "TEXT", unique: false, primary: false },
16
+ { name: "updatedAt", type: "TEXT", unique: false, primary: false },
17
+ ];
18
+ }
19
+ async prepareData() {
20
+ }
21
+ async prepareItem(item) {
22
+ if (item.createdAt && typeof item.createdAt === "string") {
23
+ item.createdAt = new Date(item.createdAt);
24
+ }
25
+ if (item.updatedAt && typeof item.updatedAt === "string") {
26
+ item.updatedAt = new Date(item.updatedAt);
27
+ }
28
+ }
29
+ async findLatestByUserId(userId, limit) {
30
+ const rows = this.db.prepare(`SELECT * FROM ${this.tableName} WHERE user = ? ORDER BY createdAt DESC LIMIT ?`).all(userId, limit);
31
+ for (const row of rows) {
32
+ await this.decorate(row);
33
+ }
34
+ return rows;
35
+ }
36
+ }
37
+ export default UserPasswordHistorySqliteRepository;
38
+ export { UserPasswordHistorySqliteRepository };
@@ -0,0 +1,27 @@
1
+ import { defaultPasswordPolicy } from "../policies/defaultPasswordPolicy.js";
2
+ import { PartialPasswordPolicySchema, PasswordPolicySchema } from "../schemas/PasswordPolicySchema.js";
3
+ import getPasswordEnvPolicy from "../utils/getPasswordEnvPolicy.js";
4
+ class PasswordPolicyResolver {
5
+ constructor() {
6
+ this.projectPolicy = {};
7
+ }
8
+ setProjectPolicy(projectPolicy) {
9
+ this.projectPolicy = projectPolicy;
10
+ }
11
+ async resolve() {
12
+ const projectPolicy = await this.getProjectPolicy();
13
+ const envPolicy = getPasswordEnvPolicy();
14
+ return PasswordPolicySchema.parse({
15
+ ...defaultPasswordPolicy,
16
+ ...projectPolicy,
17
+ ...envPolicy
18
+ });
19
+ }
20
+ async getProjectPolicy() {
21
+ return this.projectPolicy
22
+ ? PartialPasswordPolicySchema.parse(this.projectPolicy)
23
+ : {};
24
+ }
25
+ }
26
+ export default PasswordPolicyResolver;
27
+ export { PasswordPolicyResolver };
@@ -6,6 +6,7 @@ import { RegisterBodyRequestSchema, RegisterBodyResponseSchema } from "../schema
6
6
  import { MyPasswordBodyRequestSchema, PasswordBodyRequestSchema, PasswordBodyResponseSchema } from "../schemas/PasswordSchema.js";
7
7
  import { SwitchTenantBodyRequestSchema, SwitchTenantBodyResponseSchema } from "../schemas/SwitchTenantSchema.js";
8
8
  import zod from "zod";
9
+ import { PasswordPolicySchema } from "../schemas/PasswordPolicySchema.js";
9
10
  async function UserRoutes(fastify, options) {
10
11
  const controller = new UserController();
11
12
  const schemas = new CrudSchemaBuilder(UserSchema, UserCreateSchema, UserUpdateSchema, 'tenant', 'openapi-3.0', ['Identity']);
@@ -26,6 +27,15 @@ async function UserRoutes(fastify, options) {
26
27
  },
27
28
  },
28
29
  }, (req, rep) => controller.auth(req, rep));
30
+ fastify.get('/api/auth/password-policy', {
31
+ schema: {
32
+ tags: ['Auth'],
33
+ response: {
34
+ 200: zod.toJSONSchema(PasswordPolicySchema),
35
+ 500: schemas.jsonErrorBodyResponse,
36
+ },
37
+ },
38
+ }, (req, rep) => controller.passwordPolicy(req, rep));
29
39
  fastify.get('/api/auth/me', {
30
40
  schema: {
31
41
  tags: ['Auth'],
@@ -0,0 +1,18 @@
1
+ import z from "zod";
2
+ const PasswordPolicySchemaBase = z.object({
3
+ minLength: z.number().int().min(1),
4
+ maxLength: z.number().int().min(1),
5
+ requireUppercase: z.boolean(),
6
+ requireLowercase: z.boolean(),
7
+ requireNumber: z.boolean(),
8
+ requireSpecialChar: z.boolean(),
9
+ disallowSpaces: z.boolean(),
10
+ preventReuse: z.number().int().min(0),
11
+ expirationDays: z.number().int().min(1).nullable(),
12
+ });
13
+ const PasswordPolicySchema = PasswordPolicySchemaBase.refine((policy) => policy.maxLength >= policy.minLength, {
14
+ message: "validation.password.maxLength",
15
+ path: ["maxLength"]
16
+ });
17
+ const PartialPasswordPolicySchema = PasswordPolicySchemaBase.partial();
18
+ export { PasswordPolicySchema, PartialPasswordPolicySchema };
@@ -7,9 +7,7 @@ const RegisterBodyRequestSchema = z.object({
7
7
  email: email("validation.email.invalid"),
8
8
  phone: string({ error: "validation.required" }).optional(),
9
9
  password: string({ error: "validation.required" })
10
- .min(1, "validation.required")
11
- .min(8, "validation.password.min8")
12
- .max(64, "validation.password.max64"),
10
+ .min(1, "validation.required"),
13
11
  });
14
12
  const RegisterBodyResponseSchema = z.object({
15
13
  success: z.boolean(),
@@ -13,9 +13,7 @@ const UserBaseSchema = object({
13
13
  });
14
14
  const UserCreateSchema = UserBaseSchema.extend({
15
15
  password: string({ error: "validation.required" })
16
- .min(1, "validation.required")
17
- .min(8, "validation.password.min8")
18
- .max(64, "validation.password.max64"),
16
+ .min(1, "validation.required"),
19
17
  });
20
18
  const UserUpdateSchema = UserBaseSchema.extend({});
21
19
  const UserSchema = UserBaseSchema
@@ -0,0 +1,12 @@
1
+ const defaultPasswordPolicy = {
2
+ minLength: 8,
3
+ maxLength: 64,
4
+ requireUppercase: true,
5
+ requireLowercase: true,
6
+ requireNumber: true,
7
+ requireSpecialChar: false,
8
+ disallowSpaces: true,
9
+ preventReuse: 3,
10
+ expirationDays: null
11
+ };
12
+ export { defaultPasswordPolicy };
@@ -0,0 +1 @@
1
+ export {};