Download ops/backend-dm/controllers/authController.js from MouleeswaranM/FairRelay: direct link, hf CLI and curl.
- Browser
- Download file 8.2 kB
-
https://huggingface.co/MouleeswaranM/FairRelay/resolve/refs%2Fpr%2F21/ops/backend-dm/controllers/authController.js
- Command line
-
hf download hf://MouleeswaranM/FairRelay@refs/pr/21/ops/backend-dm/controllers/authController.js
-
curl -L -o authController.js https://huggingface.co/MouleeswaranM/FairRelay/resolve/refs%2Fpr%2F21/ops/backend-dm/controllers/authController.js
8.2 kB
| const prisma = require('../config/database'); | |
| const { generateAccessToken, refreshAccessToken } = require('../config/jwt'); | |
| const Joi = require('joi'); | |
| const twilio = require('twilio'); | |
| const crypto = require('crypto'); | |
| const dotenv = require('dotenv'); | |
| dotenv.config(); | |
| // In-memory OTP store: phone β { otp, expiresAt, attempts } | |
| const otpStore = new Map(); | |
| const OTP_TTL = 5 * 60 * 1000; // 5 minutes | |
| const MAX_ATT = 3; | |
| function makeTwilioClient() { | |
| try { | |
| return twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); | |
| } catch { return null; } | |
| } | |
| /** | |
| * POST /api/auth/login β Send OTP via Twilio SMS | |
| */ | |
| const sendOTP = async (req, res) => { | |
| try { | |
| const schema = Joi.object({ | |
| phone: Joi.string().pattern(/^\+?\d{7,15}$/).required().messages({ | |
| 'string.pattern.base': 'Phone must be a valid number (e.g. +91XXXXXXXXXX)', | |
| }), | |
| role: Joi.string().valid('DRIVER', 'SHIPPER', 'DISPATCHER').default('DRIVER'), | |
| }); | |
| const { error } = schema.validate(req.body); | |
| if (error) return res.status(400).json({ success: false, message: error.details[0].message }); | |
| const { phone } = req.body; | |
| // Rate limit: block if previous OTP not expired & too many attempts | |
| const existing = otpStore.get(phone); | |
| if (existing && Date.now() < existing.expiresAt && existing.attempts >= MAX_ATT) { | |
| const wait = Math.ceil((existing.expiresAt - Date.now()) / 1000); | |
| return res.status(429).json({ success: false, message: `Too many attempts. Retry in ${wait}s.` }); | |
| } | |
| const otp = Math.floor(100000 + Math.random() * 900000).toString(); | |
| otpStore.set(phone, { otp, expiresAt: Date.now() + OTP_TTL, attempts: 0 }); | |
| // Try Twilio β fall back gracefully in demo mode | |
| const client = makeTwilioClient(); | |
| let smsSent = false; | |
| if (client && process.env.TWILIO_PHONE_NUMBER) { | |
| try { | |
| await client.messages.create({ | |
| body: `Your FairRelay login code is: ${otp}. Valid for 5 minutes. Do not share.`, | |
| from: process.env.TWILIO_PHONE_NUMBER, | |
| to: phone, | |
| }); | |
| smsSent = true; | |
| console.log(`[OTP] SMS sent to ${phone.slice(0, 5)}***`); | |
| } catch (twilioErr) { | |
| console.warn('[OTP] Twilio send failed (demo mode):', twilioErr.message); | |
| } | |
| } | |
| const IS_PROD = process.env.NODE_ENV === 'production'; | |
| res.status(200).json({ | |
| success: true, | |
| message: smsSent ? 'OTP sent to your phone.' : (IS_PROD ? 'OTP service unavailable. Please try again later.' : `Demo mode β OTP is: ${otp}`), | |
| data: { phone, demo: !smsSent, otp: (!IS_PROD && !smsSent) ? otp : undefined }, | |
| }); | |
| } catch (err) { | |
| console.error('sendOTP error:', err.message); | |
| res.status(500).json({ success: false, message: err.message || 'Failed to send OTP' }); | |
| } | |
| }; | |
| /** | |
| * POST /api/auth/verify-otp β Verify OTP and issue JWT | |
| */ | |
| const verifyOTP = async (req, res) => { | |
| try { | |
| const schema = Joi.object({ | |
| phone: Joi.string().pattern(/^\+?\d{7,15}$/).required(), | |
| otp: Joi.string().length(6).required(), | |
| role: Joi.string().valid('DRIVER', 'SHIPPER', 'DISPATCHER').optional(), | |
| }); | |
| const { error } = schema.validate(req.body); | |
| if (error) return res.status(400).json({ success: false, message: error.details[0].message }); | |
| const { phone, otp, role } = req.body; | |
| // Validate OTP from in-memory store | |
| const record = otpStore.get(phone); | |
| if (!record) { | |
| return res.status(400).json({ success: false, message: 'No OTP found. Request a new one.' }); | |
| } | |
| if (Date.now() > record.expiresAt) { | |
| otpStore.delete(phone); | |
| return res.status(400).json({ success: false, message: 'OTP expired. Request a new one.' }); | |
| } | |
| record.attempts += 1; | |
| if (record.otp !== otp.trim()) { | |
| if (record.attempts >= MAX_ATT) { | |
| otpStore.delete(phone); | |
| return res.status(429).json({ success: false, message: 'Too many failed attempts. Request a new OTP.' }); | |
| } | |
| return res.status(400).json({ success: false, message: `Invalid OTP. ${MAX_ATT - record.attempts} attempt(s) left.` }); | |
| } | |
| otpStore.delete(phone); // β OTP matched β clean up | |
| // Find or create user β with full DB offline fallback | |
| let user; | |
| let isDemo = false; | |
| try { | |
| user = await prisma.user.findUnique({ | |
| where: { phone }, | |
| include: { trucks: { select: { id: true, licensePlate: true, model: true } } }, | |
| }); | |
| if (!user) { | |
| user = await prisma.user.create({ | |
| data: { phone, role: role || 'DISPATCHER', name: `User_${phone.slice(-4)}` }, | |
| include: { trucks: true }, | |
| }); | |
| } | |
| await prisma.user.update({ where: { id: user.id }, data: { lastActiveDate: new Date() } }); | |
| } catch (dbErr) { | |
| console.warn('[Auth] DB offline β using demo user:', dbErr.message); | |
| isDemo = true; | |
| user = { | |
| id: `demo-${crypto.randomUUID()}`, | |
| name: `Dispatcher_${phone.slice(-4)}`, | |
| phone, | |
| role: role || 'DISPATCHER', | |
| status: 'ACTIVE', | |
| rating: 5.0, | |
| deliveriesCount: 0, | |
| totalEarnings: 0, | |
| weeklyEarnings: 0, | |
| trucks: [], | |
| }; | |
| } | |
| const token = generateAccessToken({ userId: user.id, role: user.role }); | |
| res.status(200).json({ | |
| success: true, | |
| message: isDemo ? 'Login successful (demo mode)' : 'Login successful', | |
| data: { | |
| token, | |
| user: { | |
| id: user.id, | |
| name: user.name, | |
| phone: user.phone, | |
| role: user.role, | |
| status: user.status, | |
| rating: user.rating, | |
| deliveriesCount: user.deliveriesCount, | |
| totalEarnings: user.totalEarnings, | |
| weeklyEarnings: user.weeklyEarnings, | |
| trucks: user.trucks || [], | |
| }, | |
| }, | |
| }); | |
| } catch (err) { | |
| console.error('verifyOTP error:', err.message); | |
| res.status(500).json({ success: false, message: err.message || 'OTP verification failed' }); | |
| } | |
| }; | |
| /** | |
| * GET /api/auth/profile β Get authenticated user profile (PROTECTED) | |
| */ | |
| const getProfile = async (req, res) => { | |
| try { | |
| const userId = req.user.id; | |
| const user = await prisma.user.findUnique({ | |
| where: { id: userId }, | |
| include: { | |
| trucks: { select: { id: true, licensePlate: true, model: true, capacity: true, currentLat: true, currentLng: true } }, | |
| transactions: { take: 5, orderBy: { createdAt: 'desc' }, select: { id: true, amount: true, type: true, description: true, route: true, createdAt: true } }, | |
| }, | |
| }); | |
| if (!user) return res.status(404).json({ success: false, message: 'User not found' }); | |
| res.status(200).json({ | |
| success: true, | |
| data: { | |
| id: user.id, name: user.name, phone: user.phone, role: user.role, | |
| status: user.status, rating: user.rating, deliveriesCount: user.deliveriesCount, | |
| totalEarnings: user.totalEarnings, weeklyEarnings: user.weeklyEarnings, | |
| weeklyKmDriven: user.weeklyKmDriven, trucks: user.trucks || [], | |
| recentTransactions: user.transactions || [], lastActiveDate: user.lastActiveDate, | |
| }, | |
| }); | |
| } catch (err) { | |
| console.error('getProfile error:', err.message); | |
| res.status(500).json({ success: false, message: 'Failed to fetch profile' }); | |
| } | |
| }; | |
| /** | |
| * POST /api/auth/refresh-token | |
| */ | |
| const refreshToken = async (req, res) => { | |
| try { | |
| const { error } = Joi.object({ refreshToken: Joi.string().required().label('Refresh token') }).validate(req.body); | |
| if (error) return res.status(400).json({ success: false, message: error.details[0].message }); | |
| const newAccessToken = refreshAccessToken(req.body.refreshToken); | |
| res.status(200).json({ success: true, message: 'Token refreshed successfully', data: { accessToken: newAccessToken } }); | |
| } catch (err) { | |
| console.error('refreshToken error:', err.message); | |
| if (err.message === 'Invalid refresh token') { | |
| return res.status(403).json({ success: false, message: 'Invalid or expired refresh token' }); | |
| } | |
| res.status(500).json({ success: false, message: 'Token refresh failed' }); | |
| } | |
| }; | |
| module.exports = { sendOTP, verifyOTP, getProfile, refreshToken }; | |