Admin UI mostly there; TODO: User management

This commit is contained in:
Christopher Cookman 2024-12-15 02:09:48 -07:00
parent 8c6c0daadd
commit cea5348d6c
6 changed files with 175 additions and 80 deletions

View file

@ -29,19 +29,35 @@ db.get("SELECT * FROM users WHERE id = 1", [], (err, row) => {
console.error('Error checking for admin user:', err);
return;
}
if (!row) {
bcrypt.hash('admin', saltRounds, (err, hash) => {
if (!row || process.env.RESET_ADMIN) {
// Destroy all sessions
sessionStore.clear((err) => {
if (err) {
console.error('Error clearing sessions:', err);
return;
}
});
// delete all users
db.run("DELETE FROM users", [], (err) => {
if (err) {
console.error('Error deleting users:', err);
return;
}
});
// Generate 32 char random string
const passwd = crypto.randomBytes(32).toString('hex');
bcrypt.hash(passwd, saltRounds, (err, hash) => {
if (err) {
console.error('Error creating hash:', err);
return;
}
db.run("INSERT INTO users (id, username, passwordHash) VALUES (1, 'admin', ?)",
[hash],
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');
console.log(`Created admin user with password: ${passwd}`);
}
});
});
@ -59,7 +75,7 @@ app.use(expressSession({
secret: process.env.SESSION_SECRET || 'default_secret',
resave: false,
saveUninitialized: false,
cookie: {
cookie: {
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
@ -90,6 +106,14 @@ app.get('/admin', (req, res) => {
res.render('admin/index', { user: req.session.user });
});
app.get('/admin/create', (req, res) => {
if (!req.session.authenticated) {
res.redirect('/admin/login');
return;
}
res.render('admin/create', { user: req.session.user });
});
app.get('/admin/route/:id', (req, res) => {
if (!req.session.authenticated) {
res.redirect('/admin/login');
@ -150,7 +174,6 @@ app.get('/api/v1/admin/routes', (req, res) => { // Get all routes
res.status(500).json({ error: 'Internal server error' });
return;
}
console.log(rows)
res.json(rows);
});
});
@ -191,16 +214,29 @@ app.post('/api/v1/admin/route', (req, res) => { // Create a new route
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' });
});
// Check if route already exists (OR conditions on server, and block range)
db.get('SELECT * FROM routes WHERE server = ? OR block_start <= ? AND block_start + block_length >= ?', [server, block_start, block_start], (err, row) => {
if (err) {
console.error('Error checking for existing route:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
if (row) {
res.status(409).json({ error: 'Conflict' });
return;
} else {
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
@ -227,8 +263,8 @@ app.put('/api/v1/admin/route/:id', (req, res) => { // Update a route
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],
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);
@ -262,12 +298,10 @@ 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;
}
@ -279,14 +313,11 @@ app.get('/api/v1/route/:apiKey/:ani/:number', (req, res) => {
// 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 IAX2/${row.auth}:${row.secret}@${row.server}:${row.port}/${number}`)
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`);
}
});

View file

@ -0,0 +1,26 @@
const createForm = document.getElementById('createForm');
createForm.addEventListener('submit', async (e) => {
console.log("GUH")
e.preventDefault();
const formData = new FormData(createForm);
const data = {};
for (const [key, value] of formData.entries()) {
data[key] = value;
}
const response = await fetch('/api/v1/admin/route', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
window.location.href = '/admin';
} else {
alert('Failed to create entry');
}
});

View file

@ -1,28 +1,25 @@
document.addEventListener('DOMContentLoaded', function() {
const routeId = document.getElementById('route').value;
const editForm = document.getElementById('editForm');
editForm.addEventListener('submit', async (e) => {
e.preventDefault();
async function updateRoute(routeData) {
try {
const response = await fetch(`/api/v1/admin/route/${routeId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(routeData)
});
const formData = new FormData(editForm);
const data = {};
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error updating route:', error);
throw error;
}
for (const [key, value] of formData.entries()) {
data[key] = value;
}
// Export the function if needed
window.updateRoute = updateRoute;
const response = await fetch(`/api/v1/admin/route/${route}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
window.location.href = '/admin';
} else {
alert('Failed to update entry');
}
});

63
views/admin/create.ejs Normal file
View file

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
<title>AstroCom Admin - Create new server</title>
</head>
<body class="bg-dark text-white">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">AstroCom</a>
<div class="navbar-nav ms-auto">
<span class="navbar-text me-3">
Welcome, <%= user %>
</span>
<a href="/admin/logout" class="btn btn-danger">Logout</a>
</div>
</div>
</nav>
<div class="container mt-4">
<h2>Create new entry</h2>
<form id="createForm">
<div class="mb-3">
<label for="server" class="form-label">Server</label>
<input type="text" class="form-control" id="server" name="server" required>
</div>
<div class="mb-3">
<label for="port" class="form-label">Port</label>
<input type="number" class="form-control" id="port" name="port" value="4569" required>
</div>
<div class="mb-3">
<label for="auth" class="form-label">Auth</label>
<input type="text" class="form-control" id="auth" name="auth" value="from-astrocom" required>
</div>
<div class="mb-3">
<label for="secret" class="form-label">Secret (Optional)</label>
<input type="text" class="form-control" id="secret" name="secret">
</div>
<div class="mb-3">
<label for="block_start" class="form-label">Block Start</label>
<input type="number" class="form-control" id="block_start" name="block_start" required>
</div>
<div class="mb-3">
<label for="block_length" class="form-label">Block Length</label>
<input type="number" class="form-control" id="block_length" name="block_length" value="9999" required>
</div>
<div class="mb-3">
<label for="apiKey" class="form-label">API Key (Optional)</label>
<input type="text" class="form-control" id="apiKey" name="apiKey">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a href="/admin" class="btn btn-secondary">Cancel</a>
</form>
<script src="/assets/js/adminCreate.js"></script>
</div>
<!-- <script src="/assets/js/adminCreate.js"></script> -->
<script src="/assets/js/bootstrap.min.js"></script>
<script src="/assets/js/bootstrap.bundle.min.js"></script>
<script src="/assets/js/jquery.min.js"></script>
</body>
</html>

View file

@ -18,9 +18,9 @@
</div>
</div>
</nav>
<div class="container mt-4"></div>
<div class="container mt-4">
<h2>Edit Entry <%= data.id %></h2>
<form id="editForm" onsubmit="return false;">
<form id="editForm" onsubmit="return false;" class="mt-4">
<% for (const [key, value] of Object.entries(data)) { %>
<% if (key !== 'id') { %>
<div class="mb-3">
@ -31,38 +31,13 @@
<% } %>
<button type="submit" class="btn btn-primary">Update</button>
<a href="/admin" class="btn btn-secondary">Cancel</a>
</form>
</form>
<script>
const route = <%= data.id %>;
</script>
</div>
<script src="/assets/js/adminEdit.js"></script>
</div>
<script>
const route = <%= data.id %>;
const editForm = document.getElementById('editForm');
editForm.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(editForm);
const data = {};
for (const [key, value] of formData.entries()) {
data[key] = value;
}
const response = await fetch(`/api/v1/admin/route/${route}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
window.location.href = '/admin';
} else {
alert('Failed to update entry');
}
});
</script>
<script src="/assets/js/bootstrap.min.js"></script>
<script src="/assets/js/bootstrap.bundle.min.js"></script>
<script src="/assets/js/jquery.min.js"></script>

View file

@ -18,7 +18,10 @@
</div>
</div>
</nav>
<h2>Admin Dashboard</h2>
<div class="d-flex align-items-center gap-3 mb-3">
<h2 class="m-0">Admin Dashboard</h2>
<a href="/admin/create" class="btn btn-primary">Create New Server</a>
</div>
<table class="table table-striped table-dark" id="adminTable">
<thead>
<tr>