Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
30f5a95
removed logging for expired token
AlexisBalzano Nov 12, 2025
7fecc06
Switch expired stand log from warn to info
AlexisBalzano Nov 12, 2025
40429c2
Fixed error logging missing actual error
AlexisBalzano Nov 12, 2025
3551bd2
removed user from valid role list
AlexisBalzano Nov 12, 2025
b2e629d
Reduced unzoomed map airport circle size
AlexisBalzano Nov 12, 2025
37983c0
Fixed log/configs user verification
AlexisBalzano Nov 13, 2025
8541390
Viewer is now at root url
AlexisBalzano Nov 13, 2025
6ae4146
Fixed no API display message
AlexisBalzano Nov 13, 2025
d35e0d9
Fixed requireRole function error
AlexisBalzano Nov 14, 2025
267b88d
secured more routes
AlexisBalzano Nov 14, 2025
2f0f021
Made expired stand log info instead of warn
AlexisBalzano Nov 14, 2025
1b1eae5
Fixed manual assign token verification
AlexisBalzano Nov 14, 2025
7d50053
Added isArray check for APIkey
AlexisBalzano Nov 15, 2025
c9e6044
Ignoring databases
AlexisBalzano Nov 15, 2025
1eb1ba0
apiKeys are now in there own database
AlexisBalzano Nov 15, 2025
8268784
Now check if entire callsign is inside the stand restriction
AlexisBalzano Nov 15, 2025
ba2016f
Now check if entire callsign is inside the stand restriction
AlexisBalzano Nov 15, 2025
932fc59
Merge branch 'Dashboard-Update' of https://github.com/AlexisBalzano/R…
AlexisBalzano Nov 15, 2025
73b0dfe
Added polygon drawing on map
AlexisBalzano Nov 15, 2025
dfff0e5
Now support new Apron def + max size
AlexisBalzano Nov 15, 2025
443297a
updated API url
AlexisBalzano Nov 15, 2025
50ae5d7
Fixed generate API key
AlexisBalzano Nov 16, 2025
b15dcbe
Fixed apron display in map
AlexisBalzano Nov 16, 2025
ef77cd5
Fixed apron full when manually assigning stand
AlexisBalzano Nov 16, 2025
561a0d5
Fixing apron detection
AlexisBalzano Nov 16, 2025
7078098
Fixed missing variable definition
AlexisBalzano Nov 17, 2025
b2c3043
Fixed apron management
AlexisBalzano Nov 17, 2025
3c01724
Fixed map apron popup callsign list
AlexisBalzano Nov 17, 2025
7e47b08
version is now 1.0.7
AlexisBalzano Nov 17, 2025
bf59a43
Fixed circular apron map color
AlexisBalzano Nov 17, 2025
0b69192
Update script.js
AlexisBalzano Nov 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.vscode/settings.json
.env
logs.db
*.db

data/

Expand Down
144 changes: 144 additions & 0 deletions controllers/APIkeyController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const { error, warn } = require("../utils/logger");
const sqlite3 = require('sqlite3').verbose();
const crypto = require("crypto");
const path = require('path');

const VALIDITY_PERIOD_MS = 30 * 24 * 60 * 60 * 1000; // 30 days

// Setup database for API keys
const dbPath = path.join(__dirname, '../APIkeys.db');
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('Error opening database:', err);
} else {
console.log('Connected to API keys database');
initializeDatabase();
}
});

// Initialize database schema
function initializeDatabase() {
db.serialize(() => {
// Create table
db.run(`
CREATE TABLE IF NOT EXISTS api_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
key TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
last_used_at INTEGER,
expires_at INTEGER
)
`, (err) => {
if (err) console.error('Error creating API keys table:', err);
else console.log('API keys table ready');
});

// Create indexes
db.run('CREATE INDEX IF NOT EXISTS idx_user_id ON api_keys(user_id)');
db.run('CREATE INDEX IF NOT EXISTS idx_key ON api_keys(key)');
db.run('CREATE INDEX IF NOT EXISTS idx_created_at ON api_keys(created_at)');
db.run('CREATE INDEX IF NOT EXISTS idx_expires_at ON api_keys(expires_at)');
});
}

exports.getKeys = (req, res) => {
const sql = 'SELECT id, user_id, key, created_at, last_used_at, expires_at FROM api_keys';
db.all(sql, [], (err, rows) => {
if (err) {
console.error('Error fetching API keys:', err);
res.status(500).json({ error: 'Internal Server Error' });
} else {
const apiKeys = rows.map(row => ({
id: row.id,
user_id: row.user_id,
key: row.key,
created_at: row.created_at,
last_used_at: row.last_used_at,
expires_at: row.expires_at
}));
res.json({ keys: apiKeys });
}
});
};

