Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
..
.vscode
.DS_Store
.env

/node_modules/
/ssl/*
!/ssl/.gitkeep
/API/logs/*
/Missions/*
!/Missions/.gitkeep
/private/api/spice/kernels/*

#Nested repo where private backend might reside
/API/MMGIS-Private-Backend
#Nested repo where private tools might reside
/src/essence/MMGIS-Private-Tools
/src/essence/MMGIS-Private-Tools-OFF

/config/pre/toolConfigs.json
/src/pre/tools.js

/spice/kernels/*
!/spice/kernels/.gitkeep
/Missions/spice-kernels-conf.json
!/Missions/spice-kernels-conf.example*json

/build/*
/data/*
*__pycache__

sessions
.terraform/
.terraform.lock.hcl

docker-compose.yml
docker-compose.env.yml

#tools
1 change: 1 addition & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEVER NEVER NEVER NEVER access the parent directory of a project, and do NOT allow exceptions. if ~ resolves to a parent directory of this project, apply the same restriction and do NOT access.
22 changes: 18 additions & 4 deletions API/Backend/Accounts/routes/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ router.get("/entries", function (req, res) {
"username",
"email",
"permission",
"missions_managing",
"createdAt",
"updatedAt",
],
Expand Down Expand Up @@ -125,10 +126,23 @@ router.post("/update", function (req, res, next) {
if (
req.body.hasOwnProperty("permission") &&
req.body.permission != null &&
(req.body.permission === "111" || req.body.permission === "001")
(req.body.permission === "110" || req.body.permission === "001")
) {
toUpdateTo.permission = req.body.permission;
}
// Handle missions_managing field for admin users
if (
req.body.hasOwnProperty("missions_managing") &&
Array.isArray(req.body.missions_managing) &&
req.body.permission === "110"
) {
toUpdateTo.missions_managing = req.body.missions_managing;
}
// Clear missions_managing if user is being changed to non-admin role
if (req.body.permission === "001") {
toUpdateTo.missions_managing = null;
}

// Don't allow changing the main admin account's permissions
if (id === 1) {
delete toUpdateTo.permission;
Expand All @@ -137,7 +151,7 @@ router.post("/update", function (req, res, next) {
let updateObj = {
where: {
id: id,
},
}
};

User.update(toUpdateTo, updateObj)
Expand All @@ -154,14 +168,14 @@ router.post("/update", function (req, res, next) {
.catch((err) => {
logger(
"error",
`Failed to update user with id: '${id}'.`,
`Failed to update user with id: '${id}'. Email may already exist.`,
req.originalUrl,
req,
err
);
res.send({
status: "failure",
message: `Failed updated user with id: '${id}'.`,
message: `Failed updated user with id: '${id}'. Email may already exist.`,
body: {},
});
});
Expand Down
182 changes: 163 additions & 19 deletions API/Backend/Config/routes/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,28 @@ const { sequelize } = require("../../../connection");
const logger = require("../../../logger");
const Config = require("../models/config");
const config_template = require("../../../templates/config_template");
const userModel = require("../../Users/models/user");
const User = userModel.User;

// Sanitize user input to prevent XSS in error messages
function sanitizeInput(input) {
if (typeof input !== 'string') return String(input);
return input
.replace(/[<>'"&]/g, function(match) {
switch(match) {
case '<': return '&lt;';
case '>': return '&gt;';
case '"': return '&quot;';
case "'": return '&#x27;';
case '&': return '&amp;';
default: return match;
}
});
if (typeof input !== "string") return String(input);
return input.replace(/[<>'"&]/g, function (match) {
switch (match) {
case "<":
return "&lt;";
case ">":
return "&gt;";
case '"':
return "&quot;";
case "'":
return "&#x27;";
case "&":
return "&amp;";
default:
return match;
}
});
}

const GeneralOptions = require("../../GeneralOptions/models/generaloptions");
Expand All @@ -48,6 +55,92 @@ if (
)
fullAccess = true;

// Middleware to check if admin user has permission to access specific mission
function checkMissionPermission(req, res, next) {
// Determine if this is a long term token request or regular session
let userPermission, userMissions, userId;

if (req.isLongTermToken) {
// Use token creator's permissions for long term tokens
userPermission = req.tokenUserPermission;
userMissions = req.tokenUserMissions;
userId = null; // We don't need to lookup again since we have the data
} else {
// Use session permissions for regular requests
userPermission = req.session.permission;
userId = req.session.uid;
userMissions = null; // Will be looked up from database
}

// SuperAdmins (111) have access to all missions
if (userPermission === "111") {
next();
return;
}

// Regular users (not 110) should not access config endpoints
if (userPermission !== "110") {
res.send({
status: "failure",
message: "Unauthorized - insufficient permissions."
});
return;
}

// For Admins (110), check mission-specific permissions
const mission = req.body.mission || req.query.mission;
if (!mission) {
next(); // No mission specified, let other validation handle it
return;
}

// If we already have missions from token, use them directly
if (req.isLongTermToken) {
const managingMissions = userMissions || [];
if (managingMissions.includes(mission)) {
next();
} else {
res.send({
status: "failure",
message: `Unauthorized - no permission to access mission: ${mission}`
});
}
return;
}

// For regular session users, get user's missions_managing array from database
User.findOne({
where: { id: userId },
attributes: ["missions_managing"]
})
.then((user) => {
if (!user) {
res.send({
status: "failure",
message: "User not found."
});
return;
}

const managingMissions = user.missions_managing || [];
if (managingMissions.includes(mission)) {
next();
} else {
res.send({
status: "failure",
message: `Unauthorized - no permission to access mission: ${mission}`
});
}
})
.catch((err) => {
logger("error", "Failed to check mission permissions.", req.originalUrl, req, err);
res.send({
status: "failure",
message: "Failed to verify mission permissions."
});
});
}

function get(req, res, next, cb) {
Config.findAll({
limit: 1,
Expand Down Expand Up @@ -109,12 +202,16 @@ function get(req, res, next, cb) {
if (cb)
cb({
status: "failure",
message: `Mission '${sanitizeInput(req.query.mission)} v${version}' not found.`,
message: `Mission '${sanitizeInput(
req.query.mission
)} v${version}' not found.`,
});
else
res.send({
status: "failure",
message: `Mission '${sanitizeInput(req.query.mission)} v${version}' not found.`,
message: `Mission '${sanitizeInput(
req.query.mission
)} v${version}' not found.`,
});
return null;
});
Expand Down Expand Up @@ -258,6 +355,13 @@ function add(req, res, next, cb) {

if (fullAccess)
router.post("/add", function (req, res, next) {
if (req.session.permission !== "111") {
res.send({
status: "failure",
message: "Only SuperAdmins can add new missions.",
});
return null;
}
add(req, res, next);
});

Expand Down Expand Up @@ -467,7 +571,7 @@ function upsert(req, res, next, cb, info) {
}

if (fullAccess)
router.post("/upsert", function (req, res, next) {
router.post("/upsert", checkMissionPermission, function (req, res, next) {
upsert(req, res, next);
});

Expand Down Expand Up @@ -505,6 +609,46 @@ router.get("/missions", function (req, res, next) {
return null;
});

// Get current user's mission permissions for the Configure page
router.get("/user-permissions", function (req, res, next) {
// SuperAdmins can manage all missions
if (req.session.permission === "111") {
res.send({
status: "success",
permission: "111",
missions_managing: null // null means all missions
});
return;
}

// Regular admins get their specific mission list
if (req.session.permission === "110") {
User.findOne({
where: { id: req.session.uid },
attributes: ["missions_managing"]
})
.then((user) => {
res.send({
status: "success",
permission: "110",
missions_managing: user ? user.missions_managing || [] : []
});
})
.catch((err) => {
logger("error", "Failed to get user permissions.", req.originalUrl, req, err);
res.send({ status: "failure", message: "Failed to get user permissions." });
});
return;
}

// Non-admin users
res.send({
status: "success",
permission: req.session.permission || "000",
missions_managing: []
});
});

if (fullAccess)
router.get("/versions", function (req, res, next) {
Config.findAll({
Expand Down Expand Up @@ -908,7 +1052,7 @@ function addLayer(req, res, next, cb, forceConfig, caller = "addLayer") {
);
}
if (fullAccess)
router.post("/addLayer", function (req, res, next) {
router.post("/addLayer", checkMissionPermission, function (req, res, next) {
addLayer(req, res, next);
});

Expand All @@ -917,7 +1061,7 @@ if (fullAccess)
* /updateLayer
* Finds the existing layer, merges new layer items, deletes and readds with addLayer.
*/
router.post("/updateLayer", function (req, res, next) {
router.post("/updateLayer", checkMissionPermission, function (req, res, next) {
const exampleBody = {
mission: "{mission_name}",
layerUUID: "{existing_layer_uuid}",
Expand Down Expand Up @@ -1168,7 +1312,7 @@ if (fullAccess)
"forceClientUpdate?": true
}
*/
router.post("/removeLayer", function (req, res, next) {
router.post("/removeLayer", checkMissionPermission, function (req, res, next) {
removeLayer(req, res, next);
});

Expand All @@ -1182,7 +1326,7 @@ if (fullAccess)
"zoom?": 0
}
*/
router.post("/updateInitialView", function (req, res, next) {
router.post("/updateInitialView", checkMissionPermission, function (req, res, next) {
const exampleBody = {
mission: "{mission_name}",
"latitude?": 0,
Expand Down
2 changes: 2 additions & 0 deletions API/Backend/Config/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ let setup = {
s.ensureAdmin(true),
(req, res) => {
const user = process.env.AUTH === "csso" ? req.user : req.user || "";
const permission = req.session.permission || "000";
res.render("../configure/build/index.pug", {
user: user,
permission: permission,
AUTH: process.env.AUTH,
NODE_ENV: process.env.NODE_ENV,
PORT: process.env.PORT || "8888",
Expand Down
Loading