168 lines
6.9 KiB
JavaScript
168 lines
6.9 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const bcrypt = require('bcryptjs');
|
|
const jwt = require('jsonwebtoken');
|
|
const crypto = require('crypto');
|
|
const { Pool } = require('pg');
|
|
const { sendMail } = require('../utils/emailService');
|
|
|
|
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 JWT_SECRET = process.env.JWT_SECRET || 'supersecret123';
|
|
|
|
// Register
|
|
router.post('/register', async (req, res) => {
|
|
const { email, password } = req.body;
|
|
if (!email || !password) return res.status(400).json({ error: 'Email and password required' });
|
|
|
|
try {
|
|
const checkUser = await pgPool.query('SELECT id FROM users WHERE email = $1', [email]);
|
|
if (checkUser.rows.length > 0) return res.status(400).json({ error: 'User already exists' });
|
|
|
|
const countQuery = await pgPool.query('SELECT COUNT(*) FROM users');
|
|
const userCount = parseInt(countQuery.rows[0].count, 10);
|
|
const assignedRole = userCount === 0 ? 'enterprise' : 'free_trial';
|
|
|
|
const salt = await bcrypt.genSalt(10);
|
|
const hash = await bcrypt.hash(password, salt);
|
|
|
|
const newUser = await pgPool.query(
|
|
'INSERT INTO users (email, password_hash, role) VALUES ($1, $2, $3) RETURNING id, email, role',
|
|
[email, hash, assignedRole]
|
|
);
|
|
|
|
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] });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Login
|
|
router.post('/login', async (req, res) => {
|
|
const { email, password } = req.body;
|
|
if (!email || !password) return res.status(400).json({ error: 'Email and password required' });
|
|
|
|
try {
|
|
const userQuery = await pgPool.query('SELECT * FROM users WHERE email = $1', [email]);
|
|
if (userQuery.rows.length === 0) return res.status(404).json({ error: 'User not found' });
|
|
|
|
const user = userQuery.rows[0];
|
|
const isMatch = await bcrypt.compare(password, user.password_hash);
|
|
if (!isMatch) return res.status(401).json({ error: 'Invalid credentials' });
|
|
|
|
const token = jwt.sign({ id: user.id, email: user.email, role: user.role }, JWT_SECRET, { expiresIn: '14d' });
|
|
res.cookie('token', token, { httpOnly: true, maxAge: 14 * 24 * 60 * 60 * 1000, secure: false, sameSite: 'lax' });
|
|
|
|
res.json({ user: { id: user.id, email: user.email, role: user.role } });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Get Current User (Validate Session)
|
|
const { verifyToken } = require('../middleware/auth');
|
|
router.get('/me', verifyToken, async (req, res) => {
|
|
try {
|
|
const userQuery = await pgPool.query('SELECT id, email, role, is_email_verified, created_at FROM users WHERE id = $1', [req.user.id]);
|
|
if (userQuery.rows.length === 0) return res.status(404).json({ error: 'User not found' });
|
|
res.json({ user: userQuery.rows[0] });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Send Verification Email
|
|
router.post('/send-verification', verifyToken, async (req, res) => {
|
|
try {
|
|
const userQuery = await pgPool.query('SELECT email, is_email_verified FROM users WHERE id = $1', [req.user.id]);
|
|
if (userQuery.rows.length === 0) return res.status(404).json({ error: 'User not found' });
|
|
|
|
const user = userQuery.rows[0];
|
|
if (user.is_email_verified) {
|
|
return res.status(400).json({ error: 'Email is already verified' });
|
|
}
|
|
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
await pgPool.query('UPDATE users SET email_verification_token = $1 WHERE id = $2', [token, req.user.id]);
|
|
|
|
const baseUrl = process.env.FRONTEND_URL || 'https://qr.houseofwebsites.ai';
|
|
const verifyLink = `${baseUrl}/verify-email?token=${token}`;
|
|
|
|
const html = `
|
|
<h2>Verify Your Email</h2>
|
|
<p>Thanks for using CK-QR! Please verify your email address to unlock Pro tier upgrades.</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>
|
|
`;
|
|
|
|
await sendMail(user.email, 'Verify your CK-QR account', html);
|
|
res.json({ message: 'Verification email sent' });
|
|
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Failed to send verification email' });
|
|
}
|
|
});
|
|
|
|
// Verify Email Token
|
|
router.get('/verify-email/:token', async (req, res) => {
|
|
const { token } = req.params;
|
|
if (!token) return res.status(400).json({ error: 'Token is required' });
|
|
|
|
try {
|
|
const result = await pgPool.query(
|
|
'UPDATE users SET is_email_verified = true, email_verification_token = NULL WHERE email_verification_token = $1 RETURNING id, email',
|
|
[token]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
return res.status(400).json({ error: 'Invalid or expired token' });
|
|
}
|
|
|
|
res.json({ message: 'Email verified successfully!' });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error during verification' });
|
|
}
|
|
});
|
|
|
|
// Logout
|
|
router.post('/logout', (req, res) => {
|
|
res.clearCookie('token');
|
|
res.json({ message: 'Logged out' });
|
|
});
|
|
|
|
module.exports = router;
|