exports.getUserKey = (req, res) => {
const userId = req.params.id;
const sql = 'SELECT id, user_id, key, created_at, last_used_at, expires_at FROM api_keys WHERE user_id = ?';
db.get(sql, [userId], (err, row) => {
if (err) {
console.error('Error fetching API key:', err);
res.status(500).json({ error: 'Internal Server Error' });
} else {
res.json({ key: row });
}
});
};

exports.createKey = (req, res) => {
const userId = req.params.id;
const newKey = crypto.randomBytes(32).toString('hex');
const sql = 'INSERT INTO api_keys (user_id, key) VALUES (?, ?)';
db.run(sql, [userId, newKey], function (err) {
if (err) {
console.error('Error creating API key:', err);
res.status(500).json({ error: 'Internal Server Error' });
} else {
res.status(201).json({ key: { id: this.lastID, user_id: userId, key: newKey } });
}
});
};

exports.renewKey = (req, res) => {
const userId = req.params.id;
const sql = 'UPDATE api_keys SET expires_at = ? WHERE user_id = ?';
const expiresAt = Date.now() + VALIDITY_PERIOD_MS;

db.run(sql, [expiresAt, userId], function (err) {
if (err) {
console.error('Error renewing API key:', err);
res.status(500).json({ error: 'Internal Server Error' });
} else {
res.json({ key: { user_id: userId, expires_at: expiresAt } });
}
});
};

exports.deleteKey = (req, res) => {
const userId = req.params.id;
const sql = 'DELETE FROM api_keys WHERE user_id = ?';
db.run(sql, [userId], function (err) {
if (err) {
console.error('Error deleting API key:', err);
res.status(500).json({ error: 'Internal Server Error' });
} else {
res.json({ message: 'API key deleted successfully' });
}
});
};

// Cleanup old keys (older than 60 days)
exports.cleanupOldKeys = () => {
const sql = 'DELETE FROM api_keys WHERE expires_at < ?';
const now = Date.now() - (2 * VALIDITY_PERIOD_MS); // 60 days ago
db.run(sql, [now], function (err) {
if (err) {
console.error('Error cleaning up old API keys:', err);
} else {
console.log(`Deleted ${this.changes} old API keys`);
}
});
};

// Run cleanup periodically (every days)
setInterval(exports.cleanupOldKeys, 24 * 60 * 60 * 1000); // every days


// Graceful shutdown
process.on('SIGINT', () => {
db.close((err) => {
if (err) console.error('Error closing database:', err);
else console.log('Database connection closed');
process.exit(0);
});
});
96 changes: 17 additions & 79 deletions controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exports.getLocalUser = async (req, res) => {
const localUser = await redisService.getLocalUser(cid);
res.json(localUser);
} catch (err) {
error("Failed to get local user:", err);
error(`Failed to get local user: ${err}`, { category: "Auth" });
res.status(500).json({ error: "Failed to get local user" });
}
};
Expand All @@ -24,7 +24,7 @@ exports.getAllLocalUsers = async (req, res) => {
}
res.json(users);
} catch (err) {
error("Failed to get all local users:", err);
error(`Failed to get all local users: ${err}`, { category: "Auth" });
res.status(500).json({ error: "Failed to get users" });
}
};
Expand All @@ -39,7 +39,7 @@ exports.updateLocalUser = async (req, res) => {
}
res.json(updated);
} catch (err) {
error("Failed to update local user:", err);
error(`Failed to update local user: ${err}`, { category: "Auth" });
res.status(500).json({ error: "Failed to update local user" });
}
};
Expand Down Expand Up @@ -67,7 +67,7 @@ exports.grantRole = async function (req, res) {
}
res.json({ ok: true, user });
} catch (err) {
error("Failed to grant role:", err);
error(`Failed to grant role: ${err}`, { category: "Auth" });
res.status(500).json({ error: "Failed to grant role" });
}
};
Expand All @@ -90,28 +90,29 @@ exports.revokeRole = async function (req, res) {
}
res.json({ ok: true, user });
} catch (err) {
error("Failed to revoke role:", err);
error(`Failed to revoke role: ${err}`, { category: "Auth" });
res.status(500).json({ error: "Failed to revoke role" });
}
};

