| const twilio = require('twilio'); |
| const { generateAccessToken } = require('../config/jwt'); |
|
|
| const accountSid = process.env.TWILIO_ACCOUNT_SID; |
| const authToken = process.env.TWILIO_AUTH_TOKEN; |
| const fromPhone = process.env.TWILIO_PHONE_NUMBER; |
| const IS_DEV = process.env.NODE_ENV !== 'production'; |
|
|
| |
| const otpStore = new Map(); |
| const OTP_TTL_MS = 5 * 60 * 1000; |
| const MAX_ATTEMPTS = 5; |
|
|
| function generateOtp() { |
| return Math.floor(100000 + Math.random() * 900000).toString(); |
| } |
|
|
| |
| |
| exports.sendOtp = async (req, res) => { |
| const { phone } = req.body; |
|
|
| if (!phone || !/^\+?\d{7,15}$/.test(phone)) { |
| return res.status(400).json({ |
| success: false, |
| error: 'Invalid phone. Use E.164 format (+918925036049) or bare 10 digits.', |
| }); |
| } |
|
|
| |
| const normalised = phone.startsWith('+') ? phone : `+91${phone}`; |
|
|
| |
| const existing = otpStore.get(normalised); |
| if (existing && Date.now() < existing.expiresAt && existing.attempts >= MAX_ATTEMPTS) { |
| const waitSec = Math.ceil((existing.expiresAt - Date.now()) / 1000); |
| return res.status(429).json({ success: false, error: `Too many attempts. Try again in ${waitSec}s.` }); |
| } |
|
|
| const otp = generateOtp(); |
| const expiresAt = Date.now() + OTP_TTL_MS; |
| otpStore.set(normalised, { otp, expiresAt, attempts: 0 }); |
|
|
| |
| let smsSent = false; |
| try { |
| if (accountSid && authToken && fromPhone) { |
| const client = twilio(accountSid, authToken); |
| await client.messages.create({ |
| body: `Your FairRelay code: ${otp}. Valid 5 mins. Do not share.`, |
| from: fromPhone, |
| to: normalised, |
| }); |
| smsSent = true; |
| console.log(`[OTP] SMS sent to ${normalised.slice(0, 7)}****`); |
| } |
| } catch (err) { |
| console.warn('[OTP] Twilio failed (demo mode):', err.message); |
| } |
|
|
| const payload = { |
| success: true, |
| message: smsSent ? 'OTP sent to your phone.' : '[Demo] Twilio not configured β use the code below.', |
| expiresIn: OTP_TTL_MS / 1000, |
| phone: normalised, |
| }; |
|
|
| |
| if (IS_DEV && !smsSent) { |
| payload.demo = true; |
| payload.otp = otp; |
| } else if (!smsSent) { |
| |
| payload.message = 'OTP service unavailable. Please try again later.'; |
| } |
|
|
| res.json(payload); |
| }; |
|
|
| |
| |
| exports.verifyOtp = async (req, res) => { |
| const { phone, otp, role } = req.body; |
|
|
| if (!phone || !otp) { |
| return res.status(400).json({ success: false, error: 'phone and otp are required.' }); |
| } |
|
|
| const normalised = phone.startsWith('+') ? phone : `+91${phone}`; |
|
|
| |
| const isBypass = IS_DEV && otp.toString().trim() === '000000'; |
|
|
| if (!isBypass) { |
| const record = otpStore.get(normalised); |
| if (!record) { |
| return res.status(400).json({ success: false, error: 'No OTP found. Request a new one.' }); |
| } |
| if (Date.now() > record.expiresAt) { |
| otpStore.delete(normalised); |
| return res.status(400).json({ success: false, error: 'OTP expired. Request a new one.' }); |
| } |
| record.attempts += 1; |
| if (record.otp !== otp.toString().trim()) { |
| const remaining = MAX_ATTEMPTS - record.attempts; |
| if (remaining <= 0) { |
| otpStore.delete(normalised); |
| return res.status(429).json({ success: false, error: 'Too many attempts. Request a new OTP.' }); |
| } |
| return res.status(400).json({ success: false, error: `Invalid OTP. ${remaining} attempt(s) remaining.` }); |
| } |
| otpStore.delete(normalised); |
| } |
|
|
| console.log(`[OTP] Verified ${normalised.slice(0, 7)}**** ${isBypass ? '(dev bypass)' : ''}`); |
|
|
| const userRole = role || 'DRIVER'; |
| const token = generateAccessToken( |
| { phone: normalised, role: userRole, id: `dev-${normalised.slice(-4)}` } |
| ); |
|
|
| res.json({ |
| success: true, |
| message: 'Phone verified.', |
| data: { |
| token, |
| user: { |
| id: `dev-${normalised.slice(-4)}`, |
| phone: normalised, |
| role: userRole, |
| name: `Driver ${normalised.slice(-4)}`, |
| demo: isBypass, |
| }, |
| }, |
| }); |
| }; |
|
|
| |
| exports.resendOtp = async (req, res) => { |
| const { phone } = req.body; |
| if (phone) { |
| const normalised = phone.startsWith('+') ? phone : `+91${phone}`; |
| otpStore.delete(normalised); |
| } |
| return exports.sendOtp(req, res); |
| }; |
|
|