Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Super User Privilege Toggle #2111

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
53 changes: 53 additions & 0 deletions controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const { addLog } = require("../models/logs");
const { getUserStatus } = require("../models/userStatus");
const config = require("config");
const discordDeveloperRoleId = config.get("discordDeveloperRoleId");
const authService = require("../services/authService");

const verifyUser = async (req, res) => {
const userId = req.userData.id;
Expand Down Expand Up @@ -376,6 +377,9 @@ const getSelfDetails = async (req, res) => {
const user = await dataAccess.retrieveUsers({
userdata: req.userData,
});
if (req.userData.superUserAccess === false) {
user.roles.super_user = false;
}
return res.send(user);
}
return res.boom.notFound("User doesn't exist");
Expand Down Expand Up @@ -827,6 +831,53 @@ const setInDiscordScript = async (req, res) => {
}
};

const getSuperUserAccessStatus = async (req, res) => {
try {
if (req.userData.superUserAccess !== undefined) {
return res.json({ currentAccess: req.userData.superUserAccess });
} else {
return res.json({ message: "Super User Access Modifier Not Set!" });
}
} catch (error) {
return res.boom.badImplementation({ message: INTERNAL_SERVER_ERROR });
}
};

const setSuperUserAccessLimiter = async (req, res) => {
try {
let value;
switch (req.query.value) {
case "true":
value = true;
break;
case "false":
value = false;
break;
default:
break;
}
if (value !== undefined) {
const token = req.cookies[config.get("userToken.cookieName")];
const { userId } = authService.decodeAuthToken(token);
const newToken = authService.generateAuthToken({ userId, superUserAccess: value });
const rdsUiUrl = new URL(config.get("services.rdsUi.baseUrl"));
res.cookie(config.get("userToken.cookieName"), newToken, {
domain: rdsUiUrl.hostname,
expires: new Date(Date.now() + config.get("userToken.ttl") * 1000),
httpOnly: true,
secure: true,
sameSite: "lax",
});
return res.json({ message: "Super User Privilege Access Set!", currentAccess: value });
} else {
return res.boom.badRequest("Wrong value in query param, value can be either true or false");
}
} catch (error) {
logger.error(`Error while Setting Super Privilege Access Limiter: ${error}`);
return res.boom.badImplementation({ message: INTERNAL_SERVER_ERROR });
}
};

Comment on lines +846 to +880
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function become complex and not readable so fix this

