@drax/identity-back 3.14.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.
- package/dist/config/PasswordPolicyConfig.js +14 -0
- package/dist/controllers/UserController.js +10 -0
- package/dist/factory/PasswordPolicyResolverFactory.js +9 -0
- package/dist/factory/PasswordPolicyServiceFactory.js +27 -0
- package/dist/factory/UserPasswordHistoryServiceFactory.js +25 -0
- package/dist/factory/UserServiceFactory.js +3 -1
- package/dist/index.js +20 -8
- package/dist/interfaces/IUserPasswordHistory.js +1 -0
- package/dist/interfaces/IUserPasswordHistoryRepository.js +1 -0
- package/dist/models/UserPasswordHistoryModel.js +30 -0
- package/dist/policies/defaultPasswordPolicy.js +12 -0
- package/dist/repository/mongo/UserPasswordHistoryMongoRepository.js +20 -0
- package/dist/repository/sqlite/UserPasswordHistorySqliteRepository.js +38 -0
- package/dist/resolver/PasswordPolicyResolver.js +27 -0
- package/dist/routes/UserRoutes.js +10 -0
- package/dist/schemas/PasswordPolicySchema.js +18 -0
- package/dist/schemas/RegisterSchema.js +1 -3
- package/dist/schemas/UserSchema.js +1 -3
- package/dist/security/constants/defaultPasswordPolicy.js +12 -0
- package/dist/security/interfaces/IPasswordPolicy.js +1 -0
- package/dist/security/interfaces/IPasswordPolicyProjectContext.js +1 -0
- package/dist/security/schemas/PasswordPolicySchema.js +18 -0
- package/dist/security/services/PasswordPolicyResolver.js +21 -0
- package/dist/security/services/PasswordPolicyService.js +147 -0
- package/dist/security/utils/PasswordPolicySchemaFactory.js +36 -0
- package/dist/security/utils/getPasswordEnvPolicy.js +19 -0
- package/dist/services/PasswordPolicyService.js +147 -0
- package/dist/services/UserPasswordHistoryService.js +18 -0
- package/dist/services/UserService.js +34 -9
- package/dist/setup/LoadIdentityConfigFromEnv.js +10 -0
- package/dist/setup/SetProjectPasswordPolicy.js +7 -0
- package/dist/utils/PasswordPolicySchemaFactory.js +36 -0
- package/dist/utils/getPasswordEnvPolicy.js +19 -0
- package/docs/password-policy.md +33 -0
- package/package.json +4 -4
- package/src/config/PasswordPolicyConfig.ts +14 -0
- package/src/controllers/UserController.ts +10 -1
- package/src/factory/PasswordPolicyResolverFactory.ts +14 -0
- package/src/factory/PasswordPolicyServiceFactory.ts +38 -0
- package/src/factory/UserPasswordHistoryServiceFactory.ts +31 -0
- package/src/factory/UserServiceFactory.ts +7 -1
- package/src/index.ts +28 -3
- package/src/interfaces/IUserPasswordHistory.ts +21 -0
- package/src/interfaces/IUserPasswordHistoryRepository.ts +8 -0
- package/src/models/UserPasswordHistoryModel.ts +42 -0
- package/src/policies/defaultPasswordPolicy.ts +17 -0
- package/src/repository/mongo/UserPasswordHistoryMongoRepository.ts +25 -0
- package/src/repository/sqlite/UserPasswordHistorySqliteRepository.ts +47 -0
- package/src/resolver/PasswordPolicyResolver.ts +33 -0
- package/src/routes/UserRoutes.ts +11 -0
- package/src/schemas/PasswordPolicySchema.ts +29 -0
- package/src/schemas/RegisterSchema.ts +1 -3
- package/src/schemas/UserSchema.ts +1 -3
- package/src/services/PasswordPolicyService.ts +184 -0
- package/src/services/UserPasswordHistoryService.ts +23 -0
- package/src/services/UserService.ts +38 -9
- package/src/setup/LoadIdentityConfigFromEnv.ts +11 -0
- package/src/setup/SetProjectPasswordPolicy.ts +12 -0
- package/src/utils/PasswordPolicySchemaFactory.ts +47 -0
- package/src/utils/getPasswordEnvPolicy.ts +25 -0
- package/test/data-obj/users/root-mongo-user.ts +1 -1
- package/test/data-obj/users/root-sqlite-user.ts +1 -1
- package/test/endpoints/data/users-data.ts +3 -3
- package/test/endpoints/password-policy-route.test.ts +33 -0
- package/test/endpoints/user-route.test.ts +17 -4
- package/test/security/password-policy-resolver.test.ts +55 -0
- package/test/security/password-policy-schema-factory.test.ts +40 -0
- package/test/services/user-service.test.ts +218 -31
- package/test/setup/TestSetup.ts +22 -4
- package/test/setup/data/basic-user.ts +1 -1
- package/test/setup/data/root-user.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/types/config/PasswordPolicyConfig.d.ts +14 -0
- package/types/config/PasswordPolicyConfig.d.ts.map +1 -0
- package/types/controllers/UserController.d.ts +1 -0
- package/types/controllers/UserController.d.ts.map +1 -1
- package/types/factory/PasswordPolicyResolverFactory.d.ts +4 -0
- package/types/factory/PasswordPolicyResolverFactory.d.ts.map +1 -0
- package/types/factory/PasswordPolicyServiceFactory.d.ts +4 -0
- package/types/factory/PasswordPolicyServiceFactory.d.ts.map +1 -0
- package/types/factory/UserPasswordHistoryServiceFactory.d.ts +4 -0
- package/types/factory/UserPasswordHistoryServiceFactory.d.ts.map +1 -0
- package/types/factory/UserServiceFactory.d.ts.map +1 -1
- package/types/index.d.ts +15 -2
- package/types/index.d.ts.map +1 -1
- package/types/interfaces/IUserPasswordHistory.d.ts +17 -0
- package/types/interfaces/IUserPasswordHistory.d.ts.map +1 -0
- package/types/interfaces/IUserPasswordHistoryRepository.d.ts +7 -0
- package/types/interfaces/IUserPasswordHistoryRepository.d.ts.map +1 -0
- package/types/models/UserPasswordHistoryModel.d.ts +15 -0
- package/types/models/UserPasswordHistoryModel.d.ts.map +1 -0
- package/types/policies/defaultPasswordPolicy.d.ts +4 -0
- package/types/policies/defaultPasswordPolicy.d.ts.map +1 -0
- package/types/repository/mongo/UserPasswordHistoryMongoRepository.d.ts +10 -0
- package/types/repository/mongo/UserPasswordHistoryMongoRepository.d.ts.map +1 -0
- package/types/repository/sqlite/UserPasswordHistorySqliteRepository.d.ts +25 -0
- package/types/repository/sqlite/UserPasswordHistorySqliteRepository.d.ts.map +1 -0
- package/types/resolver/PasswordPolicyResolver.d.ts +10 -0
- package/types/resolver/PasswordPolicyResolver.d.ts.map +1 -0
- package/types/routes/UserRoutes.d.ts.map +1 -1
- package/types/schemas/PasswordPolicySchema.d.ts +25 -0
- package/types/schemas/PasswordPolicySchema.d.ts.map +1 -0
- package/types/schemas/RegisterSchema.d.ts.map +1 -1
- package/types/schemas/UserSchema.d.ts.map +1 -1
- package/types/security/constants/defaultPasswordPolicy.d.ts +4 -0
- package/types/security/constants/defaultPasswordPolicy.d.ts.map +1 -0
- package/types/security/interfaces/IPasswordPolicy.d.ts +13 -0
- package/types/security/interfaces/IPasswordPolicy.d.ts.map +1 -0
- package/types/security/interfaces/IPasswordPolicyProjectContext.d.ts +6 -0
- package/types/security/interfaces/IPasswordPolicyProjectContext.d.ts.map +1 -0
- package/types/security/schemas/PasswordPolicySchema.d.ts +25 -0
- package/types/security/schemas/PasswordPolicySchema.d.ts.map +1 -0
- package/types/security/services/PasswordPolicyResolver.d.ts +9 -0
- package/types/security/services/PasswordPolicyResolver.d.ts.map +1 -0
- package/types/security/services/PasswordPolicyService.d.ts +35 -0
- package/types/security/services/PasswordPolicyService.d.ts.map +1 -0
- package/types/security/utils/PasswordPolicySchemaFactory.d.ts +9 -0
- package/types/security/utils/PasswordPolicySchemaFactory.d.ts.map +1 -0
- package/types/security/utils/getPasswordEnvPolicy.d.ts +5 -0
- package/types/security/utils/getPasswordEnvPolicy.d.ts.map +1 -0
- package/types/services/PasswordPolicyService.d.ts +34 -0
- package/types/services/PasswordPolicyService.d.ts.map +1 -0
- package/types/services/UserPasswordHistoryService.d.ts +10 -0
- package/types/services/UserPasswordHistoryService.d.ts.map +1 -0
- package/types/services/UserService.d.ts +5 -1
- package/types/services/UserService.d.ts.map +1 -1
- package/types/setup/LoadIdentityConfigFromEnv.d.ts.map +1 -1
- package/types/setup/SetProjectPasswordPolicy.d.ts +5 -0
- package/types/setup/SetProjectPasswordPolicy.d.ts.map +1 -0
- package/types/utils/PasswordPolicySchemaFactory.d.ts +9 -0
- package/types/utils/PasswordPolicySchemaFactory.d.ts.map +1 -0
- package/types/utils/getPasswordEnvPolicy.d.ts +5 -0
- 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 };
|
|
@@ -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
|
};
|
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 {};
|
|
@@ -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;
|
|
@@ -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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defaultPasswordPolicy } from "../constants/defaultPasswordPolicy.js";
|
|
2
|
+
import { PartialPasswordPolicySchema, PasswordPolicySchema } from "../schemas/PasswordPolicySchema.js";
|
|
3
|
+
import getPasswordEnvPolicy from "../utils/getPasswordEnvPolicy.js";
|
|
4
|
+
class PasswordPolicyResolver {
|
|
5
|
+
async resolve(projectContext) {
|
|
6
|
+
const projectPolicy = await this.getProjectPolicy(projectContext);
|
|
7
|
+
const envPolicy = getPasswordEnvPolicy();
|
|
8
|
+
return PasswordPolicySchema.parse({
|
|
9
|
+
...defaultPasswordPolicy,
|
|
10
|
+
...projectPolicy,
|
|
11
|
+
...envPolicy
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async getProjectPolicy(projectContext) {
|
|
15
|
+
return projectContext?.projectPolicy
|
|
16
|
+
? PartialPasswordPolicySchema.parse(projectContext.projectPolicy)
|
|
17
|
+
: {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export default PasswordPolicyResolver;
|
|
21
|
+
export { PasswordPolicyResolver };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { randomInt } from "crypto";
|
|
2
|
+
import { ZodError } from "zod";
|
|
3
|
+
import { ValidationError, ZodErrorToValidationError } from "@drax/common-back";
|
|
4
|
+
import AuthUtils from "../../utils/AuthUtils.js";
|
|
5
|
+
import PasswordPolicySchemaFactory from "../utils/PasswordPolicySchemaFactory.js";
|
|
6
|
+
class PasswordPolicyService {
|
|
7
|
+
constructor(resolver, userRepository, userPasswordHistoryService) {
|
|
8
|
+
this.resolver = resolver;
|
|
9
|
+
this.userRepository = userRepository;
|
|
10
|
+
this.userPasswordHistoryService = userPasswordHistoryService;
|
|
11
|
+
}
|
|
12
|
+
async getFinalPolicy(projectContext) {
|
|
13
|
+
return this.resolver.resolve(projectContext);
|
|
14
|
+
}
|
|
15
|
+
async getPasswordSchema(projectContext) {
|
|
16
|
+
const policy = await this.getFinalPolicy(projectContext);
|
|
17
|
+
return PasswordPolicySchemaFactory.create(policy);
|
|
18
|
+
}
|
|
19
|
+
async validatePassword(password, projectContext, options) {
|
|
20
|
+
const field = options?.field || "password";
|
|
21
|
+
try {
|
|
22
|
+
const schema = await this.getPasswordSchema(projectContext);
|
|
23
|
+
await schema.parseAsync(password);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (e instanceof ZodError) {
|
|
27
|
+
const validationError = ZodErrorToValidationError(e, { [field]: password });
|
|
28
|
+
validationError.errors = validationError.errors.map((error) => ({ ...error, field }));
|
|
29
|
+
throw validationError;
|
|
30
|
+
}
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
if (options?.userId) {
|
|
34
|
+
await this.validateBusinessRules(password, options.userId, projectContext, {
|
|
35
|
+
field,
|
|
36
|
+
currentPasswordHash: options.currentPasswordHash
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async generateCompatiblePassword(projectContext) {
|
|
41
|
+
const policy = await this.getFinalPolicy(projectContext);
|
|
42
|
+
const uppercaseChars = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
43
|
+
const lowercaseChars = "abcdefghijkmnopqrstuvwxyz";
|
|
44
|
+
const numericChars = "23456789";
|
|
45
|
+
const specialChars = "!@#$%&*_-+=";
|
|
46
|
+
const fallbackSpecial = policy.disallowSpaces ? "!" : " ";
|
|
47
|
+
const combinedChars = [
|
|
48
|
+
uppercaseChars,
|
|
49
|
+
lowercaseChars,
|
|
50
|
+
numericChars,
|
|
51
|
+
policy.requireSpecialChar ? specialChars : "",
|
|
52
|
+
policy.disallowSpaces ? "" : " "
|
|
53
|
+
].join("") || `${uppercaseChars}${lowercaseChars}${numericChars}${fallbackSpecial}`;
|
|
54
|
+
const chars = [];
|
|
55
|
+
if (policy.requireUppercase) {
|
|
56
|
+
chars.push(this.randomChar(uppercaseChars));
|
|
57
|
+
}
|
|
58
|
+
if (policy.requireLowercase) {
|
|
59
|
+
chars.push(this.randomChar(lowercaseChars));
|
|
60
|
+
}
|
|
61
|
+
if (policy.requireNumber) {
|
|
62
|
+
chars.push(this.randomChar(numericChars));
|
|
63
|
+
}
|
|
64
|
+
if (policy.requireSpecialChar) {
|
|
65
|
+
chars.push(this.randomChar(specialChars));
|
|
66
|
+
}
|
|
67
|
+
if (!chars.length) {
|
|
68
|
+
chars.push(this.randomChar(`${uppercaseChars}${lowercaseChars}${numericChars}`));
|
|
69
|
+
}
|
|
70
|
+
while (chars.length < policy.minLength) {
|
|
71
|
+
chars.push(this.randomChar(combinedChars));
|
|
72
|
+
}
|
|
73
|
+
const password = this.shuffle(chars).join("").slice(0, policy.maxLength);
|
|
74
|
+
await this.validatePassword(password, projectContext);
|
|
75
|
+
return password;
|
|
76
|
+
}
|
|
77
|
+
async getPasswordStatus(user, projectContext) {
|
|
78
|
+
const policy = await this.getFinalPolicy(projectContext);
|
|
79
|
+
if (!policy.expirationDays) {
|
|
80
|
+
return { expired: false, expiresAt: null };
|
|
81
|
+
}
|
|
82
|
+
const lastPasswordChange = await this.getLastPasswordChangeDate(user);
|
|
83
|
+
if (!lastPasswordChange) {
|
|
84
|
+
return { expired: false, expiresAt: null };
|
|
85
|
+
}
|
|
86
|
+
const expiresAt = new Date(lastPasswordChange);
|
|
87
|
+
expiresAt.setDate(expiresAt.getDate() + policy.expirationDays);
|
|
88
|
+
return {
|
|
89
|
+
expired: expiresAt.getTime() <= Date.now(),
|
|
90
|
+
expiresAt
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async recordPassword(userId, passwordHash) {
|
|
94
|
+
await this.userPasswordHistoryService?.create(userId, passwordHash);
|
|
95
|
+
}
|
|
96
|
+
async validateBusinessRules(password, userId, projectContext, options) {
|
|
97
|
+
const policy = await this.getFinalPolicy(projectContext);
|
|
98
|
+
if (!policy.preventReuse) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const field = options?.field || "password";
|
|
102
|
+
const recentHashes = await this.getRecentPasswordHashes(userId, policy.preventReuse, options?.currentPasswordHash);
|
|
103
|
+
const reused = recentHashes.some((item) => AuthUtils.checkPassword(password, item.passwordHash));
|
|
104
|
+
if (reused) {
|
|
105
|
+
throw new ValidationError([{ field, reason: "validation.password.preventReuse" }]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async getRecentPasswordHashes(userId, limit, currentPasswordHash) {
|
|
109
|
+
const recent = await this.userPasswordHistoryService?.findLatestByUserId(userId, limit) || [];
|
|
110
|
+
const hashes = [...recent];
|
|
111
|
+
if (currentPasswordHash && !hashes.some((item) => item.passwordHash === currentPasswordHash)) {
|
|
112
|
+
hashes.unshift({ user: userId, passwordHash: currentPasswordHash });
|
|
113
|
+
}
|
|
114
|
+
else if (!currentPasswordHash && this.userRepository) {
|
|
115
|
+
const user = await this.userRepository.findByIdWithPassword(userId);
|
|
116
|
+
if (user?.password && !hashes.some((item) => item.passwordHash === user.password)) {
|
|
117
|
+
hashes.unshift({ user: userId, passwordHash: user.password });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return hashes.slice(0, limit);
|
|
121
|
+
}
|
|
122
|
+
async getLastPasswordChangeDate(user) {
|
|
123
|
+
if (!user?._id || !this.userPasswordHistoryService) {
|
|
124
|
+
return user?.updatedAt ? new Date(user.updatedAt) : user?.createdAt ? new Date(user.createdAt) : null;
|
|
125
|
+
}
|
|
126
|
+
const latest = await this.userPasswordHistoryService.findLatestByUserId(user._id.toString(), 1);
|
|
127
|
+
if (latest[0]?.createdAt) {
|
|
128
|
+
return new Date(latest[0].createdAt);
|
|
129
|
+
}
|
|
130
|
+
return user?.updatedAt ? new Date(user.updatedAt) : user?.createdAt ? new Date(user.createdAt) : null;
|
|
131
|
+
}
|
|
132
|
+
randomChar(source) {
|
|
133
|
+
return source[randomInt(0, source.length)];
|
|
134
|
+
}
|
|
135
|
+
shuffle(chars) {
|
|
136
|
+
const items = [...chars];
|
|
137
|
+
for (let i = items.length - 1; i > 0; i -= 1) {
|
|
138
|
+
const j = randomInt(0, i + 1);
|
|
139
|
+
const temp = items[i];
|
|
140
|
+
items[i] = items[j];
|
|
141
|
+
items[j] = temp;
|
|
142
|
+
}
|
|
143
|
+
return items;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export default PasswordPolicyService;
|
|
147
|
+
export { PasswordPolicyService };
|