Skip to content

Commit 8248690

Browse files
committed
add: validations for admin endpoints
1 parent 532441a commit 8248690

File tree

2 files changed

+86
-12
lines changed

2 files changed

+86
-12
lines changed

src/services/admin.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ module.exports = class AdminHelper {
114114

115115
const plaintextEmailId = bodyData.email.toLowerCase()
116116
const encryptedEmailId = emailEncryption.encrypt(plaintextEmailId)
117+
const encryptedPhoneNumber = emailEncryption.encrypt(bodyData.phone)
117118

118119
// Get default tenant details
119120
const tenantDetail = await tenantQueries.findOne({
@@ -122,14 +123,28 @@ module.exports = class AdminHelper {
122123
})
123124
if (!tenantDetail) throw new Error('DEFAULT_TENANT_NOT_FOUND')
124125

125-
const existingUser = await userQueries.findOne(
126+
const criteria = []
127+
if (encryptedEmailId) criteria.push({ email: encryptedEmailId })
128+
if (encryptedPhoneNumber) criteria.push({ phone: encryptedPhoneNumber })
129+
if (bodyData.username) criteria.push({ username: bodyData.username })
130+
131+
if (criteria.length === 0) {
132+
return // Skip if no criteria
133+
}
134+
135+
// Check if user already exists with email or phone or username
136+
let existingUser = await userQueries.findOne(
126137
{
127-
email: encryptedEmailId,
138+
[Op.or]: criteria,
128139
password: { [Op.ne]: null },
129140
tenant_code: tenantDetail.code,
130141
},
131-
{ attributes: ['id'], transaction }
142+
{
143+
attributes: ['id'],
144+
transaction,
145+
}
132146
)
147+
133148
if (existingUser) throw new Error('ADMIN_USER_ALREADY_EXISTS')
134149

135150
const role = await roleQueries.findOne(
@@ -145,6 +160,7 @@ module.exports = class AdminHelper {
145160

146161
// Prepare user data
147162
bodyData.email = encryptedEmailId
163+
bodyData.phone = encryptedPhoneNumber
148164
bodyData.password = utils.hashPassword(bodyData.password)
149165
bodyData.tenant_code = tenantDetail.code
150166
bodyData.roles = [role.id]

src/validators/v1/admin.js

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,91 @@ module.exports = {
2323
.withMessage('name is invalid')
2424

2525
req.checkBody('email')
26+
.optional()
2627
.trim()
27-
.notEmpty()
28-
.withMessage('email field is empty')
2928
.isEmail()
29+
.matches(
30+
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
31+
)
3032
.withMessage('email is invalid')
31-
.normalizeEmail()
33+
.normalizeEmail({ gmail_remove_dots: false })
3234

35+
req.checkBody('username')
36+
.optional()
37+
.trim()
38+
.matches(/^(?:[a-z0-9_-]{3,40}|[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})$/) //accept random string (min 3 max 40) of smaller case letters _ - and numbers OR email in lowercase as username
39+
.withMessage('username is invalid')
40+
41+
// Validate phone (optional)
42+
req.checkBody('phone')
43+
.optional()
44+
.trim()
45+
.matches(/^[0-9]{7,15}$/)
46+
.withMessage('phone must be a valid number between 7 and 15 digits')
47+
48+
// Validate phone_code (required if phone is provided)
49+
req.checkBody('phone_code')
50+
.optional({ checkFalsy: true })
51+
.trim()
52+
.isLength({ min: 2, max: 4 }) // Length between 2 and 4 characters
53+
.withMessage('Phone code must be between 2 and 4 characters')
54+
55+
// Validate password
3356
req.checkBody('password')
3457
.notEmpty()
3558
.withMessage('Password field is empty')
3659
.matches(process.env.PASSWORD_POLICY_REGEX)
3760
.withMessage(process.env.PASSWORD_POLICY_MESSAGE)
3861
.custom((value) => !/\s/.test(value))
3962
.withMessage('Password cannot contain spaces')
63+
64+
req.checkBody(['email', 'phone', 'phone_code']).custom(() => {
65+
const phone = req.body.phone
66+
const phone_code = req.body.phone_code
67+
const email = req.body.email
68+
69+
if (!email && !phone) {
70+
throw new Error('At least one of email or phone must be provided')
71+
}
72+
73+
if (phone && !phone_code) {
74+
throw new Error('phone_code is required when phone is provided')
75+
}
76+
77+
return true
78+
})
4079
},
4180

4281
login: (req) => {
4382
req.body = filterRequestBody(req.body, admin.login)
44-
req.checkBody('email')
83+
req.checkBody('identifier')
4584
.trim()
4685
.notEmpty()
47-
.withMessage('email field is empty')
48-
.isEmail()
49-
.withMessage('email is invalid')
50-
.normalizeEmail({ gmail_remove_dots: false })
86+
.withMessage('Identifier field is empty')
87+
.custom((value) => {
88+
// Check if the identifier is a valid email, phone, or username
89+
const isEmail = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(value)
90+
const isPhone = /^\d{6,15}$/.test(value) // Phone: 6-15 digits
91+
const isUsername = /^[a-zA-Z0-9-_]{3,30}$/.test(value)
92+
93+
if (!isEmail && !isPhone && !isUsername) {
94+
throw new Error('Identifier must be a valid email, phone number, or username')
95+
}
96+
return true
97+
})
98+
.if(body('identifier').custom((value) => /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(value)))
99+
.normalizeEmail({ gmail_remove_dots: false }) // Normalize email only if identifier is an email
100+
101+
// Validate phone_code (required only if identifier is a phone number)
102+
req.checkBody('phone_code')
103+
.if(body('identifier').custom((value) => /^\d{6,15}$/.test(value))) // Apply only for phone identifiers
104+
.notEmpty()
105+
.withMessage('Phone code is required for phone number login')
106+
.matches(/^\+[1-9]\d{0,3}$/)
107+
.withMessage('Phone code must be a valid country code (e.g., +1, +91)')
51108

52-
req.checkBody('password').trim().notEmpty().withMessage('password field is empty')
109+
// Validate password
110+
req.checkBody('password').trim().notEmpty().withMessage('Password field is empty')
53111
},
54112

55113
addOrgAdmin: (req) => {

0 commit comments

Comments
 (0)