feat: User management, SMTP email activation and password resets
Build and Deploy / build-and-push (push) Successful in 1m0s

This commit is contained in:
AI Bot
2026-09-17 08:49:11 +05:30
parent 603aee646c
commit cbf44587aa
9 changed files with 571 additions and 51 deletions
+143 -4
View File
@@ -11,8 +11,10 @@ const pdfParse = require('pdf-parse');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const axios = require('axios');
const crypto = require('crypto');
const { db, run, get, all } = require('./database');
const { sendMail } = require('./mail');
const { sendMail, sendActivationEmail, sendPasswordResetEmail } = require('./mail');
const app = express();
app.use(cors());
@@ -70,13 +72,26 @@ app.post('/api/auth/register', async (req, res) => {
const role = parseInt(countRow.count) === 0 ? 'admin' : 'user';
const is_active = parseInt(countRow.count) === 0 ? true : false;
let activation_token = null;
if (!is_active) {
activation_token = crypto.randomBytes(32).toString('hex');
}
await run(
'INSERT INTO profiles (id, email, password_hash, role, api_credits, storage_limit_mb, is_active) VALUES (?, ?, ?, ?, ?, ?, ?)',
[id, email, hash, role, 10, 500, is_active]
'INSERT INTO profiles (id, email, password_hash, role, api_credits, storage_limit_mb, is_active, activation_token) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, email, hash, role, 10, 500, is_active, activation_token]
);
if (is_active === false) {
return res.json({ status: 'pending', message: 'Account created. Please contact an administrator to activate your account.' });
// Send activation email
const baseUrl = req.headers.origin || 'http://localhost:5173';
try {
await sendActivationEmail(email, activation_token, baseUrl);
} catch (err) {
console.error('Failed to send activation email', err);
// We still return pending, maybe they can resend later
}
return res.json({ status: 'pending', message: 'Account created. Please check your email for the activation link.' });
}
const token = jwt.sign({ id, email, role }, JWT_SECRET, { expiresIn: '7d' });
@@ -87,6 +102,21 @@ app.post('/api/auth/register', async (req, res) => {
}
});
// 1.5. Auth Activate
app.post('/api/auth/activate', async (req, res) => {
const { token } = req.body;
if (!token) return res.status(400).json({ error: 'Activation token required' });
try {
const user = await get('SELECT * FROM profiles WHERE activation_token = ?', [token]);
if (!user) return res.status(400).json({ error: 'Invalid or expired activation token' });
await run('UPDATE profiles SET is_active = true, activation_token = NULL WHERE id = ?', [user.id]);
res.json({ success: true, message: 'Account activated successfully.' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 2. Auth Login
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
@@ -221,6 +251,53 @@ app.get('/api/users', authenticate, async (req, res) => {
}
});
// 7.1. Add user (Admin only)
app.post('/api/users', authenticate, async (req, res) => {
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
const { email, role, api_credits, storage_limit_mb } = req.body;
if (!email) return res.status(400).json({ error: 'Email required' });
try {
const existing = await get('SELECT * FROM profiles WHERE email = ?', [email]);
if (existing) return res.status(400).json({ error: 'User already exists' });
// Generate random temp password (they will reset it anyway)
const tempPassword = crypto.randomBytes(16).toString('hex');
const hash = await bcrypt.hash(tempPassword, 10);
const id = uuidv4();
const activation_token = crypto.randomBytes(32).toString('hex');
await run(
'INSERT INTO profiles (id, email, password_hash, role, api_credits, storage_limit_mb, is_active, activation_token) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, email, hash, role || 'user', api_credits || 10, storage_limit_mb || 500, false, activation_token]
);
const baseUrl = req.headers.origin || 'http://localhost:5173';
try {
await sendActivationEmail(email, activation_token, baseUrl);
} catch (err) {
console.error('Failed to send activation email', err);
}
res.json({ success: true, message: 'User created and activation email sent.' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 7.2. Delete user (Admin only)
app.delete('/api/users/:id', authenticate, async (req, res) => {
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
try {
// Cascade delete projects for the user
await run('DELETE FROM projects WHERE user_id = ?', [req.params.id]);
await run('DELETE FROM profiles WHERE id = ?', [req.params.id]);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 8. Update user (Admin only)
app.put('/api/users/:id', authenticate, async (req, res) => {
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
@@ -233,6 +310,68 @@ app.put('/api/users/:id', authenticate, async (req, res) => {
}
});
// 8.1. Admin trigger reset password for user
app.post('/api/users/:id/reset-password', authenticate, async (req, res) => {
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
try {
const user = await get('SELECT * FROM profiles WHERE id = ?', [req.params.id]);
if (!user) return res.status(404).json({ error: 'User not found' });
const reset_token = crypto.randomBytes(32).toString('hex');
await run('UPDATE profiles SET reset_token = ? WHERE id = ?', [reset_token, user.id]);
const baseUrl = req.headers.origin || 'http://localhost:5173';
try {
await sendPasswordResetEmail(user.email, reset_token, baseUrl);
} catch (err) {
console.error('Failed to send reset email', err);
}
res.json({ success: true, message: 'Password reset email sent to user.' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 8.2. Forgot Password (User request)
app.post('/api/auth/forgot-password', async (req, res) => {
const { email } = req.body;
if (!email) return res.status(400).json({ error: 'Email required' });
try {
const user = await get('SELECT * FROM profiles WHERE email = ?', [email]);
if (user) {
const reset_token = crypto.randomBytes(32).toString('hex');
await run('UPDATE profiles SET reset_token = ? WHERE id = ?', [reset_token, user.id]);
const baseUrl = req.headers.origin || 'http://localhost:5173';
try {
await sendPasswordResetEmail(user.email, reset_token, baseUrl);
} catch (err) {
console.error('Failed to send reset email', err);
}
}
// Always return success to prevent email enumeration
res.json({ success: true, message: 'If an account exists, a reset link has been sent.' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 8.3. Reset Password (User submission)
app.post('/api/auth/reset-password', async (req, res) => {
const { token, newPassword } = req.body;
if (!token || !newPassword) return res.status(400).json({ error: 'Token and new password required' });
try {
const user = await get('SELECT * FROM profiles WHERE reset_token = ?', [token]);
if (!user) return res.status(400).json({ error: 'Invalid or expired reset token' });
const hash = await bcrypt.hash(newPassword, 10);
await run('UPDATE profiles SET password_hash = ?, reset_token = NULL WHERE id = ?', [hash, user.id]);
res.json({ success: true, message: 'Password has been reset successfully.' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 9. Deduct API credit
app.post('/api/users/deduct-credit', authenticate, async (req, res) => {
try {