cca-auth-module 0.2.1 → 0.2.21

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/index.mjs CHANGED
@@ -139,6 +139,7 @@ import { jwtDecode } from "jwt-decode";
139
139
  import * as yup from "yup";
140
140
  import { UserRole } from "cca-entities";
141
141
  import bcrypt from "bcrypt";
142
+ import crypto from "crypto";
142
143
  var schemas = {
143
144
  id: yup.string().uuid("Invalid user ID format"),
144
145
  email: yup.string().email("Invalid email format").max(255, "Email cannot exceed 255 characters"),
@@ -151,8 +152,9 @@ var schemas = {
151
152
  };
152
153
  var validateEmail = /* @__PURE__ */ __name(async (email, repository) => {
153
154
  try {
154
- await schemas.email.validate(email?.trim().toLowerCase());
155
- const user = await repository.findByEmail(email);
155
+ const normalizedEmail = email?.trim().toLowerCase();
156
+ await schemas.email.validate(normalizedEmail);
157
+ const user = await repository.findByEmail(normalizedEmail);
156
158
  if (!user) {
157
159
  throw new NotFoundError(
158
160
  "The email address or password is incorrect. Please retry"
@@ -174,8 +176,9 @@ var validatePassword = /* @__PURE__ */ __name(async (password) => {
174
176
  }, "validatePassword");
175
177
  var validateEmailUniqueness = /* @__PURE__ */ __name(async (repository, email, excludeUserId) => {
176
178
  try {
177
- await schemas.email.validate(email?.trim().toLowerCase());
178
- const existingUser = await repository.findByEmail(email);
179
+ const normalizedEmail = email?.trim().toLowerCase();
180
+ await schemas.email.validate(normalizedEmail);
181
+ const existingUser = await repository.findByEmail(normalizedEmail);
179
182
  if (!existingUser) return;
180
183
  if (existingUser.id === excludeUserId) return;
181
184
  throw new ValidationError(`Email ${email} is already in use.`);
@@ -220,7 +223,14 @@ var validateAdminSecret = /* @__PURE__ */ __name(async (secretPassword) => {
220
223
  if (!config.adminSecretPassword) {
221
224
  throw new ValidationError("ADMIN_SECRET_PASSWORD not found in config");
222
225
  }
223
- if (parseInt(secretPassword) !== parseInt(config.adminSecretPassword)) {
226
+ const provided = secretPassword.trim();
227
+ const expected = config.adminSecretPassword.trim();
228
+ if (provided.length !== expected.length) {
229
+ throw new ValidationError("Invalid admin password");
230
+ }
231
+ const providedBuf = Buffer.from(provided);
232
+ const expectedBuf = Buffer.from(expected);
233
+ if (!crypto.timingSafeEqual(providedBuf, expectedBuf)) {
224
234
  throw new ValidationError("Invalid admin password");
225
235
  }
226
236
  } catch (error) {
@@ -250,8 +260,9 @@ var _LoginUseCase = class _LoginUseCase {
250
260
  if (!account) {
251
261
  throw new NotFoundError(`${isAdmin ? "Admin" : "User"} account not found or inactive`);
252
262
  }
253
- const accessToken = this.jwtService.generateAccessToken(account, auth.role);
254
- const expiresAt = jwtDecode(accessToken).exp;
263
+ const accessToken = this.jwtService.generateAccessToken(account, auth.role, false);
264
+ const decoded = jwtDecode(accessToken);
265
+ const expiresAt = decoded.exp ?? 0;
255
266
  return { id: account.id, accessToken, expiresAt, enabled: auth.twoFactorEnabled };
256
267
  }
257
268
  };
@@ -267,11 +278,17 @@ var _LogoutUseCase = class _LogoutUseCase {
267
278
  async initialize() {
268
279
  await validateRepository2(this.repository, (repo) => repo.getAll());
269
280
  }
270
- async execute(authId) {
281
+ async execute(userId) {
271
282
  try {
272
- await this.repository.logout(authId);
283
+ if (!userId) {
284
+ throw new NotFoundError("User ID is required");
285
+ }
286
+ await this.repository.logout(userId);
273
287
  } catch (error) {
274
- new NotFoundError("Auth not found");
288
+ if (error instanceof NotFoundError) {
289
+ throw error;
290
+ }
291
+ throw new NotFoundError("Auth not found");
275
292
  }
276
293
  }
277
294
  };
@@ -443,7 +460,11 @@ var _RegisterUseCase = class _RegisterUseCase {
443
460
  const authEntity = mapper.map(dto, RegisterDTO, AuthEntity3);
444
461
  const userOrAdminEntity = isAdmin ? mapper.map(dto, RegisterDTO, AdminEntity2) : mapper.map(dto, RegisterDTO, UserEntity2);
445
462
  userOrAdminEntity.updatedAt = void 0;
446
- authEntity.user = userOrAdminEntity;
463
+ if (isAdmin) {
464
+ authEntity.admin = userOrAdminEntity;
465
+ } else {
466
+ authEntity.user = userOrAdminEntity;
467
+ }
447
468
  authEntity.password = hashedPassword;
448
469
  authEntity.refreshToken = "";
449
470
  return authEntity;
@@ -464,18 +485,23 @@ var _RefreshTokenUseCase = class _RefreshTokenUseCase {
464
485
  }
465
486
  async execute(refreshToken) {
466
487
  try {
488
+ if (!refreshToken) return null;
467
489
  const decoded = await this.service.verifyRefreshToken(refreshToken);
468
490
  if (!decoded.userId) return null;
469
- const authEntity = await this.repository.findByUseAdminId(decoded.userId);
491
+ const authEntity = await this.repository.findByUserOrAdminId(decoded.userId);
470
492
  if (!authEntity) return null;
493
+ if (!authEntity.refreshToken || authEntity.refreshToken !== refreshToken) return null;
471
494
  const account = authEntity.user ?? authEntity.admin;
472
495
  if (!account) return null;
473
- const accessToken = this.service.generateAccessToken(account, authEntity.role);
496
+ const accessToken = this.service.generateAccessToken(
497
+ account,
498
+ authEntity.role,
499
+ !!authEntity.twoFactorEnabled
500
+ );
474
501
  const newRefreshToken = this.service.generateRefreshToken(account);
475
502
  await this.repository.update(authEntity.id, { refreshToken: newRefreshToken });
476
503
  return { accessToken, refreshToken: newRefreshToken };
477
504
  } catch (error) {
478
- console.error("Refresh token failed:", error);
479
505
  return null;
480
506
  }
481
507
  }
@@ -541,6 +567,9 @@ var _TwoFactorEnableUseCase = class _TwoFactorEnableUseCase {
541
567
  if (!token) {
542
568
  throw new TwoFactorError("Token is required");
543
569
  }
570
+ if (!userId) {
571
+ throw new TwoFactorError("User ID is required");
572
+ }
544
573
  const auth = await this.authRepository.findByUserOrAdminId(userId);
545
574
  if (!auth || !auth.twoFactorSecret) {
546
575
  throw new TwoFactorError("Please set up two-factor authentication first");
@@ -623,7 +652,7 @@ var _TwoFactorVerifyUseCase = class _TwoFactorVerifyUseCase {
623
652
  if (!account) throw new NotFoundError("User or Admin account not found for AuthEntity");
624
653
  account.lastLoginAt = /* @__PURE__ */ new Date();
625
654
  account.isActive = true;
626
- await this.authRepository.update(auth.id, auth);
655
+ await this.authRepository.saveAccount(account);
627
656
  }
628
657
  async updateUserRefreshToken(auth, refreshToken) {
629
658
  auth.refreshToken = refreshToken;
@@ -633,7 +662,7 @@ var _TwoFactorVerifyUseCase = class _TwoFactorVerifyUseCase {
633
662
  const account = auth.user ?? auth.admin;
634
663
  if (!account) throw new NotFoundError("User or Admin account not found for AuthEntity");
635
664
  return {
636
- accessToken: this.jwtService.generateAccessToken(account, auth.role),
665
+ accessToken: this.jwtService.generateAccessToken(account, auth.role, true),
637
666
  refreshToken: this.jwtService.generateRefreshToken(account)
638
667
  };
639
668
  }
@@ -655,7 +684,6 @@ var _TwoFactorDisableUseCase = class _TwoFactorDisableUseCase {
655
684
  this.twoFactorService.initialize(),
656
685
  validateRepository7(this.authRepository, (repo) => repo.getAll())
657
686
  ]);
658
- 4;
659
687
  this.isInitialized = true;
660
688
  }
661
689
  async execute(userId, dto) {
@@ -663,6 +691,12 @@ var _TwoFactorDisableUseCase = class _TwoFactorDisableUseCase {
663
691
  await this.initialize();
664
692
  }
665
693
  const { token } = dto;
694
+ if (!token) {
695
+ throw new TwoFactorError("Token is required");
696
+ }
697
+ if (!userId) {
698
+ throw new TwoFactorError("User ID is required");
699
+ }
666
700
  const user = await this.authRepository.findByUserOrAdminId(userId);
667
701
  if (!user || !user.twoFactorSecret || !user.twoFactorEnabled) {
668
702
  throw new TwoFactorError("Two-factor authentication is not enabled");
@@ -738,11 +772,13 @@ var _AuthController = class _AuthController {
738
772
  }
739
773
  const result = await this.loginUseCase.execute(loginDTO, adminPassword);
740
774
  const adminLoginData = {
741
- message: result,
775
+ accessToken: result.accessToken,
776
+ userId: result.id,
777
+ expiresAt: result.expiresAt,
742
778
  auth: this.createAuthData(
743
779
  true,
744
- false,
745
- AUTH_STATUS.BASIC_AUTH,
780
+ result.enabled ?? false,
781
+ result.enabled ?? false ? AUTH_STATUS.PENDING_VERIFICATION : AUTH_STATUS.BASIC_AUTH,
746
782
  false
747
783
  )
748
784
  };
@@ -753,7 +789,8 @@ var _AuthController = class _AuthController {
753
789
  }, "adminLogin");
754
790
  this.logout = /* @__PURE__ */ __name(async (req, res, next) => {
755
791
  try {
756
- await this.logoutUseCase.execute(req.body.id);
792
+ const userId = req.auth?.userId ?? req.body.id;
793
+ await this.logoutUseCase.execute(userId);
757
794
  const logoutData = {
758
795
  auth: this.createAuthData(
759
796
  false,
@@ -814,10 +851,10 @@ var _AuthController = class _AuthController {
814
851
  }, "refreshToken");
815
852
  this.setup2FA = /* @__PURE__ */ __name(async (req, res, next) => {
816
853
  try {
817
- if (!req.auth?.id) {
854
+ if (!req.auth?.userId) {
818
855
  throw new ForbiddenError("User authentication required");
819
856
  }
820
- const result = await this.twoFactorSetupUseCase.execute(req.auth.id);
857
+ const result = await this.twoFactorSetupUseCase.execute(req.auth.userId);
821
858
  const setupData = {
822
859
  qrCode: result.qrCodeUrl,
823
860
  auth: this.createAuthData(true, false, AUTH_STATUS.NEEDS_SETUP)
@@ -833,7 +870,10 @@ var _AuthController = class _AuthController {
833
870
  }, "setup2FA");
834
871
  this.enable2FA = /* @__PURE__ */ __name(async (req, res, next) => {
835
872
  try {
836
- const dto = { ...req.body, userId: req.auth?.id };
873
+ if (!req.auth?.userId) {
874
+ throw new ForbiddenError("User authentication required");
875
+ }
876
+ const dto = { ...req.body, userId: req.auth?.userId };
837
877
  await this.twoFactorEnableUseCase.execute(dto);
838
878
  const enableData = {
839
879
  enabledAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -850,16 +890,22 @@ var _AuthController = class _AuthController {
850
890
  }, "enable2FA");
851
891
  this.verify2FA = /* @__PURE__ */ __name(async (req, res, next) => {
852
892
  try {
853
- const dto = req.body;
893
+ if (!req.auth?.userId) {
894
+ throw new ForbiddenError("User authentication required");
895
+ }
896
+ const dto = { ...req.body, userId: req.auth.userId };
854
897
  const result = await this.twoFactorVerifyUseCase.execute(dto);
898
+ if (!result) {
899
+ throw new Error("Two-factor verification failed");
900
+ }
855
901
  const verifyData = {
856
- token: result?.token,
857
- refreshToken: result?.refreshToken,
902
+ token: result.token,
903
+ refreshToken: result.refreshToken,
858
904
  user: {
859
- id: result?.data?.id,
860
- email: result?.data?.email,
861
- name: result?.data?.name,
862
- role: result?.data?.role
905
+ id: result.data?.id,
906
+ email: result.data?.email,
907
+ name: result.data?.name,
908
+ role: result.data?.role
863
909
  },
864
910
  auth: this.createAuthData(true, true, AUTH_STATUS.FULL_AUTH, true)
865
911
  };
@@ -874,7 +920,10 @@ var _AuthController = class _AuthController {
874
920
  }, "verify2FA");
875
921
  this.disable2FA = /* @__PURE__ */ __name(async (req, res, next) => {
876
922
  try {
877
- const userId = req.auth.id;
923
+ const userId = req.auth?.userId;
924
+ if (!userId) {
925
+ throw new ForbiddenError("User authentication required");
926
+ }
878
927
  const dto = req.body;
879
928
  await this.twoFactorDisableUseCase.execute(userId, dto);
880
929
  const disableData = {
@@ -938,11 +987,14 @@ var _RequireComplete2FA = class _RequireComplete2FA {
938
987
  return res.status(401).json({ message: "Authentication required" });
939
988
  }
940
989
  const decoded = await this.jwtService.verifyAccessToken(token);
990
+ if (!decoded.userId) {
991
+ return res.status(401).json({ message: "Invalid token payload" });
992
+ }
941
993
  if (!decoded.twoFactorAuthenticated) {
942
994
  return res.status(403).json({
943
995
  message: "Two-factor authentication required",
944
996
  code: "REQUIRE_2FA",
945
- userId: decoded.id
997
+ userId: decoded.userId
946
998
  });
947
999
  }
948
1000
  req.auth = { ...decoded, twoFactorAuthenticated: true };
@@ -966,13 +1018,20 @@ var _AuthRepository = class _AuthRepository extends BaseRepository {
966
1018
  return result;
967
1019
  }
968
1020
  async create(entity) {
969
- return super.create(entity);
1021
+ const now = /* @__PURE__ */ new Date();
1022
+ const auth = this.repository.create({
1023
+ ...entity,
1024
+ createdAt: now,
1025
+ updatedAt: now,
1026
+ isDeleted: false
1027
+ });
1028
+ return this.repository.save(auth);
970
1029
  }
971
1030
  async findByUserOrAdminId(id) {
972
- return await this.repository.createQueryBuilder("auth").leftJoinAndSelect("auth.user", "user").leftJoinAndSelect("auth.admin", "admin").addSelect("auth.twoFactorSecret").where("user.id = :id", { id }).orWhere("admin.id = :id", { id }).getOne();
1031
+ return await this.repository.createQueryBuilder("auth").leftJoinAndSelect("auth.user", "user").leftJoinAndSelect("auth.admin", "admin").addSelect(["auth.twoFactorSecret", "auth.refreshToken"]).where("user.id = :id", { id }).orWhere("admin.id = :id", { id }).getOne();
973
1032
  }
974
1033
  async findByUseAdminId(userId, isAdmin = false) {
975
- const query = this.repository.createQueryBuilder("auth").addSelect("auth.twoFactorSecret");
1034
+ const query = this.repository.createQueryBuilder("auth").addSelect(["auth.twoFactorSecret", "auth.refreshToken"]);
976
1035
  if (isAdmin) {
977
1036
  query.leftJoinAndSelect("auth.admin", "admin").where("admin.id = :userId", { userId });
978
1037
  } else {
@@ -989,6 +1048,7 @@ var _AuthRepository = class _AuthRepository extends BaseRepository {
989
1048
  if (account) {
990
1049
  auth.refreshToken = "";
991
1050
  account.isActive = false;
1051
+ await this.saveAccount(account);
992
1052
  }
993
1053
  await this.update(auth.id, auth);
994
1054
  }
@@ -1023,6 +1083,9 @@ var _AuthRepository = class _AuthRepository extends BaseRepository {
1023
1083
  }
1024
1084
  return auth.twoFactorSecret;
1025
1085
  }
1086
+ async saveAccount(account) {
1087
+ await this.repository.manager.save(account);
1088
+ }
1026
1089
  };
1027
1090
  __name(_AuthRepository, "AuthRepository");
1028
1091
  var AuthRepository = _AuthRepository;
@@ -1033,34 +1096,40 @@ import { validateRepository as validateRepository8 } from "cca-core";
1033
1096
  var _JwtAuthService = class _JwtAuthService {
1034
1097
  constructor(repository, config) {
1035
1098
  this.repository = repository;
1036
- this.loadConfig(config);
1037
- }
1038
- async loadConfig(config) {
1039
- const configData = await createConfigInstance();
1040
- this.jwtConfig = {
1041
- accessTokenSecret: configData.accessTokenSecret,
1042
- refreshTokenSecret: configData.refreshTokenSecret,
1043
- accessTokenExpiry: parseInt(configData.accessTokenExpiry, 10),
1044
- refreshTokenExpiry: parseInt(configData.refreshTokenExpiry, 10),
1045
- ...config
1046
- };
1099
+ if (config) {
1100
+ this.jwtConfig = {
1101
+ accessTokenSecret: config.accessTokenSecret,
1102
+ refreshTokenSecret: config.refreshTokenSecret,
1103
+ accessTokenExpiry: config.accessTokenExpiry,
1104
+ refreshTokenExpiry: config.refreshTokenExpiry
1105
+ };
1106
+ }
1047
1107
  this.validateConfiguration();
1048
1108
  }
1049
1109
  async initialize() {
1050
1110
  await validateRepository8(this.repository, (repo) => repo.getAll());
1111
+ this.validateConfiguration();
1051
1112
  }
1052
1113
  validateConfiguration() {
1053
1114
  if (!this.jwtConfig?.accessTokenSecret || !this.jwtConfig?.refreshTokenSecret) {
1054
1115
  throw new JwtError("JWT secrets required in config");
1055
1116
  }
1117
+ if (this.jwtConfig.accessTokenExpiry == null || this.jwtConfig.refreshTokenExpiry == null) {
1118
+ throw new JwtError("JWT expirations required in config");
1119
+ }
1056
1120
  }
1057
1121
  verifyJwtConfig() {
1058
1122
  if (!this.jwtConfig) throw new JwtError("JWT config not loaded");
1059
1123
  }
1060
- generateAccessToken(user, role) {
1124
+ generateAccessToken(user, role, twoFactorAuthenticated = false) {
1061
1125
  this.verifyJwtConfig();
1062
1126
  return jwt.sign(
1063
- { userId: user.id, email: user.email, role },
1127
+ {
1128
+ userId: user.id,
1129
+ email: user.email,
1130
+ role,
1131
+ twoFactorAuthenticated
1132
+ },
1064
1133
  this.jwtConfig.accessTokenSecret,
1065
1134
  { expiresIn: this.jwtConfig.accessTokenExpiry }
1066
1135
  );
@@ -1075,11 +1144,8 @@ var _JwtAuthService = class _JwtAuthService {
1075
1144
  }
1076
1145
  async verifyToken(token, secret) {
1077
1146
  try {
1078
- console.log("Verifying token:", token);
1079
- console.log("Using secret:", secret);
1080
1147
  return jwt.verify(token, secret);
1081
1148
  } catch (error) {
1082
- console.error("Error verifying token:", error);
1083
1149
  throw new UnauthorizedError();
1084
1150
  }
1085
1151
  }
@@ -1102,9 +1168,11 @@ var _TwoFactorService = class _TwoFactorService {
1102
1168
  constructor(config) {
1103
1169
  this.initialized = false;
1104
1170
  this.config = config;
1171
+ const parsedTokenWindow = Number.parseInt(config.tokenWindow, 10);
1172
+ const parsedSecretLength = Number.parseInt(config.secretLength, 10);
1105
1173
  this.twoFactorConfig = {
1106
- tokenWindow: parseInt(config.tokenWindow) ?? 1,
1107
- secretLength: parseInt(config.secretLength) ?? 20,
1174
+ tokenWindow: Number.isFinite(parsedTokenWindow) ? parsedTokenWindow : 1,
1175
+ secretLength: Number.isFinite(parsedSecretLength) ? parsedSecretLength : 20,
1108
1176
  qrCodeOptions: {
1109
1177
  errorCorrectionLevel: "M",
1110
1178
  margin: 4,
@@ -1193,25 +1261,35 @@ async function createAuthContainer(database) {
1193
1261
  database.getRepository(AuthEntity5)
1194
1262
  );
1195
1263
  container.registerRepository("AuthRepository", authRepository);
1196
- const jwtAuthService = new JwtAuthService(authRepository);
1197
- container.registerService("JwtAuthService", jwtAuthService);
1198
1264
  const configData = await createConfigInstance();
1265
+ const parseExpiry = /* @__PURE__ */ __name((value) => {
1266
+ const numeric = Number(value);
1267
+ return Number.isFinite(numeric) ? numeric : value;
1268
+ }, "parseExpiry");
1269
+ const jwtConfig = {
1270
+ accessTokenSecret: configData.accessTokenSecret,
1271
+ refreshTokenSecret: configData.refreshTokenSecret,
1272
+ accessTokenExpiry: parseExpiry(configData.accessTokenExpiry),
1273
+ refreshTokenExpiry: parseExpiry(configData.refreshTokenExpiry)
1274
+ };
1275
+ const configuredJwtAuthService = new JwtAuthService(authRepository, jwtConfig);
1276
+ container.registerService("JwtAuthService", configuredJwtAuthService);
1199
1277
  const twoFactorService = new TwoFactorService(configData);
1200
1278
  container.registerService("TwoFactorService", twoFactorService);
1201
- const requireComplete2FA = new RequireComplete2FA(jwtAuthService);
1202
- const loginUseCase = new LoginUseCase(authRepository, jwtAuthService);
1279
+ const requireComplete2FA = new RequireComplete2FA(configuredJwtAuthService);
1280
+ const loginUseCase = new LoginUseCase(authRepository, configuredJwtAuthService);
1203
1281
  const logoutUseCase = new LogoutUseCase(authRepository);
1204
1282
  const registerUseCase = new RegisterUseCase(authRepository);
1205
1283
  const refreshTokenUseCase = new RefreshTokenUseCase(
1206
1284
  authRepository,
1207
- jwtAuthService
1285
+ configuredJwtAuthService
1208
1286
  );
1209
1287
  const twoFactorSetupUseCase = new TwoFactorSetupUseCase(twoFactorService, authRepository);
1210
1288
  const twoFactorEnableUseCase = new TwoFactorEnableUseCase(twoFactorService, authRepository);
1211
1289
  const twoFactorVerifyUseCase = new TwoFactorVerifyUseCase(
1212
1290
  twoFactorService,
1213
1291
  authRepository,
1214
- jwtAuthService
1292
+ configuredJwtAuthService
1215
1293
  );
1216
1294
  const twoFactorDisableUseCase = new TwoFactorDisableUseCase(twoFactorService, authRepository);
1217
1295
  container.registerService("LoginUseCase", loginUseCase);