AstroCom-API/index.js

267 lines
8.2 KiB
JavaScript

require("dotenv").config();
const express = require('express');
const expressSession = require('express-session');
const FileStore = require('session-file-store')(expressSession);
const ejs = require("ejs")
const sqlite3 = require('sqlite3').verbose();
const bcrypt = require("bcrypt")
const crypto = require("crypto")
const app = express();
const port = process.env.SERVER_PORT || 3000;
const db = new sqlite3.Database('astrocom.db', (err) => {
if (err) {
console.error('Error connecting to database:', err);
} else {
console.log('Connected to SQLite database');
}
});
// Create 'routes' table
// We need to store server address, port, secret, block_start and block_length. Then make a query that takes in an arbitrary number, and returns a row if that number between block start and block start + block length.
db.run('CREATE TABLE IF NOT EXISTS routes (id INTEGER PRIMARY KEY AUTOINCREMENT, server TEXT NOT NULL, port INTEGER NOT NULL DEFAULT 4569, auth TEST NOT NULL DEFAULT astrocom, secret TEXT NOT NULL, block_start INTEGER UNIQUE NOT NULL, block_length INTEGER NOT NULL DEFAULT 9999, apiKey TEXT NOT NULL)');
db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, passwordHash TEXT NOT NULL)');
// Check if user 1 exists, if not, create it (admin:admin)
const saltRounds = 10;
db.get("SELECT * FROM users WHERE id = 1", [], (err, row) => {
if (err) {
console.error('Error checking for admin user:', err);
return;
}
if (!row) {
bcrypt.hash('admin', saltRounds, (err, hash) => {
if (err) {
console.error('Error creating hash:', err);
return;
}
db.run("INSERT INTO users (id, username, passwordHash) VALUES (1, 'admin', ?)",
[hash],
(err) => {
if (err) {
console.error('Error creating admin user:', err);
} else {
console.log('Admin user created');
}
});
});
}
});
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const fileStoreOptions = {};
const sessionStore = new FileStore(fileStoreOptions);
app.use(expressSession({
store: sessionStore,
secret: process.env.SESSION_SECRET || 'default_secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// Admin routes
// admin/logout
app.get('/admin/logout', (req, res) => {
req.session.destroy();
res.redirect('/admin/login');
});
app.get('/admin/login', (req, res) => {
res.render('login');
});
app.post('/admin/login', (req, res) => {
const username = req.body.username;
const password = req.body.password;
db.get("SELECT * FROM users WHERE username = ?", [String(username)], (err, row) => {
if (err) {
console.error('Error getting user:', err);
res.status(500).send('Internal server error');
return;
}
if (!row) {
res.status(401).send('Unauthorized (Not Found)');
return;
}
bcrypt.compare(password, row.passwordHash, (err, result) => {
if (err) {
console.error('Error comparing password:', err);
res.status(500).send('Internal server error');
return;
}
if (result) {
req.session.authenticated = true;
res.redirect('/admin');
} else {
res.status(401).send('Unauthorized');
}
});
});
})
app.get('/api/v1/admin/routes', (req, res) => { // Get all routes
if (!req.session.authenticated) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
db.all('SELECT * FROM routes', (err, rows) => {
if (err) {
console.error('Error getting routes:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
console.log(rows)
res.json(rows);
});
});
app.get('/api/v1/admin/route/:id', (req, res) => { // Get route
if (!req.session.authenticated) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
db.get('SELECT * FROM routes WHERE id = ?', [req.params.id], (err, row) => {
if (err) {
console.error('Error getting route:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
if (!row) {
res.status(404).json({ error: 'Not Found' });
return;
}
res.json(row);
});
});
app.post('/api/v1/admin/route', (req, res) => { // Create a new route
if (!req.session.authenticated) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
const server = req.body.server;
const port = req.body.port;
const auth = req.body.auth || "astrocom";
const secret = req.body.secret || crypto.randomBytes(15).toString('hex');
const block_start = req.body.block_start;
const block_length = req.body.block_length || 9999;
const apiKey = crypto.randomBytes(32).toString('hex');
// Validate all inputs exist
if (!server || !port || !block_start) {
res.status(400).json({ error: 'Bad Request' });
return;
}
db.run('INSERT INTO routes (server, port, auth, secret, block_start, block_length, apiKey) VALUES (?, ?, ?, ?, ?, ?, ?)',
[server, port, auth, secret, block_start, block_length, apiKey],
(err) => {
if (err) {
console.error('Error creating route:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
res.status(201).json({ message: 'Created' });
});
});
app.put('/api/v1/admin/route/:id', (req, res) => { // Update a route
// Check if authenticated
if (!req.session.authenticated) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
// Check if route exists
db.get('SELECT * FROM routes WHERE id = ?', [req.params.id], (err, row) => {
if (err) {
console.error('Error getting route:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
if (!row) {
res.status(404).json({ error: 'Not Found' });
return;
}
// Update route
const server = req.body.server || row.server;
const port = req.body.port || row.port;
const auth = req.body.auth || row.auth;
const secret = req.body.secret || row.secret;
const block_start = req.body.block_start || row.block_start;
const block_length = req.body.block_length || row.block_length;
db.run('UPDATE routes SET server = ?, port = ?, auth = ?, secret = ?, block_start = ?, block_length = ? WHERE id = ?',
[server, port, auth, secret, block_start, block_length, req.params.id],
(err) => {
if (err) {
console.error('Error updating route:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
res.json({ message: 'Updated' });
});
});
});
app.delete('/api/v1/admin/route/:id', (req, res) => { // Delete a route
if (!req.session.authenticated) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
db.run('DELETE FROM routes WHERE id = ?', [req.params.id], (err) => {
if (err) {
console.error('Error deleting route:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
res.json({ message: 'Deleted' });
});
});
// Query to get a route
app.get('/api/v1/route/:apiKey/:ani/:number', (req, res) => {
const apiKey = req.params.apiKey;
const number = Number(req.params.number);
const ani = Number(req.params.ani);
console.log(`${apiKey}:${req.params.ani}:${number}`);
db.get("SELECT * FROM routes WHERE apiKey = ? AND block_start <= ? AND block_start + block_length >= ?", [apiKey, ani, ani], (err, row) => {
// If no row or error, return 401
if (err || !row) {
console.error(err);
console.log(row)
res.status(401).send(`${process.env.MSG_ROUTE_ADDRESS}/401`)
return;
}
db.get('SELECT * FROM routes WHERE block_start <= ? AND block_start + block_length >= ?', [number, number], (err, row) => {
if (err) {
console.error('Error getting route:', err);
res.status(500).send(`${process.env.MSG_ROUTE_ADDRESS}/500`)
} else if (row) {
// Check if the ANI is within the block range
// If it is, return `local`
if (req.params.ani >= row.block_start && req.params.ani <= row.block_start + row.block_length) {
console.log("sent local")
res.status(200).send('local');
} else {
console.log("sent remote")
res.status(200).send(`IAX2/${row.auth}:${row.secret}@${row.server}:${row.port}/${number}`);
}
} else {
console.log("boowomp")
res.status(404).send(`${process.env.MSG_ROUTE_ADDRESS}/404`);
}
});
});
});
// Start server
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});