exports.requireRoles = (roles) => {
const requiredRoles = Array.isArray(roles) ? roles : roles ? [roles] : [];
return async (req, res, next) => {
if (!req.user || !req.user.cid) {
return res.status(401).json({ error: "Not authenticated" });
}

try {
const userRoles = req.user.roles || [];
const hasRequiredRole = roles.some((role) => userRoles.includes(role));
const hasRequiredRole = requiredRoles.some((role) => userRoles.includes(role));

if (hasRequiredRole) {
return next();
}

return res.status(403).json({ error: "Insufficient permissions" });
} catch (err) {
error("Failed to check roles:", err);
error(`Failed to check roles: ${err}`, { category: "Auth" });
return res.status(500).json({ error: "Failed to check permissions" });
}
};
Expand All @@ -120,7 +121,7 @@ exports.requireRoles = (roles) => {
// Verifying token from plugins for manual stand assignement
exports.verifyToken = (token, client) => {
// Return true if token is valid, false otherwise
const secret = process.env.CORE_JWT_KEY;
const secret = process.env.AUTH_SECRET;

if (!secret) {
error("No secret found", { category: "Auth" });
Expand Down Expand Up @@ -151,7 +152,7 @@ exports.logout = async (req, res) => {
try {
deleteSession(res);
const baseURL = process.env.BASE_URL;
return res.redirect(baseURL + "/rampagent/debug/");
return res.redirect(baseURL + "/rampagent/");
} catch (err) {
error("logout error: " + (err.message || err), { category: "Auth" });
return res.status(500).send("Error during logout");
Expand Down Expand Up @@ -202,7 +203,7 @@ exports.requireAuth = async (req, res, next) => {

return next();
} catch (err) {
error("Auth error:", err);
error(`Auth error: ${err.message || err}`, { category: "Auth" });
return res.status(401).json({ error: "Not authenticated" });
}
};
Expand Down Expand Up @@ -239,7 +240,9 @@ async function decryptToken(accessToken) {
tokenContent: payload,
};
} catch (err) {
error("Failed to verify session: " + err, { category: "Auth" });
if (err.name !== "TokenExpiredError") {
error("Failed to verify session: " + err, { category: "Auth" });
}
return null;
}
}
Expand All @@ -257,7 +260,7 @@ exports.getSession = async (req, res) => {
if (!sessionData) return res.status(401).json({ error: "Invalid session" });

const coreUserUrl =
process.env.CORE_URL_INTERNAL + `/v1/user/${sessionData.tokenContent.cid}`; //FIXME: is correct ?
process.env.CORE_URL_INTERNAL + `/v1/user/${sessionData.tokenContent.cid}`;
const coreRes = await fetch(coreUserUrl, {
method: "GET",
headers: {
Expand Down Expand Up @@ -355,74 +358,9 @@ exports.loginCallback = async (req, res) => {

// Redirect back to UI
const baseURL = process.env.BASE_URL;
return res.redirect(baseURL + "/rampagent/debug/#dashboard");
return res.redirect(baseURL + "/rampagent/#dashboard");
} catch (err) {
error("loginCallback error: " + (err.message || err), { category: "Auth" });
return res.status(401).send("Authentication failed, check logs");
}
};

// API Key Management

exports.getKeys = async (req, res) => {
try {
const keys = await redisService.getAllKeys();
return res.json(keys);
} catch (err) {
error("Error fetching keys:", err);
return res.status(500).json({ error: "Internal Server Error" });
}
};

exports.getUserKey = async (req, res) => {
try {
const keyId = req.params.id;
const key = await redisService.getKeyById(keyId);
if (key) {
return res.json(key);
}
return res.status(404).json({ error: "Key not found" });
} catch (err) {
error("Error fetching key:", err);
return res.status(500).json({ error: "Internal Server Error" });
}
};

exports.createKey = async (req, res) => {
try {
const id = req.params.id;
const newKey = await redisService.createKey(id);
return res.status(201).json(newKey);
} catch (err) {
error("Error creating key:", err);
return res.status(500).json({ error: "Internal Server Error" });
}
};

exports.renewKey = async (req, res) => {
try {
const keyId = req.params.id;
const renewed = await redisService.renewKey(keyId);
if (renewed) {
return res.status(200).json({ message: "Key renewed successfully" });
}
return res.status(404).json({ error: "Key not found" });
} catch (err) {
error("Error renewing key:", err);
return res.status(500).json({ error: "Internal Server Error" });
}
};

exports.deleteKey = async (req, res) => {
try {
const keyId = req.params.id;
const deleted = await redisService.deleteKey(keyId);
if (deleted) {
return res.status(200).json({ message: "Key deleted successfully" });
}
return res.status(404).json({ error: "Key not found" });
} catch (err) {
error("Error deleting key:", err);
return res.status(500).json({ error: "Internal Server Error" });
}
};
};
6 changes: 6 additions & 0 deletions controllers/occupancyController.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ exports.getOccupied = (req, res) => {
name: s.name,
icao: s.icao,
callsign: s.callsign || null,
remark: s.remark || null,
apronSize: s.apronSize || 0,
}));
res.json(occupied);
} catch (err) {
Expand All @@ -41,6 +43,8 @@ exports.getAssigned = (req, res) => {
name: s.name,
icao: s.icao,
callsign: s.callsign || null,
remark: s.remark || null,
apronSize: s.apronSize || 0,
}));
res.json(assigned);
} catch (err) {
Expand All @@ -58,6 +62,8 @@ exports.getBlocked = (req, res) => {
name: s.name,
icao: s.icao,
callsign: s.callsign || null,
remark: s.remark || null,
apronSize: s.apronSize || 0,
}));
res.json(blocked);
} catch (err) {
Expand Down
Loading