174 lines
5.5 KiB
JavaScript
174 lines
5.5 KiB
JavaScript
let db;
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
|
|
module.exports = (dbInit) => {
|
|
db = dbInit;
|
|
return router;
|
|
};
|
|
|
|
|
|
router.get('/', (req, res) => {
|
|
res.json({ success: true, message: "Welcome to the NOTXCS API!" });
|
|
});
|
|
router.get('/ping', (req, res) => {
|
|
// TODO: Implement a more robust health check. Probably will revolve around checking db conn and others.
|
|
res.json({ success: true, message: "pong" });
|
|
});
|
|
|
|
router.get('/:placeId', (req, res) => {
|
|
const placeId = req.params.placeId;
|
|
const apiKey = req.query.apiKey;
|
|
const universeid = req.query.universeid;
|
|
|
|
// For some reason the official XCS api doesn't actually check the api key when getting settings? So we won't either.
|
|
// XCS Also ignores universe ID. Probably used for some logging. We will ignore it.
|
|
if (!placeId) {
|
|
return res.status(404).json({ success: false, message: "What? How?" });
|
|
}
|
|
|
|
db.get(`SELECT * FROM places WHERE id = ?`, [placeId], (err, row) => {
|
|
if (err) {
|
|
console.error('Failed to retrieve place settings:', err.message);
|
|
return res.status(500).json({ success: false, message: "Internal server error" });
|
|
}
|
|
|
|
if (!row) {
|
|
return res.status(404).json({ success: false, message: "Place not found" });
|
|
}
|
|
|
|
db.all(`SELECT * FROM access_points WHERE placeId = ?`, [placeId], (err, accessPoints) => {
|
|
if (err) {
|
|
console.error('Failed to retrieve access points:', err.message);
|
|
return res.status(500).json({ success: false, message: "Internal server error" });
|
|
}
|
|
|
|
let resp = {
|
|
success: true,
|
|
accessPoints: accessPoints.reduce((acc, ap) => {
|
|
const armed = !!ap.armState; // Will have state 2 eventually for armed on schedule, but for now just 1 or 0.
|
|
|
|
acc[ap.id] = {
|
|
id: ap.id,
|
|
name: ap.name,
|
|
unlockTime: ap.unlockTime,
|
|
config: {
|
|
active: !!ap.enabled,
|
|
armed,
|
|
scanData: {
|
|
ready: JSON.parse(ap.readyData || '{}'),
|
|
disarmed: JSON.parse(ap.disarmedData || '{}')
|
|
}
|
|
}
|
|
}
|
|
return acc;
|
|
}, {})
|
|
};
|
|
console.log(resp)
|
|
res.json(resp);
|
|
});
|
|
});
|
|
});
|
|
|
|
router.get('/:placeId/:accessPointId/onScan', async (req, res) => {
|
|
// This one DOES check api key.
|
|
const placeId = req.params.placeId;
|
|
const accessPointId = req.params.accessPointId;
|
|
const apiKey = req.query.apiKey;
|
|
const userId = req.query.userId;
|
|
const cardNumbers = req.query.cardNumbers ? req.query.cardNumbers.split(',') : [];
|
|
const universeid = req.query.universeid || '0';
|
|
|
|
if (!placeId || !accessPointId) {
|
|
return res.status(404).json({ success: false, message: "What? How?" });
|
|
}
|
|
|
|
if (!apiKey) {
|
|
return res.status(400).json({ success: false, message: "API key is required" });
|
|
}
|
|
|
|
db.get(`SELECT * FROM places WHERE id = ? AND apiKey = ?`, [placeId, apiKey], (err, place) => {
|
|
if (err) {
|
|
console.error('Failed to retrieve place settings:', err.message);
|
|
return res.status(500).json({ success: false, message: "Internal server error" });
|
|
}
|
|
|
|
if (!place) {
|
|
return res.status(404).json({ success: false, message: "Place not found or invalid API key" });
|
|
}
|
|
|
|
db.get(`SELECT * FROM access_points WHERE id = ? AND placeId = ?`, [accessPointId, placeId], (err, ap) => {
|
|
if (err) {
|
|
console.error('Failed to retrieve access point:', err.message);
|
|
return res.status(500).json({ success: false, message: "Internal server error" });
|
|
}
|
|
|
|
if (!ap) {
|
|
return res.status(404).json({ success: false, message: "Access point not found" });
|
|
}
|
|
|
|
db.all(`SELECT * FROM acl WHERE accessPoint = ?`, [accessPointId], async (err, aclEntries) => {
|
|
if (err) {
|
|
console.error('Failed to retrieve ACL entries:', err.message);
|
|
return res.status(500).json({ success: false, message: "Internal server error" });
|
|
}
|
|
|
|
let userGroups = await fetch(`https://groups.roblox.com/v1/users/${userId}/groups/roles`).then(r => r.json()).then(data => data.data || []).catch(() => []);
|
|
userGroups = userGroups.map(g => ({group: g.group.id, rank: g.role.rank}));
|
|
console.log('User groups:', userGroups);
|
|
let grant_type;
|
|
let granted = false;
|
|
// Types: 0 = User ID; 1 = Card Number; 2 = Group Exact Rank; 3 = Group Min Rank, 4 = Allow All
|
|
// Check user ID, then group ranks, then card numbers.
|
|
for (const entry of aclEntries) {
|
|
switch (entry.type) {
|
|
case 0: // User ID
|
|
if (entry.data == userId) {
|
|
granted = true;
|
|
}
|
|
break;
|
|
case 1: // Card Number
|
|
if (cardNumbers.includes(entry.data)) {
|
|
granted = true;
|
|
}
|
|
break;
|
|
case 2: { // Group Exact Rank
|
|
const [group, rank] = entry.data.split(':');
|
|
if (userGroups.some(g => g.group == group && g.rank == rank)) {
|
|
granted = true;
|
|
}
|
|
break;
|
|
}
|
|
case 3: { // Group Min Rank
|
|
const [group, rank] = entry.data.split(':');
|
|
if (userGroups.some(g => g.group == group && g.rank >= rank)) {
|
|
granted = true;
|
|
}
|
|
break;
|
|
}
|
|
case 4: // Allow All
|
|
granted = true;
|
|
break;
|
|
}
|
|
|
|
if (granted) break;
|
|
}
|
|
|
|
const responseData = granted ? {
|
|
success: true,
|
|
grant_type: 'user_scan',
|
|
response_code: 'access_granted',
|
|
response_time: 0, // This is just analytics for how long their backend took to generate the response
|
|
scan_data: JSON.parse(ap.grantedData || '{}')
|
|
} : {
|
|
success: true,
|
|
grant_type: 'user_scan',
|
|
response_code: 'access_denied',
|
|
response_time: 0,
|
|
scan_data: JSON.parse(ap.deniedData || '{}')
|
|
}
|
|
res.json(responseData);
|
|
});
|
|
});
|
|
});
|
|
}); |