const updateRoles = async (req, res) => {
try {
const result = await dataAccess.retrieveUsers({ id: req.params.id });
Expand Down Expand Up @@ -985,5 +1036,7 @@ module.exports = {
archiveUserIfNotInDiscord,
usersPatchHandler,
isDeveloper,
setSuperUserAccessLimiter,
getSuperUserAccessStatus,
getIdentityStats,
};
21 changes: 16 additions & 5 deletions middlewares/authenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const checkRestricted = async (req, res, next) => {
module.exports = async (req, res, next) => {
try {
let token = req.cookies[config.get("userToken.cookieName")];

/**
* Enable Bearer Token authentication for NON-PRODUCTION environments
* This is enabled as Swagger UI does not support cookie authe
Expand All @@ -51,12 +50,24 @@ module.exports = async (req, res, next) => {
token = req.headers.authorization.split(" ")[1];
}

const { userId } = authService.verifyAuthToken(token);
const { userId, superUserAccess } = authService.verifyAuthToken(token);
const userDoc = await dataAccess.retrieveUsers({ id: userId });
let userData;

// add user data to `req.userData` for further use
const userData = await dataAccess.retrieveUsers({ id: userId });
req.userData = userData.user;

if (superUserAccess === false) {
userData = userDoc.user;
userData.roles.super_user = false;
userData.superUserAccess = false;
} else if (superUserAccess === true || superUserAccess === undefined) {
userData = userDoc.user;
if (superUserAccess === true) {
userData.superUserAccess = true;
} else {
userData.superUserAccess = undefined;
}
}
req.userData = userData;
Comment on lines +53 to +70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Middleware tests are missing

return checkRestricted(req, res, next);
} catch (err) {
logger.error(err);
Expand Down
2 changes: 2 additions & 0 deletions routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,19 @@

router.post("/", authorizeAndAuthenticate([ROLES.SUPERUSER], [Services.CRON_JOB_HANDLER]), users.markUnverified);
router.post("/update-in-discord", authenticate, authorizeRoles([SUPERUSER]), users.setInDiscordScript);
router.post("/verify", authenticate, users.verifyUser);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/userId/:userId", users.getUserById);
router.patch("/self", authenticate, userValidator.updateUser, users.updateSelf);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/", userValidator.getUsers, users.getUsers);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/self", authenticate, users.getSelfDetails);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/set-super-user-access", authenticate, users.setSuperUserAccessLimiter);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change the route name to be more descriptive?
as this is a GET request and route name is kind on weird

router.get("/get-super-user-access-status", authenticate, users.getSuperUserAccessStatus);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/isDeveloper", authenticate, users.isDeveloper);
router.get("/isUsernameAvailable/:username", authenticate, users.getUsernameAvailabilty);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/username", authenticate, userValidator.validateGenerateUsernameQuery, users.generateUsername);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/chaincode", authenticate, users.generateChaincode);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/search", userValidator.validateUserQueryParams, users.filterUsers);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/identity-stats", authenticate, authorizeRoles([SUPERUSER]), users.getIdentityStats);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.patch(
"/:userId/update-nickname",
authenticate,
Expand All @@ -33,13 +35,13 @@
users.updateDiscordUserNickname
);
router.get("/:username", users.getUser);
router.get("/:userId/intro", authenticate, authorizeRoles([SUPERUSER]), users.getUserIntro);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.put("/self/intro", authenticate, userValidator.validateJoinData, users.addUserIntro);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/:id/skills", users.getUserSkills);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.get("/:id/badges", getUserBadges);
router.patch(
"/",
authenticate,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
authorizeRoles([SUPERUSER]),
userValidator.validateUsersPatchHandler,
users.usersPatchHandler
Expand All @@ -48,22 +50,22 @@
"/:id/temporary/data",
authenticate,
authorizeRoles([SUPERUSER]),
userValidator.validateUpdateRoles,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
users.updateRoles

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
);

// upload.single('profile') -> multer inmemory storage of file for type multipart/form-data
router.post("/picture", authenticate, checkIsVerifiedDiscord, upload.single("profile"), users.postUserPicture);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.patch(
"/picture/verify/:id",
authenticate,

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
authorizeRoles([SUPERUSER]),
userValidator.validateImageVerificationQuery,
users.verifyUserImage
);
router.get("/picture/:id", authenticate, authorizeRoles([SUPERUSER]), users.getUserImageForVerification);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.patch("/profileURL", authenticate, userValidator.updateProfileURL, users.profileURL);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.patch("/rejectDiff", authenticate, authorizeRoles([SUPERUSER]), users.rejectProfileDiff);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
router.patch("/:userId", authenticate, authorizeRoles([SUPERUSER]), users.updateUser);
router.get("/suggestedUsers/:skillId", authenticate, authorizeRoles([SUPERUSER]), users.getSuggestedUsers);

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
module.exports = router;

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
174 changes: 174 additions & 0 deletions test/integration/users.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,42 @@ describe("Users", function () {
});
});

it("Should return the logged in super_user with modified privilege, super_user : false, based on superUserAccess in jwt token", async function () {
const superUser = userData[4];
const userId = await addUser(superUser);
jwt = authService.generateAuthToken({ userId, superUserAccess: false });
const res = await chai.request(app).get("/users/self").set("cookie", `${cookieName}=${jwt}`);

expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body.roles.super_user).to.be.equal(false);
await cleanDb();
});

it("Should return the logged in super_user with unmodified privileges, super_user : true, based on superUserAccess in jwt token", async function () {
const superUser = userData[4];
const userId = await addUser(superUser);
jwt = authService.generateAuthToken({ userId, superUserAccess: true });
const res = await chai.request(app).get("/users/self").set("cookie", `${cookieName}=${jwt}`);

expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body.roles.super_user).to.be.equal(true);
await cleanDb();
});

it("Should return the logged in super_user as it is if the superUserAccess not found in jwt token", async function () {
const superUser = userData[4];
const userId = await addUser(superUser);
jwt = authService.generateAuthToken({ userId });
const res = await chai.request(app).get("/users/self").set("cookie", `${cookieName}=${jwt}`);

expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body.roles.super_user).to.be.equal(true);
await cleanDb();
});

it("Should return 401 if not logged in", function (done) {
chai
.request(app)
Expand Down Expand Up @@ -2423,4 +2459,142 @@ describe("Users", function () {
});
});
});

describe("GET /get-super-user-access-status", function () {
beforeEach(async function () {
userId = await addUser();
});

afterEach(async function () {
await cleanDb();
});

it("Should return true if the token has superUserAccess role in it, and it's set to be true", function (done) {
jwt = authService.generateAuthToken({ userId, superUserAccess: true });
chai
.request(app)
.get("/users/get-super-user-access-status")
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body).to.have.a.property("currentAccess");
expect(res.body.currentAccess).to.be.equal(true);
return done();
});
});

it("Should return false if the token has superUserAccess role in it, and it's set to be false", function (done) {
jwt = authService.generateAuthToken({ userId, superUserAccess: false });
chai
.request(app)
.get("/users/get-super-user-access-status")
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) {
return done(err);
}

expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body).to.have.property("currentAccess");
expect(res.body.currentAccess).to.be.equal(false);
return done();
});
});

it("Should return a message: 'Super User Access Modifier Not Set!', if superUserAccess not set in jwt token", function (done) {
jwt = authService.generateAuthToken({ userId });
chai
.request(app)
.get("/users/get-super-user-access-status")
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) {
return done(err);
}

expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body).to.not.have.property("currentAccess");
expect(res.body).to.have.property("message");
expect(res.body.currentAccess).to.be.equal(undefined);
expect(res.body.message).to.be.equal("Super User Access Modifier Not Set!");
return done();
});
});
});

describe("GET /set-super-user-access", function () {
beforeEach(async function () {
userId = await addUser();
});

afterEach(async function () {
await cleanDb();
});

it("Should return currentAccess: true, with message: 'Super User Privilege Access Set!'", function (done) {
jwt = authService.generateAuthToken({ userId });
chai
.request(app)
.get("/users/set-super-user-access?value=true")
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body).to.have.a.property("currentAccess");
expect(res.body).to.have.a.property("message");
expect(res.body.currentAccess).to.be.equal(true);
expect(res.body.message).to.be.equal("Super User Privilege Access Set!");
return done();
});
});

it("Should return currentAccess: false, with message: 'Super User Privilege Access Set!'", function (done) {
jwt = authService.generateAuthToken({ userId });
chai
.request(app)
.get("/users/set-super-user-access?value=false")
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res).to.have.status(200);
expect(res.body).to.be.a("object");
expect(res.body).to.have.a.property("currentAccess");
expect(res.body).to.have.a.property("message");
expect(res.body.currentAccess).to.be.equal(false);
expect(res.body.message).to.be.equal("Super User Privilege Access Set!");
return done();
});
});

it("Should return status 400", function (done) {
jwt = authService.generateAuthToken({ userId });
chai
.request(app)
.get("/users/set-super-user-access?value=xyz")
.set("cookie", `${cookieName}=${jwt}`)
.end((err, res) => {
if (err) {
return done(err);
}
expect(res).to.have.status(400);
expect(res.body).to.be.a("object");
expect(res.body).to.not.have.a.property("currentAccess");
expect(res.body).to.have.a.property("message");
expect(res.body.currentAccess).to.be.equal(undefined);
expect(res.body.message).to.be.equal("Wrong value in query param, value can be either true or false");
return done();
});
});
});
});
Loading