vr-commons 1.0.112 → 1.0.114

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.
@@ -21,7 +21,7 @@ const checkUserAuthentication = (allowedRoles) => async (req, res, next) => {
21
21
  }
22
22
  catch (error) {
23
23
  if (error instanceof jsonwebtoken_1.default.TokenExpiredError) {
24
- return (0, response_utils_1.sendErrorResponse)(res, "Token expired. Please re-verify your phone.", 401);
24
+ return (0, response_utils_1.sendErrorResponse)(res, "Token expired. Please re-verify your Email/Phone", 401);
25
25
  }
26
26
  return (0, response_utils_1.sendErrorResponse)(res, "Token invalid", 401);
27
27
  }
@@ -40,7 +40,7 @@ const checkUserAuthentication = (allowedRoles) => async (req, res, next) => {
40
40
  }
41
41
  // 🔐 Token versioning (logout all devices)
42
42
  if (user.tokenVersion !== payload.tokenVersion) {
43
- return (0, response_utils_1.sendErrorResponse)(res, "Session expired. Please re-verify your phone.", 401);
43
+ return (0, response_utils_1.sendErrorResponse)(res, "Session expired. Please re-verify your Email/Phone", 401);
44
44
  }
45
45
  // 🧱 Role enforcement
46
46
  if (!allowedRoles.includes(user.securityClearance.role)) {
@@ -11,8 +11,7 @@ export interface BaseUserProfile {
11
11
  id: string;
12
12
  firstName: string;
13
13
  lastName: string;
14
- primaryPhone: string;
15
- email: string | null;
14
+ primaryContact: string;
16
15
  gender: "male" | "female";
17
16
  birthDate: number | null;
18
17
  birthMonth: number | null;
@@ -24,8 +24,7 @@ const formatUserProfile = (user, viewerType = "ADMIN", options = {}) => {
24
24
  id: user.id,
25
25
  firstName: user.firstName,
26
26
  lastName: user.lastName,
27
- primaryPhone: user.primaryPhone?.phoneNumber,
28
- email: user.email,
27
+ primaryContact: user.primaryContact?.contactValue,
29
28
  gender: user.gender || "male",
30
29
  birthDate: user.birthDate || null,
31
30
  birthMonth: user.birthMonth || null,
@@ -394,11 +393,11 @@ const validateUniqueFields = async (fields, excludeUserId) => {
394
393
  const errors = [];
395
394
  // Check phone number (in PhoneContact table)
396
395
  if (fields.phoneNumber) {
397
- const phoneWhere = { phoneNumber: fields.phoneNumber, isActive: true };
396
+ const phoneWhere = { conactValue: fields.phoneNumber, isActive: true };
398
397
  if (excludeUserId) {
399
398
  phoneWhere.userId = { [sequelize_1.Op.ne]: excludeUserId };
400
399
  }
401
- const existingPhone = await vr_models_1.PhoneContact.findOne({
400
+ const existingPhone = await vr_models_1.Contact.findOne({
402
401
  where: phoneWhere,
403
402
  include: [{ model: vr_models_1.User, as: "user", attributes: ["id"] }],
404
403
  });
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
2
  export declare const phoneRegex: RegExp;
3
+ export declare const emailRegex: RegExp;
3
4
  export declare const strongPasswordRegex: RegExp;
5
+ export declare const detectContactType: (value: string) => "EMAIL" | "PHONE" | "WHATSAPP" | "UNKNOWN";
6
+ export declare const validateContactValue: (value: string) => boolean;
4
7
  export declare const updateProfileSchema: z.ZodObject<{
5
8
  body: z.ZodObject<{
6
9
  firstName: z.ZodOptional<z.ZodString>;
@@ -28,6 +31,40 @@ export declare const updateProfileSchema: z.ZodObject<{
28
31
  plateNumber?: string | undefined;
29
32
  };
30
33
  }>;
34
+ export declare const updateContactSchema: z.ZodObject<{
35
+ body: z.ZodObject<{
36
+ contactValue: z.ZodEffects<z.ZodString, string, string>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ contactValue: string;
39
+ }, {
40
+ contactValue: string;
41
+ }>;
42
+ }, "strip", z.ZodTypeAny, {
43
+ body: {
44
+ contactValue: string;
45
+ };
46
+ }, {
47
+ body: {
48
+ contactValue: string;
49
+ };
50
+ }>;
51
+ export declare const changeEmailSchema: z.ZodObject<{
52
+ body: z.ZodObject<{
53
+ newEmail: z.ZodString;
54
+ }, "strip", z.ZodTypeAny, {
55
+ newEmail: string;
56
+ }, {
57
+ newEmail: string;
58
+ }>;
59
+ }, "strip", z.ZodTypeAny, {
60
+ body: {
61
+ newEmail: string;
62
+ };
63
+ }, {
64
+ body: {
65
+ newEmail: string;
66
+ };
67
+ }>;
31
68
  export declare const requestPhoneChangeSchema: z.ZodObject<{
32
69
  body: z.ZodObject<{
33
70
  newPhoneNumber: z.ZodString;
@@ -67,6 +104,113 @@ export declare const verifyPhoneChangeSchema: z.ZodObject<{
67
104
  newPhoneOtp: string;
68
105
  };
69
106
  }>;
107
+ export declare const verifyEmailChangeSchema: z.ZodObject<{
108
+ body: z.ZodObject<{
109
+ emailOtp: z.ZodString;
110
+ }, "strip", z.ZodTypeAny, {
111
+ emailOtp: string;
112
+ }, {
113
+ emailOtp: string;
114
+ }>;
115
+ }, "strip", z.ZodTypeAny, {
116
+ body: {
117
+ emailOtp: string;
118
+ };
119
+ }, {
120
+ body: {
121
+ emailOtp: string;
122
+ };
123
+ }>;
124
+ export declare const addContactSchema: z.ZodObject<{
125
+ body: z.ZodObject<{
126
+ contactValue: z.ZodEffects<z.ZodString, string, string>;
127
+ type: z.ZodEffects<z.ZodOptional<z.ZodEnum<["EMAIL", "PHONE", "WHATSAPP"]>>, "EMAIL" | "PHONE" | "WHATSAPP" | undefined, "EMAIL" | "PHONE" | "WHATSAPP" | undefined>;
128
+ }, "strip", z.ZodTypeAny, {
129
+ contactValue: string;
130
+ type?: "EMAIL" | "PHONE" | "WHATSAPP" | undefined;
131
+ }, {
132
+ contactValue: string;
133
+ type?: "EMAIL" | "PHONE" | "WHATSAPP" | undefined;
134
+ }>;
135
+ }, "strip", z.ZodTypeAny, {
136
+ body: {
137
+ contactValue: string;
138
+ type?: "EMAIL" | "PHONE" | "WHATSAPP" | undefined;
139
+ };
140
+ }, {
141
+ body: {
142
+ contactValue: string;
143
+ type?: "EMAIL" | "PHONE" | "WHATSAPP" | undefined;
144
+ };
145
+ }>;
146
+ export declare const deleteContactSchema: z.ZodObject<{
147
+ params: z.ZodObject<{
148
+ contactId: z.ZodString;
149
+ }, "strip", z.ZodTypeAny, {
150
+ contactId: string;
151
+ }, {
152
+ contactId: string;
153
+ }>;
154
+ }, "strip", z.ZodTypeAny, {
155
+ params: {
156
+ contactId: string;
157
+ };
158
+ }, {
159
+ params: {
160
+ contactId: string;
161
+ };
162
+ }>;
163
+ export declare const setPrimaryContactSchema: z.ZodObject<{
164
+ params: z.ZodObject<{
165
+ contactId: z.ZodString;
166
+ }, "strip", z.ZodTypeAny, {
167
+ contactId: string;
168
+ }, {
169
+ contactId: string;
170
+ }>;
171
+ }, "strip", z.ZodTypeAny, {
172
+ params: {
173
+ contactId: string;
174
+ };
175
+ }, {
176
+ params: {
177
+ contactId: string;
178
+ };
179
+ }>;
180
+ export declare const verifyContactSchema: z.ZodObject<{
181
+ params: z.ZodObject<{
182
+ contactId: z.ZodString;
183
+ }, "strip", z.ZodTypeAny, {
184
+ contactId: string;
185
+ }, {
186
+ contactId: string;
187
+ }>;
188
+ }, "strip", z.ZodTypeAny, {
189
+ params: {
190
+ contactId: string;
191
+ };
192
+ }, {
193
+ params: {
194
+ contactId: string;
195
+ };
196
+ }>;
197
+ export declare const confirmVerificationSchema: z.ZodObject<{
198
+ body: z.ZodObject<{
199
+ otp: z.ZodString;
200
+ }, "strip", z.ZodTypeAny, {
201
+ otp: string;
202
+ }, {
203
+ otp: string;
204
+ }>;
205
+ }, "strip", z.ZodTypeAny, {
206
+ body: {
207
+ otp: string;
208
+ };
209
+ }, {
210
+ body: {
211
+ otp: string;
212
+ };
213
+ }>;
70
214
  export declare const deleteAccountSchema: z.ZodObject<{
71
215
  body: z.ZodObject<{
72
216
  confirmation: z.ZodLiteral<"DELETE">;
@@ -111,3 +255,90 @@ export declare const changePasswordSchema: z.ZodObject<{
111
255
  newPassword: string;
112
256
  };
113
257
  }>;
258
+ export declare const loginSchema: z.ZodObject<{
259
+ body: z.ZodObject<{
260
+ contactValue: z.ZodEffects<z.ZodString, string, string>;
261
+ password: z.ZodString;
262
+ }, "strip", z.ZodTypeAny, {
263
+ password: string;
264
+ contactValue: string;
265
+ }, {
266
+ password: string;
267
+ contactValue: string;
268
+ }>;
269
+ }, "strip", z.ZodTypeAny, {
270
+ body: {
271
+ password: string;
272
+ contactValue: string;
273
+ };
274
+ }, {
275
+ body: {
276
+ password: string;
277
+ contactValue: string;
278
+ };
279
+ }>;
280
+ export declare const forgotPasswordSchema: z.ZodObject<{
281
+ body: z.ZodObject<{
282
+ contactValue: z.ZodEffects<z.ZodString, string, string>;
283
+ }, "strip", z.ZodTypeAny, {
284
+ contactValue: string;
285
+ }, {
286
+ contactValue: string;
287
+ }>;
288
+ }, "strip", z.ZodTypeAny, {
289
+ body: {
290
+ contactValue: string;
291
+ };
292
+ }, {
293
+ body: {
294
+ contactValue: string;
295
+ };
296
+ }>;
297
+ export declare const resetPasswordSchema: z.ZodEffects<z.ZodObject<{
298
+ body: z.ZodObject<{
299
+ newPassword: z.ZodString;
300
+ confirmPassword: z.ZodString;
301
+ }, "strip", z.ZodTypeAny, {
302
+ newPassword: string;
303
+ confirmPassword: string;
304
+ }, {
305
+ newPassword: string;
306
+ confirmPassword: string;
307
+ }>;
308
+ }, "strip", z.ZodTypeAny, {
309
+ body: {
310
+ newPassword: string;
311
+ confirmPassword: string;
312
+ };
313
+ }, {
314
+ body: {
315
+ newPassword: string;
316
+ confirmPassword: string;
317
+ };
318
+ }>, {
319
+ body: {
320
+ newPassword: string;
321
+ confirmPassword: string;
322
+ };
323
+ }, {
324
+ body: {
325
+ newPassword: string;
326
+ confirmPassword: string;
327
+ };
328
+ }>;
329
+ export type UpdateProfileSchema = z.infer<typeof updateProfileSchema>;
330
+ export type UpdateContactSchema = z.infer<typeof updateContactSchema>;
331
+ export type ChangeEmailSchema = z.infer<typeof changeEmailSchema>;
332
+ export type RequestPhoneChangeSchema = z.infer<typeof requestPhoneChangeSchema>;
333
+ export type VerifyPhoneChangeSchema = z.infer<typeof verifyPhoneChangeSchema>;
334
+ export type VerifyEmailChangeSchema = z.infer<typeof verifyEmailChangeSchema>;
335
+ export type AddContactSchema = z.infer<typeof addContactSchema>;
336
+ export type DeleteContactSchema = z.infer<typeof deleteContactSchema>;
337
+ export type SetPrimaryContactSchema = z.infer<typeof setPrimaryContactSchema>;
338
+ export type VerifyContactSchema = z.infer<typeof verifyContactSchema>;
339
+ export type ConfirmVerificationSchema = z.infer<typeof confirmVerificationSchema>;
340
+ export type DeleteAccountSchema = z.infer<typeof deleteAccountSchema>;
341
+ export type ChangePasswordSchema = z.infer<typeof changePasswordSchema>;
342
+ export type LoginSchema = z.infer<typeof loginSchema>;
343
+ export type ForgotPasswordSchema = z.infer<typeof forgotPasswordSchema>;
344
+ export type ResetPasswordSchema = z.infer<typeof resetPasswordSchema>;
@@ -1,11 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.changePasswordSchema = exports.deleteAccountSchema = exports.verifyPhoneChangeSchema = exports.requestPhoneChangeSchema = exports.updateProfileSchema = exports.strongPasswordRegex = exports.phoneRegex = void 0;
3
+ exports.resetPasswordSchema = exports.forgotPasswordSchema = exports.loginSchema = exports.changePasswordSchema = exports.deleteAccountSchema = exports.confirmVerificationSchema = exports.verifyContactSchema = exports.setPrimaryContactSchema = exports.deleteContactSchema = exports.addContactSchema = exports.verifyEmailChangeSchema = exports.verifyPhoneChangeSchema = exports.requestPhoneChangeSchema = exports.changeEmailSchema = exports.updateContactSchema = exports.updateProfileSchema = exports.validateContactValue = exports.detectContactType = exports.strongPasswordRegex = exports.emailRegex = exports.phoneRegex = void 0;
4
4
  // src/modules/account/validations/account.schema.ts
5
5
  const zod_1 = require("zod");
6
+ // ==================== REGEX PATTERNS ====================
6
7
  exports.phoneRegex = /^\+250(78|79|73|72)[0-9]{7}$/;
8
+ exports.emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
7
9
  const plateRegex = /^[A-Z0-9]{3,10}$/i;
8
10
  exports.strongPasswordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^()_\-+=])[A-Za-z\d@$!%*?&#^()_\-+=]{8,}$/;
11
+ // ==================== CONTACT VALIDATION HELPERS ====================
12
+ const detectContactType = (value) => {
13
+ if (!value)
14
+ return "UNKNOWN";
15
+ // Check if it's an email
16
+ if (exports.emailRegex.test(value)) {
17
+ return "EMAIL";
18
+ }
19
+ // Check if it's a phone number
20
+ if (exports.phoneRegex.test(value)) {
21
+ return "PHONE";
22
+ }
23
+ return "UNKNOWN";
24
+ };
25
+ exports.detectContactType = detectContactType;
26
+ const validateContactValue = (value) => {
27
+ return exports.emailRegex.test(value) || exports.phoneRegex.test(value);
28
+ };
29
+ exports.validateContactValue = validateContactValue;
30
+ // ==================== SCHEMAS ====================
9
31
  exports.updateProfileSchema = zod_1.z.object({
10
32
  body: zod_1.z.object({
11
33
  firstName: zod_1.z.string().min(2).max(50).optional(),
@@ -16,18 +38,91 @@ exports.updateProfileSchema = zod_1.z.object({
16
38
  .optional(),
17
39
  }),
18
40
  });
41
+ // Schema for updating contact (email or phone)
42
+ exports.updateContactSchema = zod_1.z.object({
43
+ body: zod_1.z.object({
44
+ contactValue: zod_1.z
45
+ .string()
46
+ .min(1, "Contact value is required")
47
+ .refine((val) => (0, exports.validateContactValue)(val), {
48
+ message: "Invalid contact value. Must be a valid email or Rwandan phone number (+250...)",
49
+ }),
50
+ }),
51
+ });
52
+ // Schema for changing email specifically
53
+ exports.changeEmailSchema = zod_1.z.object({
54
+ body: zod_1.z.object({
55
+ newEmail: zod_1.z
56
+ .string()
57
+ .email("Invalid email format")
58
+ .min(1, "Email is required"),
59
+ }),
60
+ });
61
+ // Schema for changing phone number specifically
19
62
  exports.requestPhoneChangeSchema = zod_1.z.object({
20
63
  body: zod_1.z.object({
21
- newPhoneNumber: zod_1.z.string().regex(exports.phoneRegex, "Invalid phone number format"),
64
+ newPhoneNumber: zod_1.z
65
+ .string()
66
+ .regex(exports.phoneRegex, "Invalid phone number format. Must be a valid Rwandan phone number (+250...)"),
22
67
  }),
23
68
  });
24
- // Updated validation schema
69
+ // Schema for verifying phone change with OTP
25
70
  exports.verifyPhoneChangeSchema = zod_1.z.object({
26
71
  body: zod_1.z.object({
27
72
  oldPhoneOtp: zod_1.z.string().length(6, "Old phone OTP must be 6 digits"),
28
73
  newPhoneOtp: zod_1.z.string().length(6, "New phone OTP must be 6 digits"),
29
74
  }),
30
75
  });
76
+ // Schema for verifying email change with OTP
77
+ exports.verifyEmailChangeSchema = zod_1.z.object({
78
+ body: zod_1.z.object({
79
+ emailOtp: zod_1.z.string().length(6, "Email OTP must be 6 digits"),
80
+ }),
81
+ });
82
+ // Schema for adding new contact
83
+ exports.addContactSchema = zod_1.z.object({
84
+ body: zod_1.z.object({
85
+ contactValue: zod_1.z
86
+ .string()
87
+ .min(1, "Contact value is required")
88
+ .refine((val) => (0, exports.validateContactValue)(val), {
89
+ message: "Invalid contact value. Must be a valid email or Rwandan phone number (+250...)",
90
+ }),
91
+ type: zod_1.z
92
+ .enum(["EMAIL", "PHONE", "WHATSAPP"])
93
+ .optional()
94
+ .refine((val) => {
95
+ if (!val)
96
+ return true;
97
+ return true;
98
+ }, { message: "Invalid contact type" }),
99
+ }),
100
+ });
101
+ // Schema for deleting a contact
102
+ exports.deleteContactSchema = zod_1.z.object({
103
+ params: zod_1.z.object({
104
+ contactId: zod_1.z.string().uuid("Invalid contact ID"),
105
+ }),
106
+ });
107
+ // Schema for making a contact primary
108
+ exports.setPrimaryContactSchema = zod_1.z.object({
109
+ params: zod_1.z.object({
110
+ contactId: zod_1.z.string().uuid("Invalid contact ID"),
111
+ }),
112
+ });
113
+ // Schema for verifying contact (sending OTP)
114
+ exports.verifyContactSchema = zod_1.z.object({
115
+ params: zod_1.z.object({
116
+ contactId: zod_1.z.string().uuid("Invalid contact ID"),
117
+ }),
118
+ });
119
+ // Schema for confirming contact verification with OTP
120
+ exports.confirmVerificationSchema = zod_1.z.object({
121
+ body: zod_1.z.object({
122
+ otp: zod_1.z.string().length(6, "OTP must be 6 digits"),
123
+ }),
124
+ });
125
+ // Schema for deleting account
31
126
  exports.deleteAccountSchema = zod_1.z.object({
32
127
  body: zod_1.z.object({
33
128
  confirmation: zod_1.z.literal("DELETE", {
@@ -36,6 +131,7 @@ exports.deleteAccountSchema = zod_1.z.object({
36
131
  reason: zod_1.z.string().max(500).optional(),
37
132
  }),
38
133
  });
134
+ // Schema for changing password
39
135
  exports.changePasswordSchema = zod_1.z.object({
40
136
  body: zod_1.z.object({
41
137
  oldPassword: zod_1.z.string().min(1, "Old password is required"),
@@ -44,3 +140,40 @@ exports.changePasswordSchema = zod_1.z.object({
44
140
  .regex(exports.strongPasswordRegex, "Password must be at least 8 characters, include 1 uppercase letter, 1 number and 1 symbol"),
45
141
  }),
46
142
  });
143
+ // Schema for login with contact
144
+ exports.loginSchema = zod_1.z.object({
145
+ body: zod_1.z.object({
146
+ contactValue: zod_1.z
147
+ .string()
148
+ .min(1, "Email or phone number is required")
149
+ .refine((val) => (0, exports.validateContactValue)(val), {
150
+ message: "Invalid contact value. Must be a valid email or Rwandan phone number (+250...)",
151
+ }),
152
+ password: zod_1.z.string().min(1, "Password is required"),
153
+ }),
154
+ });
155
+ // Schema for forgot password
156
+ exports.forgotPasswordSchema = zod_1.z.object({
157
+ body: zod_1.z.object({
158
+ contactValue: zod_1.z
159
+ .string()
160
+ .min(1, "Email or phone number is required")
161
+ .refine((val) => (0, exports.validateContactValue)(val), {
162
+ message: "Invalid contact value. Must be a valid email or Rwandan phone number (+250...)",
163
+ }),
164
+ }),
165
+ });
166
+ // Schema for reset password
167
+ exports.resetPasswordSchema = zod_1.z
168
+ .object({
169
+ body: zod_1.z.object({
170
+ newPassword: zod_1.z
171
+ .string()
172
+ .regex(exports.strongPasswordRegex, "Password must be at least 8 characters, include 1 uppercase letter, 1 number and 1 symbol"),
173
+ confirmPassword: zod_1.z.string(),
174
+ }),
175
+ })
176
+ .refine((data) => data.body.newPassword === data.body.confirmPassword, {
177
+ message: "Passwords do not match",
178
+ path: ["confirmPassword"],
179
+ });