init
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
const crypto = require('crypto');
|
||||
const express = require('express');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const prisma = require('../db');
|
||||
const { asyncHandler, httpError } = require('../lib/errors');
|
||||
const { isValidPhone, normalizePhone } = require('../lib/phone');
|
||||
const { publicUser, userPublicSelect } = require('../lib/users');
|
||||
const { assignedDealerIds } = require('../lib/access');
|
||||
const { sendSmsOtp } = require('../services/smsService');
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
const OTP_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
function generateOtp() {
|
||||
return String(crypto.randomInt(0, 1000000)).padStart(6, '0');
|
||||
}
|
||||
|
||||
async function accessibleDealers(user) {
|
||||
if (user.role === 'superAdmin') {
|
||||
return prisma.dealer.findMany({ orderBy: { name: 'asc' } });
|
||||
}
|
||||
if (user.role === 'dealer') {
|
||||
if (!user.dealerId) return [];
|
||||
const dealer = await prisma.dealer.findUnique({ where: { id: user.dealerId } });
|
||||
return dealer ? [dealer] : [];
|
||||
}
|
||||
const ids = assignedDealerIds(user);
|
||||
if (ids.length === 0) return [];
|
||||
return prisma.dealer.findMany({
|
||||
where: { id: { in: ids } },
|
||||
orderBy: { name: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
router.post(
|
||||
'/send-otp',
|
||||
asyncHandler(async (req, res) => {
|
||||
const phone = normalizePhone(req.body?.phone);
|
||||
if (!isValidPhone(phone)) {
|
||||
throw httpError(400, 'Phone must be in 947XXXXXXXX format');
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { phone } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ success: false, message: 'Not Registered' });
|
||||
}
|
||||
|
||||
const otp = generateOtp();
|
||||
const otpExpiresAt = new Date(Date.now() + OTP_TTL_MS);
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { currentOtp: otp, otpExpiresAt },
|
||||
});
|
||||
|
||||
try {
|
||||
await sendSmsOtp(phone, otp);
|
||||
} catch (error) {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { currentOtp: null, otpExpiresAt: null },
|
||||
});
|
||||
throw httpError(502, error.message || 'Failed to send OTP SMS');
|
||||
}
|
||||
|
||||
return res.json({ success: true, message: 'OTP sent via SMS' });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/verify-otp',
|
||||
asyncHandler(async (req, res) => {
|
||||
const phone = normalizePhone(req.body?.phone);
|
||||
const otp = String(req.body?.otp || '').trim();
|
||||
if (!isValidPhone(phone) || !/^\d{6}$/.test(otp)) {
|
||||
throw httpError(400, 'Phone and a 6-digit OTP are required');
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { phone },
|
||||
select: {
|
||||
...userPublicSelect,
|
||||
currentOtp: true,
|
||||
otpExpiresAt: true,
|
||||
},
|
||||
});
|
||||
if (!user || !user.currentOtp || !user.otpExpiresAt) {
|
||||
throw httpError(401, 'Invalid OTP');
|
||||
}
|
||||
if (user.otpExpiresAt.getTime() < Date.now()) {
|
||||
throw httpError(401, 'OTP expired');
|
||||
}
|
||||
|
||||
const stored = Buffer.from(user.currentOtp);
|
||||
const given = Buffer.from(otp);
|
||||
const matches = stored.length === given.length && crypto.timingSafeEqual(stored, given);
|
||||
if (!matches) {
|
||||
throw httpError(401, 'Invalid OTP');
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { currentOtp: null, otpExpiresAt: null },
|
||||
});
|
||||
|
||||
const token = jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, {
|
||||
expiresIn: '10y',
|
||||
});
|
||||
const dealers = await accessibleDealers(user);
|
||||
return res.json({ token, user: publicUser(user, dealers) });
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/me',
|
||||
requireAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const dealers = await accessibleDealers(req.user);
|
||||
return res.json(publicUser(req.user, dealers));
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user