space / server.js
Skydata001's picture
Update server.js
e6b7176 verified
Raw
History Blame Contribute Delete
13.2 kB
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