const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const sqlite3 = require('sqlite3'); const { open } = require('sqlite'); const JWT_SECRET = 'super-secret-key-change-me'; const DB_FILE = './users.db'; const onlineUsers = new Map(); const ONLINE_TIMEOUT = 30000; function verifyToken(req) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return null; } const token = authHeader.split(' ')[1]; try { return jwt.verify(token, JWT_SECRET); } catch (err) { return null; } } async function initDb() { const db = await open({ filename: DB_FILE, driver: sqlite3.Database }); await db.exec(` CREATE TABLE IF NOT EXISTS users ( username TEXT PRIMARY KEY, passwordHash TEXT ) `); return db; } (async () => { const db = await initDb(); if (process.argv.length > 2) { const command = process.argv[2]; if (command === 'create') { const username = process.argv[3]; if (!username) { console.error('Please provide a username: npm run airclientauth -- create '); process.exit(1); } const user = await db.get('SELECT * FROM users WHERE username = ?', [username]); if (user) { console.error('User already exists.'); process.exit(1); } await db.run('INSERT INTO users (username, passwordHash) VALUES (?, ?)', [username, null]); console.log(`User '${username}' created successfully.`); console.log(`Waiting for first login to set the password.`); process.exit(0); } else { console.error('Unknown command. Available commands: create '); process.exit(1); } } const app = express(); app.use(express.json()); app.post('/login', async (req, res) => { const { username, password } = req.body; if (!username) { return res.status(400).json({ error: 'Username is required' }); } const user = await db.get('SELECT * FROM users WHERE username = ?', [username]); if (!user) { return res.status(404).json({ error: 'User not found' }); } if (!user.passwordHash) { if (password) { const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(password, salt); await db.run('UPDATE users SET passwordHash = ? WHERE username = ?', [hash, username]); const token = jwt.sign({ username }, JWT_SECRET, { expiresIn: '1h' }); return res.json({ status: 'success', message: 'Password set successfully. Logged in.', token }); } else { return res.status(403).json({ status: 'require_password', message: 'First login requires setting a password.' }); } } else { if (!password) { return res.status(400).json({ error: 'Password is required' }); } const isMatch = await bcrypt.compare(password, user.passwordHash); if (!isMatch) { return res.status(401).json({ error: 'Invalid password' }); } const token = jwt.sign({ username }, JWT_SECRET, { expiresIn: '1h' }); return res.json({ status: 'success', message: 'Logged in successfully', token }); } }); app.get('/verify', (req, res) => { const decoded = verifyToken(req); if (!decoded) { return res.status(401).json({ valid: false, error: 'Invalid or missing token' }); } return res.json({ valid: true, user: decoded.username }); }); app.post('/online', (req, res) => { const decoded = verifyToken(req); if (!decoded) { return res.status(401).json({ error: 'Invalid or missing token' }); } const { username } = decoded; onlineUsers.set(username, Date.now()); return res.json({ status: 'online', count: onlineUsers.size }); }); app.post('/ping', (req, res) => { const decoded = verifyToken(req); if (!decoded) { return res.status(401).json({ error: 'Invalid or missing token' }); } const { username } = decoded; if (onlineUsers.has(username)) { onlineUsers.set(username, Date.now()); return res.json({ status: 'alive' }); } else { onlineUsers.set(username, Date.now()); return res.json({ status: 'reconnected' }); } }); app.post('/offline', (req, res) => { const decoded = verifyToken(req); if (!decoded) { return res.status(401).json({ error: 'Invalid or missing token' }); } const { username } = decoded; onlineUsers.delete(username); return res.json({ status: 'offline', count: onlineUsers.size }); }); app.get('/playercount', (req, res) => { return res.json({ count: onlineUsers.size }); }); setInterval(() => { const now = Date.now(); for (const [username, lastPing] of onlineUsers.entries()) { if (now - lastPing > ONLINE_TIMEOUT) { onlineUsers.delete(username); console.log(`User '${username}' timed out (offline)`); } } }, 10000); const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); })();