Spaces:
Runtime error
Runtime error
| const express = require('express'); | |
| const cors = require('cors'); | |
| const helmet = require('helmet'); | |
| const cookieParser = require('cookie-parser'); | |
| const axios = require('axios'); | |
| const { OAuth2Client } = require('google-auth-library'); | |
| const { v4: uuidv4 } = require('uuid'); | |
| require('dotenv').config(); | |
| // ========================================== | |
| // نظام حماية الخادم من الانهيار (يمنع دوامة Restarting) | |
| // ========================================== | |
| process.on('uncaughtException', (err) => { | |
| console.error('CRITICAL ERROR (Uncaught Exception):', err); | |
| }); | |
| process.on('unhandledRejection', (reason, promise) => { | |
| console.error('CRITICAL ERROR (Unhandled Rejection):', reason); | |
| }); | |
| const { db, initDB } = require('./db'); | |
| const { sendWelcomeEmail } = require('./email'); | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; | |
| // ========================================== | |
| // 1. نظام السجلات المتقدم (In-Memory Logs) | |
| // ========================================== | |
| let serverLogs = []; | |
| const MAX_LOGS = 5000; | |
| function addLog(level, category, message, details = '') { | |
| const logEntry = { | |
| id: uuidv4(), | |
| timestamp: new Date().toISOString(), | |
| type: level.toLowerCase(), // info, warn, error, security, event, request | |
| category: category, | |
| message: message, | |
| metadata: typeof details === 'object' ? details : { info: details } | |
| }; | |
| serverLogs.unshift(logEntry); | |
| if (serverLogs.length > MAX_LOGS) serverLogs.pop(); | |
| console.log(`[${logEntry.timestamp}] [${logEntry.type.toUpperCase()}] [${logEntry.category}] - ${logEntry.message}`); | |
| } | |
| function logSecurityEvent(type, ip, userAgent, status, details = '') { | |
| const level = status === 'FAILED' ? 'error' : (status === 'SUCCESS' ? 'info' : 'warn'); | |
| addLog(level, 'auth', `[${type}] [${status}]`, { ip, userAgent, details }); | |
| } | |
| // ========================================== | |
| // 2. إعدادات الحماية والـ Middlewares | |
| // ========================================== | |
| app.use(helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false })); | |
| app.use(cors({ | |
| origin: function(origin, callback) { callback(null, true); }, | |
| credentials: true | |
| })); | |
| app.use(express.json()); | |
| app.use(cookieParser()); | |
| // Request Logger Middleware | |
| app.use((req, res, next) => { | |
| const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; | |
| const start = Date.now(); | |
| res.on('finish', () => { | |
| const duration = Date.now() - start; | |
| let type = res.statusCode >= 500 ? 'error' : (res.statusCode >= 400 ? 'warn' : 'info'); | |
| addLog(type, 'request', `${req.method} ${req.path} → ${res.statusCode} (${duration}ms)`, { ip, method: req.method, path: req.path }); | |
| }); | |
| next(); | |
| }); | |
| const googleClient = new OAuth2Client( | |
| process.env.GOOGLE_CLIENT_ID, | |
| process.env.GOOGLE_CLIENT_SECRET | |
| ); | |
| // ========================================== | |
| // 3. مسارات نظام السجلات (Logs API) | |
| // ========================================== | |
| function adminAuth(req, res, next) { | |
| const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; | |
| const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':'); | |
| // يجب إضافة ADMIN_USER و ADMIN_PASS في متغيرات البيئة | |
| const adminUser = process.env.ADMIN_USER || 'admin'; | |
| const adminPass = process.env.ADMIN_PASS || 'admin'; | |
| if (login && password && login === adminUser && password === adminPass) { | |
| return next(); | |
| } | |
| addLog('warn', 'security', 'محاولة وصول غير مصرح بها لصفحة السجلات', { ip: req.ip }); | |
| res.set('WWW-Authenticate', 'Basic realm="401"'); | |
| res.status(401).send('Authentication required.'); | |
| } | |
| app.get('/api/logs', adminAuth, (req, res) => { | |
| res.json({ logs: serverLogs, count: serverLogs.length }); | |
| }); | |
| app.post('/api/logs/clear', adminAuth, (req, res) => { | |
| serverLogs = []; | |
| addLog('info', 'system', 'تم مسح جميع السجلات بواسطة المسؤول', { ip: req.ip }); | |
| res.json({ success: true }); | |
| }); | |
| app.post('/api/logs', (req, res) => { | |
| const { type, category, message, metadata } = req.body; | |
| const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; | |
| addLog(type || 'event', category || 'frontend', message || 'Client Event', { ...metadata, ip }); | |
| res.json({ success: true }); | |
| }); | |
| // ========================================== | |
| // 4. مسارات المصادقة (Authentication) | |
| // ========================================== | |
| // أ. توليد رابط جوجل (لزر جوجل المخصص - Redirect Flow) | |
| app.post('/api/auth/google-url', async (req, res) => { | |
| const { captchaToken, marketing, redirectUri } = req.body; | |
| const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; | |
| const userAgent = req.headers['user-agent']; | |
| if (!captchaToken || !redirectUri) { | |
| logSecurityEvent('hCaptcha', ip, userAgent, 'FAILED', 'Missing parameters'); | |
| return res.status(400).json({ message: "Missing parameters" }); | |
| } | |
| try { | |
| const verifyRes = await axios.post('https://hcaptcha.com/siteverify', new URLSearchParams({ | |
| secret: process.env.HCAPTCHA_SECRET_KEY, | |
| response: captchaToken | |
| })); | |
| if (!verifyRes.data.success) { | |
| logSecurityEvent('hCaptcha', ip, userAgent, 'FAILED', 'Invalid token'); | |
| return res.status(403).json({ message: "Captcha verification failed" }); | |
| } | |
| logSecurityEvent('hCaptcha', ip, userAgent, 'SUCCESS'); | |
| const stateObj = { marketing: !!marketing, csrf: uuidv4() }; | |
| const stateStr = Buffer.from(JSON.stringify(stateObj)).toString('base64'); | |
| const url = googleClient.generateAuthUrl({ | |
| access_type: 'offline', | |
| scope: ['email', 'profile'], | |
| state: stateStr, | |
| redirect_uri: redirectUri // الرابط الذي سيظهر في جوجل (skydata.bond/home.html) | |
| }); | |
| res.json({ url }); | |
| } catch (err) { | |
| addLog('error', 'auth', 'Google URL Generation Failed', { error: err.message }); | |
| res.status(500).json({ message: "Server error" }); | |
| } | |
| }); | |
| // ب. استقبال الكود من home.html وإنشاء الجلسة | |
| app.post('/api/auth/google/callback-ajax', async (req, res) => { | |
| const { code, state, redirectUri } = req.body; | |
| const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; | |
| const userAgent = req.headers['user-agent']; | |
| if (!code || !redirectUri) { | |
| return res.status(400).json({ message: "Missing code or redirectUri" }); | |
| } | |
| try { | |
| const { tokens } = await googleClient.getToken({ code, redirect_uri: redirectUri }); | |
| const ticket = await googleClient.verifyIdToken({ | |
| idToken: tokens.id_token, | |
| audience: process.env.GOOGLE_CLIENT_ID | |
| }); | |
| const payload = ticket.getPayload(); | |
| const email = payload.email; | |
| const name = payload.name || email.split('@')[0]; | |
| const picture = payload.picture; | |
| let marketing = false; | |
| if (state) { | |
| try { | |
| const stateObj = JSON.parse(Buffer.from(state, 'base64').toString('utf-8')); | |
| marketing = stateObj.marketing; | |
| } catch(e) {} | |
| } | |
| let userRes = await db.execute({ sql: "SELECT * FROM users WHERE email = ?", args: [email] }); | |
| let userId; | |
| if (userRes.rows.length === 0) { | |
| const username = email.split('@')[0] + Math.floor(Math.random() * 1000); | |
| const insertRes = await db.execute({ | |
| sql: `INSERT INTO users (username, display_name, email, profile, marketing_mail, last_login_ip) VALUES (?, ?, ?, ?, ?, ?) RETURNING user_id`, | |
| args: [username, name, email, picture, marketing ? 1 : 0, ip] | |
| }); | |
| userId = insertRes.rows[0].user_id; | |
| if (marketing) sendWelcomeEmail(email, name); | |
| } else { | |
| userId = userRes.rows[0].user_id; | |
| await db.execute({ sql: "UPDATE users SET login_count = login_count + 1, last_login_ip = ? WHERE user_id = ?", args: [ip, userId] }); | |
| } | |
| logSecurityEvent('Login', ip, userAgent, 'SUCCESS', `User: ${email}`); | |
| const sessionId = uuidv4(); | |
| const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); | |
| await db.execute({ | |
| sql: `INSERT INTO sessions (session_id, user_id, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)`, | |
| args: [sessionId, userId, expiresAt, ip, userAgent] | |
| }); | |
| res.cookie('skydata_session', sessionId, { httpOnly: true, secure: true, sameSite: 'None', maxAge: 7 * 24 * 60 * 60 * 1000 }); | |
| const finalUserRes = await db.execute({ sql: "SELECT username, display_name, email, profile, lv, power, xp, coins, gems, hp FROM users WHERE user_id = ?", args: [userId] }); | |
| res.json({ success: true, user: finalUserRes.rows[0] }); | |
| } catch (err) { | |
| logSecurityEvent('Login', ip, userAgent, 'FAILED', err.message); | |
| res.status(500).json({ message: "Authentication failed: " + err.message }); | |
| } | |
| }); | |
| // ========================================== | |
| // 5. إدارة الجلسات (Session Management) | |
| // ========================================== | |
| async function requireAuth(req, res, next) { | |
| const sessionId = req.cookies.skydata_session; | |
| if (!sessionId) return res.status(401).json({ message: "Unauthorized" }); | |
| try { | |
| const sessionRes = await db.execute({ sql: "SELECT * FROM sessions WHERE session_id = ? AND is_active = 1", args: [sessionId] }); | |
| if (sessionRes.rows.length === 0) { | |
| res.clearCookie('skydata_session'); | |
| return res.status(401).json({ message: "Invalid session" }); | |
| } | |
| const session = sessionRes.rows[0]; | |
| const now = new Date(); | |
| const expiresAt = new Date(session.expires_at); | |
| if (now > expiresAt) { | |
| await db.execute({ sql: "UPDATE sessions SET is_active = 0 WHERE session_id = ?", args: [sessionId] }); | |
| res.clearCookie('skydata_session'); | |
| return res.status(401).json({ message: "Session expired" }); | |
| } | |
| const createdAt = new Date(session.created_at); | |
| const diffMins = (now - createdAt) / (1000 * 60); | |
| if (diffMins > 15) { | |
| const newSessionId = uuidv4(); | |
| await db.execute({ sql: "UPDATE sessions SET is_active = 0 WHERE session_id = ?", args: [sessionId] }); | |
| await db.execute({ | |
| sql: `INSERT INTO sessions (session_id, user_id, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)`, | |
| args: [newSessionId, session.user_id, session.expires_at, session.ip, session.user_agent] | |
| }); | |
| res.cookie('skydata_session', newSessionId, { httpOnly: true, secure: true, sameSite: 'None', expires: expiresAt }); | |
| } else { | |
| await db.execute({ sql: "UPDATE sessions SET last_activity = CURRENT_TIMESTAMP WHERE session_id = ?", args: [sessionId] }); | |
| } | |
| req.userId = session.user_id; | |
| next(); | |
| } catch (err) { | |
| addLog('error', 'system', 'Session Verification Error', { error: err.message }); | |
| res.status(500).json({ message: "Server error" }); | |
| } | |
| } | |
| app.get('/api/auth/me', requireAuth, async (req, res) => { | |
| try { | |
| const userRes = await db.execute({ sql: "SELECT username, display_name, email, profile, lv, power, xp, coins, gems, hp FROM users WHERE user_id = ?", args: [req.userId] }); | |
| res.json(userRes.rows[0]); | |
| } catch (err) { | |
| res.status(500).json({ message: "Server error" }); | |
| } | |
| }); | |
| app.post('/api/auth/settings', requireAuth, async (req, res) => { | |
| const { sessionDays } = req.body; | |
| const days = parseInt(sessionDays) || 7; | |
| try { | |
| const expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString(); | |
| await db.execute({ sql: "UPDATE sessions SET expires_at = ? WHERE session_id = ?", args: [expiresAt, req.cookies.skydata_session] }); | |
| res.cookie('skydata_session', req.cookies.skydata_session, { httpOnly: true, secure: true, sameSite: 'None', maxAge: days * 24 * 60 * 60 * 1000 }); | |
| res.json({ success: true }); | |
| } catch (err) { | |
| res.status(500).json({ message: "Server error" }); | |
| } | |
| }); | |
| app.post('/api/auth/logout', requireAuth, async (req, res) => { | |
| try { | |
| await db.execute({ sql: "UPDATE sessions SET is_active = 0 WHERE session_id = ?", args: [req.cookies.skydata_session] }); | |
| res.clearCookie('skydata_session', { httpOnly: true, secure: true, sameSite: 'None' }); | |
| res.json({ success: true }); | |
| } catch (err) { | |
| res.status(500).json({ message: "Server error" }); | |
| } | |
| }); | |
| // ========================================== | |
| // 6. بدء تشغيل الخادم | |
| // ========================================== | |
| initDB().then(() => { | |
| app.listen(PORT, '0.0.0.0', () => { | |
| console.log(`✅ Server running on port ${PORT}`); | |
| addLog('info', 'system', `Server started o |