feat(auth): Enforce global email verification for all users, restrict dashboard if unverified, add verification badges and banners
This commit is contained in:
+23
-1
@@ -1,4 +1,13 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { Pool } = require('pg');
|
||||
|
||||
const pgPool = new Pool({
|
||||
host: process.env.PGHOST || 'postgres',
|
||||
port: process.env.PGPORT || 5432,
|
||||
user: process.env.PGUSER || 'postgres',
|
||||
password: process.env.PGPASSWORD || 'postgres',
|
||||
database: process.env.PGDATABASE || 'ckqr',
|
||||
});
|
||||
|
||||
const verifyToken = (req, res, next) => {
|
||||
let token = req.headers['authorization'];
|
||||
@@ -19,4 +28,17 @@ const verifyToken = (req, res, next) => {
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { verifyToken };
|
||||
const requireEmailVerification = async (req, res, next) => {
|
||||
try {
|
||||
const userCheck = await pgPool.query('SELECT is_email_verified FROM users WHERE id = $1', [req.user.id]);
|
||||
if (userCheck.rows.length === 0 || !userCheck.rows[0].is_email_verified) {
|
||||
return res.status(403).json({ error: 'Email verification required' });
|
||||
}
|
||||
next();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error checking verification' });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { verifyToken, requireEmailVerification };
|
||||
|
||||
+21
-1
@@ -37,7 +37,27 @@ router.post('/register', async (req, res) => {
|
||||
[email, hash, assignedRole]
|
||||
);
|
||||
|
||||
const token = jwt.sign({ id: newUser.rows[0].id, email: newUser.rows[0].email, role: newUser.rows[0].role }, JWT_SECRET, { expiresIn: '14d' });
|
||||
const userId = newUser.rows[0].id;
|
||||
const userEmail = newUser.rows[0].email;
|
||||
|
||||
// Auto send verification email
|
||||
const tokenStr = crypto.randomBytes(32).toString('hex');
|
||||
await pgPool.query('UPDATE users SET email_verification_token = $1 WHERE id = $2', [tokenStr, userId]);
|
||||
|
||||
const baseUrl = process.env.FRONTEND_URL || 'https://qr.houseofwebsites.ai';
|
||||
const verifyLink = `${baseUrl}/verify-email?token=${tokenStr}`;
|
||||
|
||||
const html = `
|
||||
<h2>Verify Your Email</h2>
|
||||
<p>Thanks for registering for CK-QR! Please verify your email address to unlock your dashboard features.</p>
|
||||
<a href="${verifyLink}" style="display:inline-block;padding:10px 20px;background:#4f46e5;color:#fff;text-decoration:none;border-radius:5px;">Verify Email</a>
|
||||
<p>Or copy and paste this link: <br> ${verifyLink}</p>
|
||||
`;
|
||||
|
||||
// Send email asynchronously without blocking registration response
|
||||
sendMail(userEmail, 'Verify your CK-QR account', html).catch(e => console.error('Failed to send auto-verify email:', e));
|
||||
|
||||
const token = jwt.sign({ id: userId, email: userEmail, role: newUser.rows[0].role }, JWT_SECRET, { expiresIn: '14d' });
|
||||
res.cookie('token', token, { httpOnly: true, maxAge: 14 * 24 * 60 * 60 * 1000, secure: false, sameSite: 'lax' });
|
||||
|
||||
res.status(201).json({ message: 'User registered', user: newUser.rows[0] });
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const QRCode = require('qrcode');
|
||||
const shortid = require('shortid');
|
||||
const { verifyToken } = require('../middleware/auth');
|
||||
const { verifyToken, requireEmailVerification } = require('../middleware/auth');
|
||||
const { Pool } = require('pg');
|
||||
|
||||
const pgPool = new Pool({
|
||||
@@ -56,7 +56,7 @@ router.get('/:id', verifyToken, async (req, res) => {
|
||||
});
|
||||
|
||||
// Generate QR Code
|
||||
router.post('/generate', verifyToken, async (req, res) => {
|
||||
router.post('/generate', verifyToken, requireEmailVerification, async (req, res) => {
|
||||
const { name, type, dataType, destinationUrl, designData, customSlug, folderId } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
@@ -107,7 +107,7 @@ router.post('/generate', verifyToken, async (req, res) => {
|
||||
});
|
||||
|
||||
// Update QR Code (Editable Link / Move Folder / Update Design)
|
||||
router.put('/:id', verifyToken, async (req, res) => {
|
||||
router.put('/:id', verifyToken, requireEmailVerification, async (req, res) => {
|
||||
const { name, destinationUrl, shortUrlId, designData, folderId } = req.body;
|
||||
const qrId = req.params.id;
|
||||
const userId = req.user.id;
|
||||
@@ -150,7 +150,7 @@ router.put('/:id', verifyToken, async (req, res) => {
|
||||
});
|
||||
|
||||
// Delete QR Code
|
||||
router.delete('/:id', verifyToken, async (req, res) => {
|
||||
router.delete('/:id', verifyToken, requireEmailVerification, async (req, res) => {
|
||||
try {
|
||||
const result = await pgPool.query('DELETE FROM qr_codes WHERE id = $1 AND user_id = $2 RETURNING id', [req.params.id, req.user.id]);
|
||||
if (result.rows.length === 0) return res.status(404).json({ error: 'Not found' });
|
||||
|
||||
Reference in New Issue
Block a user