Skip to content

Commit 9c36747

Browse files
committed
fix: enhance OTP request validation and error handling for unregistered email and phone numbers
1 parent 12e385f commit 9c36747

File tree

1 file changed

+116
-13
lines changed
  • src/server/routes/api/auth

1 file changed

+116
-13
lines changed

src/server/routes/api/auth/otp.ts

Lines changed: 116 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ const otpApi: FastifyPluginCallback = (fastify, _, done) => {
134134
fingerprint?: string;
135135
};
136136
}>('/api/auth/request-otp', async function (req, reply) {
137-
const { phoneNumber, email, geolocation, resolution, source, fingerprint } = req.body;
137+
const { phoneNumber, email } = req.body;
138138

139139
// Validate that exactly one of phoneNumber or email is provided
140140
if ((phoneNumber == null && email == null) || (phoneNumber != null && email != null)) {
@@ -192,6 +192,93 @@ const otpApi: FastifyPluginCallback = (fastify, _, done) => {
192192
});
193193
}
194194

195+
// Validate that the user has the requested email/phone registered
196+
if (email != null) {
197+
const userEmails = user.emails ?? [];
198+
const hasEmail = userEmails.some(e => e.address?.toLowerCase() === email.toLowerCase());
199+
if (!hasEmail) {
200+
const userAgent = req.headers['user-agent'];
201+
const ua = new UAParser(userAgent ?? 'API Call').getResult();
202+
const ip = extractIp(req);
203+
204+
const accessLog = {
205+
_id: randomId(),
206+
_createdAt: new Date(),
207+
_updatedAt: new Date(),
208+
ip,
209+
login: identifier,
210+
browser: ua.browser.name,
211+
browserVersion: ua.browser.version,
212+
os: ua.os.name,
213+
platform: ua.device.type,
214+
reason: `Email [${email}] not registered in user account`,
215+
__from: 'request-otp',
216+
_user: [
217+
{
218+
_id: user._id?.toString() || `${user._id}`,
219+
name: user.name,
220+
group: user.group,
221+
},
222+
],
223+
};
224+
225+
await MetaObject.Collections.AccessFailedLog.insertOne(accessLog as DataDocument);
226+
227+
return reply.status(StatusCodes.BAD_REQUEST).send({
228+
success: false,
229+
errors: [{ message: 'Este email não está cadastrado no sistema' }],
230+
});
231+
}
232+
}
233+
234+
if (phoneNumber != null) {
235+
const userDoc = (await MetaObject.Collections.User.findOne({ _id: user._id }, { projection: { phone: 1 } })) as {
236+
phone?: Array<{ phoneNumber?: string; countryCode?: number }>;
237+
} | null;
238+
const userPhones = userDoc?.phone ?? [];
239+
const normalizedPhone = phoneNumber.replace(/[+\s]/g, '');
240+
const hasPhone = userPhones.some((p: { phoneNumber?: string; countryCode?: number }) => {
241+
if (p.phoneNumber == null) {
242+
return false;
243+
}
244+
const phoneStr = `${p.countryCode ?? ''}${p.phoneNumber}`.replace(/[+\s]/g, '');
245+
return phoneStr === normalizedPhone || p.phoneNumber.replace(/[+\s]/g, '') === normalizedPhone;
246+
});
247+
if (!hasPhone) {
248+
const userAgent = req.headers['user-agent'];
249+
const ua = new UAParser(userAgent ?? 'API Call').getResult();
250+
const ip = extractIp(req);
251+
252+
const accessLog = {
253+
_id: randomId(),
254+
_createdAt: new Date(),
255+
_updatedAt: new Date(),
256+
ip,
257+
login: identifier,
258+
browser: ua.browser.name,
259+
browserVersion: ua.browser.version,
260+
os: ua.os.name,
261+
platform: ua.device.type,
262+
reason: `Phone number [${phoneNumber}] not registered in user account`,
263+
__from: 'request-otp',
264+
_user: [
265+
{
266+
_id: user._id?.toString() || `${user._id}`,
267+
name: user.name,
268+
group: user.group,
269+
},
270+
],
271+
};
272+
273+
await MetaObject.Collections.AccessFailedLog.insertOne(accessLog as DataDocument);
274+
275+
return reply.status(StatusCodes.BAD_REQUEST).send({
276+
success: false,
277+
errors: [{ message: 'Este telefone não está cadastrado no sistema' }],
278+
});
279+
}
280+
}
281+
195282
// Create OTP request (rate limiting is handled inside createOtpRequest via database transaction)
196283
const getOtpResult = async (): Promise<{ otpRequest: OtpRequest; otpCode: string }> => {
197284
try {
@@ -216,9 +303,28 @@ const otpApi: FastifyPluginCallback = (fastify, _, done) => {
216303

217304
if (!deliveryResult.success) {
218305
logger.error(`Failed to send OTP: ${deliveryResult.error}`);
306+
307+
// Map specific errors to user-friendly messages
308+
const errorMessage = deliveryResult.error ?? 'Erro desconhecido ao enviar OTP';
309+
let userMessage = 'Falha no envio do código de verificação. Tente novamente mais tarde.';
310+
311+
if (errorMessage.includes('User does not have an email address') || errorMessage.includes('does not have an email')) {
312+
userMessage = 'O email informado não está cadastrado no sistema';
313+
} else if (errorMessage.includes('User not found')) {
314+
userMessage = 'Email ou telefone não encontrado no sistema';
315+
} else if (errorMessage.includes('WhatsApp configuration not available') || errorMessage.includes('WhatsApp')) {
316+
userMessage = 'Serviço de WhatsApp temporariamente indisponível. Tente novamente mais tarde.';
317+
} else if (errorMessage.includes('RabbitMQ') || errorMessage.includes('queue')) {
318+
userMessage = 'Serviço de mensagens temporariamente indisponível. Tente novamente mais tarde.';
319+
} else if (errorMessage.includes('Failed to render email template')) {
320+
userMessage = 'Erro ao processar template de email. Entre em contato com o suporte.';
321+
} else if (errorMessage.includes('Failed to create message')) {
322+
userMessage = 'Erro ao criar mensagem. Tente novamente mais tarde.';
323+
}
324+
219325
return reply.status(StatusCodes.INTERNAL_SERVER_ERROR).send({
220326
success: false,
221-
errors: [{ message: 'Failed to send OTP. Please try again later.' }],
327+
errors: [{ message: userMessage }],
222328
});
223329
}
224330

@@ -522,17 +628,14 @@ const otpApi: FastifyPluginCallback = (fastify, _, done) => {
522628
const hashStampedToken = generateStampedLoginToken();
523629

524630
// Update user
525-
await MetaObject.Collections.User.updateOne(
526-
{ _id: user._id },
527-
{
528-
$set: {
529-
lastLogin: new Date(),
530-
},
531-
$push: {
532-
'services.resume.loginTokens': hashStampedToken,
533-
},
534-
} as any,
535-
);
631+
await MetaObject.Collections.User.updateOne({ _id: user._id }, {
632+
$set: {
633+
lastLogin: new Date(),
634+
},
635+
$push: {
636+
'services.resume.loginTokens': hashStampedToken,
637+
},
638+
} as any);
536639

537640
// Create AccessLog with all available data (same format as traditional login)
538641
const userAgent = req.headers['user-agent'];

0 commit comments

Comments
 (0)