diff --git a/index.js b/index.js index 9807afa..6aed6fa 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,22 @@ 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, @@ -14,8 +30,8 @@ async function initDb() { }); await db.exec(` CREATE TABLE IF NOT EXISTS users ( - username TEXT PRIMARY KEY, - passwordHash TEXT + username TEXT PRIMARY KEY, + passwordHash TEXT ) `); return db; @@ -32,15 +48,12 @@ async function initDb() { 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); @@ -55,70 +68,104 @@ async function initDb() { 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 + return res.json({ + status: 'success', + message: 'Password set successfully. Logged in.', + token }); } else { - return res.status(403).json({ + return res.status(403).json({ status: 'require_password', - message: 'First login requires setting a 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 + return res.json({ + status: 'success', + message: 'Logged in successfully', + token }); } }); app.get('/verify', (req, res) => { - const authHeader = req.headers.authorization; - if (!authHeader || !authHeader.startsWith('Bearer ')) { - return res.status(401).json({ valid: false, error: 'No token provided' }); + 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 }); + }); - const token = authHeader.split(' ')[1]; - try { - const decoded = jwt.verify(token, JWT_SECRET); - return res.json({ valid: true, user: decoded.username }); - } catch (err) { - return res.status(401).json({ valid: false, error: 'Invalid or expired token' }); + 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}`);