File manager - Edit - /home/webapp69.cm.in.th/u69319090038/Shop/controllers/authController.js
Back
const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const pool = require('../config/db'); require('dotenv').config(); const SALT_ROUNDS = 10; const OTP_TTL_MINS = 5; // ─── Helpers ──────────────────────────────────────────────────────────────── function generateOTP() { return String(Math.floor(100000 + Math.random() * 900000)); } function issueAccessToken(payload) { return jwt.sign(payload, process.env.JWT_ACCESS_SECRET, { expiresIn: process.env.JWT_ACCESS_EXPIRY || '15m', }); } function issueRefreshToken(payload) { return jwt.sign(payload, process.env.JWT_REFRESH_SECRET, { expiresIn: process.env.JWT_REFRESH_EXPIRY || '7d', }); } function setRefreshCookie(res, token, rememberMe = false) { const maxAge = rememberMe ? 7 * 24 * 60 * 60 * 1000 // 7 days : 2 * 60 * 60 * 1000; // 2 hours (session-like) res.cookie('refreshToken', token, { httpOnly: true, secure: process.env.COOKIE_SECURE === 'true', sameSite: process.env.COOKIE_SAME_SITE || 'lax', maxAge, path: '/api/auth/refresh', }); } // ─── REGISTER ─────────────────────────────────────────────────────────────── exports.register = async (req, res) => { const { username, email, phone, password } = req.body; if (!username || !email || !password) { return res.status(400).json({ success: false, message: 'Username, email, and password are required.' }); } // Password strength (server-side mirror) const strongRegex = /^(?=.*[0-9])(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,}$/; if (!strongRegex.test(password)) { return res.status(400).json({ success: false, message: 'Password must be at least 8 characters and include a number and a symbol.', }); } try { // Duplicate check const [existing] = await pool.query( 'SELECT id FROM users WHERE email = ? OR username = ? OR (phone IS NOT NULL AND phone = ?)', [email, username, phone || null] ); if (existing.length > 0) { return res.status(409).json({ success: false, message: 'Email, username, or phone already in use.' }); } const password_hash = await bcrypt.hash(password, SALT_ROUNDS); const [result] = await pool.query( `INSERT INTO users (username, email, phone, password_hash, role) VALUES (?, ?, ?, ?, 'buyer')`, [username, email, phone || null, password_hash] ); // Auto-generate and "send" OTP for email verification const code = generateOTP(); const expiresAt = new Date(Date.now() + OTP_TTL_MINS * 60000); await pool.query( 'INSERT INTO otp_codes (identifier, code, purpose, expires_at) VALUES (?, ?, ?, ?)', [email, code, 'register', expiresAt] ); // In production, send via email/SMS. For now, log to console. console.log(`\n📧 OTP for [${email}]: ${code} (expires in ${OTP_TTL_MINS} mins)\n`); return res.status(201).json({ success: true, message: 'Account created. Please verify your email with the OTP sent.', userId: result.insertId, requiresOtp: true, identifier: email, }); } catch (err) { console.error('register error:', err); return res.status(500).json({ success: false, message: 'Server error during registration.' }); } }; // ─── SEND OTP ──────────────────────────────────────────────────────────────── exports.sendOtp = async (req, res) => { const { identifier, purpose = 'login' } = req.body; if (!identifier) { return res.status(400).json({ success: false, message: 'Email or phone is required.' }); } try { // Check user exists for non-register purposes if (purpose !== 'register') { const [users] = await pool.query( 'SELECT id FROM users WHERE email = ? OR phone = ?', [identifier, identifier] ); if (users.length === 0) { return res.status(404).json({ success: false, message: 'No account found with this email or phone.' }); } } // Invalidate old OTPs await pool.query( 'UPDATE otp_codes SET is_used = 1 WHERE identifier = ? AND purpose = ? AND is_used = 0', [identifier, purpose] ); const code = generateOTP(); const expiresAt = new Date(Date.now() + OTP_TTL_MINS * 60000); await pool.query( 'INSERT INTO otp_codes (identifier, code, purpose, expires_at) VALUES (?, ?, ?, ?)', [identifier, code, purpose, expiresAt] ); // Simulate delivery console.log(`\n📱 OTP [${purpose}] for [${identifier}]: ${code} (expires in ${OTP_TTL_MINS} mins)\n`); return res.json({ success: true, message: `OTP sent to ${identifier}. Valid for ${OTP_TTL_MINS} minutes.` }); } catch (err) { console.error('sendOtp error:', err); return res.status(500).json({ success: false, message: 'Server error sending OTP.' }); } }; // ─── VERIFY OTP ────────────────────────────────────────────────────────────── exports.verifyOtp = async (req, res) => { const { identifier, code, purpose = 'login' } = req.body; if (!identifier || !code) { return res.status(400).json({ success: false, message: 'Identifier and OTP code are required.' }); } try { const [rows] = await pool.query( `SELECT * FROM otp_codes WHERE identifier = ? AND code = ? AND purpose = ? AND is_used = 0 AND expires_at > NOW() ORDER BY id DESC LIMIT 1`, [identifier, code, purpose] ); if (rows.length === 0) { // Track attempts await pool.query('UPDATE otp_codes SET attempts = attempts + 1 WHERE identifier = ? AND is_used = 0', [identifier]); return res.status(400).json({ success: false, message: 'Invalid or expired OTP.' }); } // Mark used await pool.query('UPDATE otp_codes SET is_used = 1 WHERE id = ?', [rows[0].id]); if (purpose === 'register' || purpose === 'verify') { // Mark email/phone verified const field = identifier.includes('@') ? 'is_email_verified' : 'is_phone_verified'; await pool.query(`UPDATE users SET ${field} = 1 WHERE email = ? OR phone = ?`, [identifier, identifier]); } if (purpose === 'login') { // Passwordless login: find user, issue tokens const [users] = await pool.query( 'SELECT id, username, email, phone, role, avatar_url FROM users WHERE (email = ? OR phone = ?) AND is_active = 1', [identifier, identifier] ); if (users.length === 0) { return res.status(404).json({ success: false, message: 'User not found.' }); } const user = users[0]; const accessToken = issueAccessToken({ id: user.id, username: user.username, role: user.role }); const refreshToken = issueRefreshToken({ id: user.id }); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); await pool.query( 'INSERT INTO user_sessions (user_id, refresh_token, ip_address, expires_at) VALUES (?, ?, ?, ?)', [user.id, refreshToken, req.ip, expiresAt] ); setRefreshCookie(res, refreshToken, true); return res.json({ success: true, message: 'OTP login successful.', accessToken, user: { id: user.id, username: user.username, email: user.email, role: user.role, avatar_url: user.avatar_url }, }); } return res.json({ success: true, message: 'OTP verified successfully.' }); } catch (err) { console.error('verifyOtp error:', err); return res.status(500).json({ success: false, message: 'Server error verifying OTP.' }); } }; // ─── LOGIN ─────────────────────────────────────────────────────────────────── exports.login = async (req, res) => { const { identifier, password, rememberMe = false } = req.body; if (!identifier || !password) { return res.status(400).json({ success: false, message: 'Identifier and password are required.' }); } try { const [users] = await pool.query( `SELECT id, username, email, phone, password_hash, role, avatar_url, is_active FROM users WHERE email = ? OR username = ? OR phone = ? LIMIT 1`, [identifier, identifier, identifier] ); if (users.length === 0) { await pool.query('INSERT INTO login_attempts (identifier, ip_address, success) VALUES (?, ?, 0)', [identifier, req.ip]); return res.status(401).json({ success: false, message: 'Invalid credentials.' }); } const user = users[0]; if (!user.is_active) { return res.status(403).json({ success: false, message: 'Account is disabled. Contact support.' }); } const match = await bcrypt.compare(password, user.password_hash); if (!match) { await pool.query('INSERT INTO login_attempts (identifier, ip_address, success) VALUES (?, ?, 0)', [identifier, req.ip]); return res.status(401).json({ success: false, message: 'Invalid credentials.' }); } // Successful login await pool.query('INSERT INTO login_attempts (identifier, ip_address, success) VALUES (?, ?, 1)', [identifier, req.ip]); const accessToken = issueAccessToken({ id: user.id, username: user.username, role: user.role }); const refreshToken = issueRefreshToken({ id: user.id }); const ttlMs = rememberMe ? 7 * 24 * 60 * 60 * 1000 : 2 * 60 * 60 * 1000; const expiresAt = new Date(Date.now() + ttlMs); await pool.query( 'INSERT INTO user_sessions (user_id, refresh_token, user_agent, ip_address, expires_at, remember_me) VALUES (?, ?, ?, ?, ?, ?)', [user.id, refreshToken, req.headers['user-agent'] || '', req.ip, expiresAt, rememberMe ? 1 : 0] ); setRefreshCookie(res, refreshToken, rememberMe); return res.json({ success: true, message: 'Login successful.', accessToken, user: { id: user.id, username: user.username, email: user.email, phone: user.phone, role: user.role, avatar_url: user.avatar_url }, }); } catch (err) { console.error('login error:', err); return res.status(500).json({ success: false, message: 'Server error during login.' }); } }; // ─── REFRESH TOKEN ──────────────────────────────────────────────────────────── exports.refresh = async (req, res) => { const token = req.cookies?.refreshToken; if (!token) { return res.status(401).json({ success: false, message: 'No refresh token provided.' }); } try { const decoded = jwt.verify(token, process.env.JWT_REFRESH_SECRET); const [sessions] = await pool.query( 'SELECT * FROM user_sessions WHERE refresh_token = ? AND expires_at > NOW() LIMIT 1', [token] ); if (sessions.length === 0) { res.clearCookie('refreshToken', { path: '/api/auth/refresh' }); return res.status(401).json({ success: false, message: 'Session expired. Please login again.' }); } const [users] = await pool.query( 'SELECT id, username, email, role, avatar_url FROM users WHERE id = ? AND is_active = 1', [decoded.id] ); if (users.length === 0) { return res.status(401).json({ success: false, message: 'User not found.' }); } const user = users[0]; const accessToken = issueAccessToken({ id: user.id, username: user.username, role: user.role }); return res.json({ success: true, accessToken, user: { id: user.id, username: user.username, email: user.email, role: user.role, avatar_url: user.avatar_url }, }); } catch (err) { res.clearCookie('refreshToken', { path: '/api/auth/refresh' }); return res.status(401).json({ success: false, message: 'Invalid or expired refresh token.' }); } }; // ─── LOGOUT ────────────────────────────────────────────────────────────────── exports.logout = async (req, res) => { const token = req.cookies?.refreshToken; if (token) { try { await pool.query('DELETE FROM user_sessions WHERE refresh_token = ?', [token]); } catch (_) { /* ignore */ } res.clearCookie('refreshToken', { path: '/api/auth/refresh' }); } return res.json({ success: true, message: 'Logged out successfully.' }); }; // ─── GET PROFILE ────────────────────────────────────────────────────────────── exports.getProfile = async (req, res) => { try { const [rows] = await pool.query( `SELECT u.id, u.username, u.email, u.phone, u.role, u.avatar_url, u.full_name, u.is_email_verified, u.is_phone_verified, u.created_at, sp.shop_name, sp.shop_desc, sp.shop_logo, sp.is_approved FROM users u LEFT JOIN seller_profiles sp ON sp.user_id = u.id WHERE u.id = ?`, [req.user.id] ); if (rows.length === 0) return res.status(404).json({ success: false, message: 'User not found.' }); return res.json({ success: true, user: rows[0] }); } catch (err) { console.error('getProfile error:', err); return res.status(500).json({ success: false, message: 'Server error.' }); } }; // ─── APPLY TO SELLER ───────────────────────────────────────────────────────── exports.applyToSeller = async (req, res) => { const { shop_name, shop_desc } = req.body; if (!shop_name) { return res.status(400).json({ success: false, message: 'Shop name is required.' }); } try { const [existing] = await pool.query('SELECT id FROM seller_profiles WHERE user_id = ?', [req.user.id]); if (existing.length > 0) { return res.status(409).json({ success: false, message: 'Seller application already submitted.' }); } await pool.query( 'INSERT INTO seller_profiles (user_id, shop_name, shop_desc) VALUES (?, ?, ?)', [req.user.id, shop_name, shop_desc || null] ); // Auto-approve in dev: elevate role await pool.query("UPDATE users SET role = 'seller' WHERE id = ?", [req.user.id]); return res.json({ success: true, message: 'Seller application approved! Welcome to the Seller Center.', role: 'seller' }); } catch (err) { console.error('applyToSeller error:', err); return res.status(500).json({ success: false, message: 'Server error.' }); } };
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.23 |
proxy
|
phpinfo
|
Settings