Skip to content

Commit 25285ca

Browse files
#716 Per Mission Permissions (#717)
* #716 Per-Mission Permission part 1 * #716 Per Mission Permissions
1 parent 1fededb commit 25285ca

File tree

17 files changed

+953
-130
lines changed

17 files changed

+953
-130
lines changed

.cursorignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
..
2+
.vscode
3+
.DS_Store
4+
.env
5+
6+
/node_modules/
7+
/ssl/*
8+
!/ssl/.gitkeep
9+
/API/logs/*
10+
/Missions/*
11+
!/Missions/.gitkeep
12+
/private/api/spice/kernels/*
13+
14+
#Nested repo where private backend might reside
15+
/API/MMGIS-Private-Backend
16+
#Nested repo where private tools might reside
17+
/src/essence/MMGIS-Private-Tools
18+
/src/essence/MMGIS-Private-Tools-OFF
19+
20+
/config/pre/toolConfigs.json
21+
/src/pre/tools.js
22+
23+
/spice/kernels/*
24+
!/spice/kernels/.gitkeep
25+
/Missions/spice-kernels-conf.json
26+
!/Missions/spice-kernels-conf.example*json
27+
28+
/build/*
29+
/data/*
30+
*__pycache__
31+
32+
sessions
33+
.terraform/
34+
.terraform.lock.hcl
35+
36+
docker-compose.yml
37+
docker-compose.env.yml
38+
39+
#tools

.cursorrules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

API/Backend/Accounts/routes/accounts.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ router.get("/entries", function (req, res) {
1717
"username",
1818
"email",
1919
"permission",
20+
"missions_managing",
2021
"createdAt",
2122
"updatedAt",
2223
],
@@ -125,10 +126,23 @@ router.post("/update", function (req, res, next) {
125126
if (
126127
req.body.hasOwnProperty("permission") &&
127128
req.body.permission != null &&
128-
(req.body.permission === "111" || req.body.permission === "001")
129+
(req.body.permission === "110" || req.body.permission === "001")
129130
) {
130131
toUpdateTo.permission = req.body.permission;
131132
}
133+
// Handle missions_managing field for admin users
134+
if (
135+
req.body.hasOwnProperty("missions_managing") &&
136+
Array.isArray(req.body.missions_managing) &&
137+
req.body.permission === "110"
138+
) {
139+
toUpdateTo.missions_managing = req.body.missions_managing;
140+
}
141+
// Clear missions_managing if user is being changed to non-admin role
142+
if (req.body.permission === "001") {
143+
toUpdateTo.missions_managing = null;
144+
}
145+
132146
// Don't allow changing the main admin account's permissions
133147
if (id === 1) {
134148
delete toUpdateTo.permission;
@@ -137,7 +151,7 @@ router.post("/update", function (req, res, next) {
137151
let updateObj = {
138152
where: {
139153
id: id,
140-
},
154+
}
141155
};
142156

143157
User.update(toUpdateTo, updateObj)
@@ -154,14 +168,14 @@ router.post("/update", function (req, res, next) {
154168
.catch((err) => {
155169
logger(
156170
"error",
157-
`Failed to update user with id: '${id}'.`,
171+
`Failed to update user with id: '${id}'. Email may already exist.`,
158172
req.originalUrl,
159173
req,
160174
err
161175
);
162176
res.send({
163177
status: "failure",
164-
message: `Failed updated user with id: '${id}'.`,
178+
message: `Failed updated user with id: '${id}'. Email may already exist.`,
165179
body: {},
166180
});
167181
});

API/Backend/Config/routes/configs.js

Lines changed: 163 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,28 @@ const { sequelize } = require("../../../connection");
1212
const logger = require("../../../logger");
1313
const Config = require("../models/config");
1414
const config_template = require("../../../templates/config_template");
15+
const userModel = require("../../Users/models/user");
16+
const User = userModel.User;
1517

1618
// Sanitize user input to prevent XSS in error messages
1719
function sanitizeInput(input) {
18-
if (typeof input !== 'string') return String(input);
19-
return input
20-
.replace(/[<>'"&]/g, function(match) {
21-
switch(match) {
22-
case '<': return '&lt;';
23-
case '>': return '&gt;';
24-
case '"': return '&quot;';
25-
case "'": return '&#x27;';
26-
case '&': return '&amp;';
27-
default: return match;
28-
}
29-
});
20+
if (typeof input !== "string") return String(input);
21+
return input.replace(/[<>'"&]/g, function (match) {
22+
switch (match) {
23+
case "<":
24+
return "&lt;";
25+
case ">":
26+
return "&gt;";
27+
case '"':
28+
return "&quot;";
29+
case "'":
30+
return "&#x27;";
31+
case "&":
32+
return "&amp;";
33+
default:
34+
return match;
35+
}
36+
});
3037
}
3138

3239
const GeneralOptions = require("../../GeneralOptions/models/generaloptions");
@@ -48,6 +55,92 @@ if (
4855
)
4956
fullAccess = true;
5057

58+
// Middleware to check if admin user has permission to access specific mission
59+
function checkMissionPermission(req, res, next) {
60+
// Determine if this is a long term token request or regular session
61+
let userPermission, userMissions, userId;
62+
63+
if (req.isLongTermToken) {
64+
// Use token creator's permissions for long term tokens
65+
userPermission = req.tokenUserPermission;
66+
userMissions = req.tokenUserMissions;
67+
userId = null; // We don't need to lookup again since we have the data
68+
} else {
69+
// Use session permissions for regular requests
70+
userPermission = req.session.permission;
71+
userId = req.session.uid;
72+
userMissions = null; // Will be looked up from database
73+
}
74+
75+
// SuperAdmins (111) have access to all missions
76+
if (userPermission === "111") {
77+
next();
78+
return;
79+
}
80+
81+
// Regular users (not 110) should not access config endpoints
82+
if (userPermission !== "110") {
83+
res.send({
84+
status: "failure",
85+
message: "Unauthorized - insufficient permissions."
86+
});
87+
return;
88+
}
89+
90+
// For Admins (110), check mission-specific permissions
91+
const mission = req.body.mission || req.query.mission;
92+
if (!mission) {
93+
next(); // No mission specified, let other validation handle it
94+
return;
95+
}
96+
97+
// If we already have missions from token, use them directly
98+
if (req.isLongTermToken) {
99+
const managingMissions = userMissions || [];
100+
if (managingMissions.includes(mission)) {
101+
next();
102+
} else {
103+
res.send({
104+
status: "failure",
105+
message: `Unauthorized - no permission to access mission: ${mission}`
106+
});
107+
}
108+
return;
109+
}
110+
111+
// For regular session users, get user's missions_managing array from database
112+
User.findOne({
113+
where: { id: userId },
114+
attributes: ["missions_managing"]
115+
})
116+
.then((user) => {
117+
if (!user) {
118+
res.send({
119+
status: "failure",
120+
message: "User not found."
121+
});
122+
return;
123+
}
124+
125+
const managingMissions = user.missions_managing || [];
126+
if (managingMissions.includes(mission)) {
127+
next();
128+
} else {
129+
res.send({
130+
status: "failure",
131+
message: `Unauthorized - no permission to access mission: ${mission}`
132+
});
133+
}
134+
})
135+
.catch((err) => {
136+
logger("error", "Failed to check mission permissions.", req.originalUrl, req, err);
137+
res.send({
138+
status: "failure",
139+
message: "Failed to verify mission permissions."
140+
});
141+
});
142+
}
143+
51144
function get(req, res, next, cb) {
52145
Config.findAll({
53146
limit: 1,
@@ -109,12 +202,16 @@ function get(req, res, next, cb) {
109202
if (cb)
110203
cb({
111204
status: "failure",
112-
message: `Mission '${sanitizeInput(req.query.mission)} v${version}' not found.`,
205+
message: `Mission '${sanitizeInput(
206+
req.query.mission
207+
)} v${version}' not found.`,
113208
});
114209
else
115210
res.send({
116211
status: "failure",
117-
message: `Mission '${sanitizeInput(req.query.mission)} v${version}' not found.`,
212+
message: `Mission '${sanitizeInput(
213+
req.query.mission
214+
)} v${version}' not found.`,
118215
});
119216
return null;
120217
});
@@ -258,6 +355,13 @@ function add(req, res, next, cb) {
258355

259356
if (fullAccess)
260357
router.post("/add", function (req, res, next) {
358+
if (req.session.permission !== "111") {
359+
res.send({
360+
status: "failure",
361+
message: "Only SuperAdmins can add new missions.",
362+
});
363+
return null;
364+
}
261365
add(req, res, next);
262366
});
263367

@@ -467,7 +571,7 @@ function upsert(req, res, next, cb, info) {
467571
}
468572

469573
if (fullAccess)
470-
router.post("/upsert", function (req, res, next) {
574+
router.post("/upsert", checkMissionPermission, function (req, res, next) {
471575
upsert(req, res, next);
472576
});
473577

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

612+
// Get current user's mission permissions for the Configure page
613+
router.get("/user-permissions", function (req, res, next) {
614+
// SuperAdmins can manage all missions
615+
if (req.session.permission === "111") {
616+
res.send({
617+
status: "success",
618+
permission: "111",
619+
missions_managing: null // null means all missions
620+
});
621+
return;
622+
}
623+
624+
// Regular admins get their specific mission list
625+
if (req.session.permission === "110") {
626+
User.findOne({
627+
where: { id: req.session.uid },
628+
attributes: ["missions_managing"]
629+
})
630+
.then((user) => {
631+
res.send({
632+
status: "success",
633+
permission: "110",
634+
missions_managing: user ? user.missions_managing || [] : []
635+
});
636+
})
637+
.catch((err) => {
638+
logger("error", "Failed to get user permissions.", req.originalUrl, req, err);
639+
res.send({ status: "failure", message: "Failed to get user permissions." });
640+
});
641+
return;
642+
}
643+
644+
// Non-admin users
645+
res.send({
646+
status: "success",
647+
permission: req.session.permission || "000",
648+
missions_managing: []
649+
});
650+
});
651+
508652
if (fullAccess)
509653
router.get("/versions", function (req, res, next) {
510654
Config.findAll({
@@ -908,7 +1052,7 @@ function addLayer(req, res, next, cb, forceConfig, caller = "addLayer") {
9081052
);
9091053
}
9101054
if (fullAccess)
911-
router.post("/addLayer", function (req, res, next) {
1055+
router.post("/addLayer", checkMissionPermission, function (req, res, next) {
9121056
addLayer(req, res, next);
9131057
});
9141058

@@ -917,7 +1061,7 @@ if (fullAccess)
9171061
* /updateLayer
9181062
* Finds the existing layer, merges new layer items, deletes and readds with addLayer.
9191063
*/
920-
router.post("/updateLayer", function (req, res, next) {
1064+
router.post("/updateLayer", checkMissionPermission, function (req, res, next) {
9211065
const exampleBody = {
9221066
mission: "{mission_name}",
9231067
layerUUID: "{existing_layer_uuid}",
@@ -1168,7 +1312,7 @@ if (fullAccess)
11681312
"forceClientUpdate?": true
11691313
}
11701314
*/
1171-
router.post("/removeLayer", function (req, res, next) {
1315+
router.post("/removeLayer", checkMissionPermission, function (req, res, next) {
11721316
removeLayer(req, res, next);
11731317
});
11741318

@@ -1182,7 +1326,7 @@ if (fullAccess)
11821326
"zoom?": 0
11831327
}
11841328
*/
1185-
router.post("/updateInitialView", function (req, res, next) {
1329+
router.post("/updateInitialView", checkMissionPermission, function (req, res, next) {
11861330
const exampleBody = {
11871331
mission: "{mission_name}",
11881332
"latitude?": 0,

API/Backend/Config/setup.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ let setup = {
4040
s.ensureAdmin(true),
4141
(req, res) => {
4242
const user = process.env.AUTH === "csso" ? req.user : req.user || "";
43+
const permission = req.session.permission || "000";
4344
res.render("../configure/build/index.pug", {
4445
user: user,
46+
permission: permission,
4547
AUTH: process.env.AUTH,
4648
NODE_ENV: process.env.NODE_ENV,
4749
PORT: process.env.PORT || "8888",

0 commit comments

Comments
 (0)