diff --git a/api/middleware/auth.js b/api/middleware/auth.js
index 32626d5..c36f0d0 100644
--- a/api/middleware/auth.js
+++ b/api/middleware/auth.js
@@ -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 };
diff --git a/api/routes/auth.js b/api/routes/auth.js
index cf5988f..7918a8f 100644
--- a/api/routes/auth.js
+++ b/api/routes/auth.js
@@ -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 = `
+
Verify Your Email
+ Thanks for registering for CK-QR! Please verify your email address to unlock your dashboard features.
+ Verify Email
+ Or copy and paste this link:
${verifyLink}
+ `;
+
+ // 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] });
diff --git a/api/routes/qr.js b/api/routes/qr.js
index d16cdd9..705b74e 100644
--- a/api/routes/qr.js
+++ b/api/routes/qr.js
@@ -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' });
diff --git a/client/src/app/dashboard/layout.tsx b/client/src/app/dashboard/layout.tsx
index 84d9717..15ac549 100644
--- a/client/src/app/dashboard/layout.tsx
+++ b/client/src/app/dashboard/layout.tsx
@@ -6,6 +6,17 @@ import { usePathname } from 'next/navigation';
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+ const [user, setUser] = useState(null);
+
+ React.useEffect(() => {
+ import('@/lib/api').then(({ default: api }) => {
+ api.get('/auth/me').then(res => {
+ setUser(res.data.user);
+ }).catch(err => {
+ console.error('Failed to fetch user', err);
+ });
+ });
+ }, []);
const menuItems = [
{ name: 'My QR Codes', path: '/dashboard', icon: 'M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z' },
@@ -38,10 +49,17 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
-
-
- Create New QR Code
-
+ {user?.is_email_verified === false ? (
+
+ ) : (
+
+
+ Create New QR Code
+
+ )}