-
Notifications
You must be signed in to change notification settings - Fork 280
feat: Implement restricted user status self-update endpoint, preventi… #2549
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validateUserStatus, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validateMassUpdate, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validateGetQueryParams, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validateUserStatusSelf, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = require("../middlewares/validators/userStatus"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndService"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ROLES = require("../constants/roles"); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -24,7 +25,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.get("/", validateGetQueryParams, getUserStatusControllers); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.get("/self", authenticate, getUserStatus); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.get("/:userId", getUserStatus); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.patch("/self", authenticate, validateUserStatus, updateUserStatusController); // this route is being deprecated, please use /users/status/:userId PATCH endpoint instead. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.patch("/self", authenticate, validateUserStatusSelf, updateUserStatusController); // this route is being deprecated, please use /users/status/:userId PATCH endpoint instead. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failureCode scanning / CodeQL Missing rate limiting High
This route handler performs
authorization Error loading related location Loading This route handler performs authorization Error loading related location Loading This route handler performs authorization Error loading related location Loading
Copilot AutofixAI 19 days ago In general, the fix is to introduce rate limiting middleware so that requests to the sensitive route cannot be made arbitrarily fast. In Express, this is commonly done via For this file, the least intrusive and clearest fix is to: (1) import const userStatusSelfLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
});Then change line 28 so that it uses this limiter:
Suggested changeset
2
routes/userStatus.js
package.json
Outside changed files
This fix introduces these dependencies
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.patch("/update", authorizeAndAuthenticate([ROLES.SUPERUSER], [Services.CRON_JOB_HANDLER]), updateAllUserStatus); | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| router.patch( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "/batch", | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| const chai = require("chai"); | ||
| const { expect } = chai; | ||
| const chaiHttp = require("chai-http"); | ||
| const app = require("../../server"); | ||
| const authService = require("../../services/authService"); | ||
| const addUser = require("../utils/addUser"); | ||
| const cleanDb = require("../utils/cleanDb"); | ||
| const { updateUserStatus } = require("../../models/userStatus"); | ||
| const { generateUserStatusData } = require("../fixtures/userStatus/userStatus"); | ||
| const config = require("config"); | ||
| const cookieName = config.get("userToken.cookieName"); | ||
| const firestore = require("../../utils/firestore"); | ||
|
|
||
| chai.use(chaiHttp); | ||
|
|
||
| describe("Restricted PATCH /users/status/self", function () { | ||
| let jwt; | ||
| let userId = ""; | ||
|
|
||
| beforeEach(async function () { | ||
| userId = await addUser(); | ||
| jwt = authService.generateAuthToken({ userId }); | ||
| const initialStatus = generateUserStatusData("OOO", Date.now(), Date.now(), Date.now() + 86400000); | ||
| await updateUserStatus(userId, initialStatus); | ||
| }); | ||
|
|
||
| afterEach(async function () { | ||
| await cleanDb(); | ||
| }); | ||
|
|
||
| it("Should return 400 when trying to update 'state'", async function () { | ||
| const res = await chai | ||
| .request(app) | ||
| .patch("/users/status/self") | ||
| .set("cookie", `${cookieName}=${jwt}`) | ||
| .send({ | ||
| currentStatus: { | ||
| state: "ACTIVE", | ||
| updatedAt: Date.now(), | ||
| from: Date.now(), | ||
| }, | ||
| }); | ||
|
|
||
| expect(res).to.have.status(400); | ||
| expect(res.body.error).to.equal("Bad Request"); | ||
| expect(res.body.message).to.include("Updating 'state' is not allowed via this endpoint"); | ||
| }); | ||
|
|
||
| it("Should return 400 when trying to update 'until'", async function () { | ||
| const res = await chai | ||
| .request(app) | ||
| .patch("/users/status/self") | ||
| .set("cookie", `${cookieName}=${jwt}`) | ||
| .send({ | ||
| currentStatus: { | ||
| until: Date.now() + 100000, | ||
| updatedAt: Date.now(), | ||
| from: Date.now(), | ||
| }, | ||
| }); | ||
|
|
||
| expect(res).to.have.status(400); | ||
| expect(res.body.error).to.equal("Bad Request"); | ||
| expect(res.body.message).to.include("Updating 'until' is not allowed via this endpoint"); | ||
| }); | ||
|
|
||
| it("Should allow updating other fields (message) and preserve state/until", async function () { | ||
| // Current state is OOO. We update message. | ||
| const newMessage = "Updated message via restricted endpoint"; | ||
| const now = Date.now(); | ||
|
|
||
| const res = await chai | ||
| .request(app) | ||
| .patch("/users/status/self") | ||
| .set("cookie", `${cookieName}=${jwt}`) | ||
| .send({ | ||
| currentStatus: { | ||
| message: newMessage, | ||
| updatedAt: now, | ||
| from: now, | ||
| }, | ||
| }); | ||
|
|
||
| expect(res).to.have.status(200); | ||
| expect(res.body.message).to.equal("User Status updated successfully."); | ||
|
|
||
| // Verify persistence | ||
| const doc = await firestore.collection("usersStatus").where("userId", "==", userId).get(); | ||
| const data = doc.docs[0].data(); | ||
|
|
||
| expect(data.currentStatus.state).to.equal("OOO"); | ||
| expect(data.currentStatus.message).to.equal(newMessage); | ||
| expect(data.currentStatus.until).to.not.equal(undefined); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 280
🏁 Script executed:
# Read the full userStatus.js file to see the context cat -n middlewares/validators/userStatus.jsRepository: RealDevSquad/website-backend
Length of output: 6836
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 54
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 220
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 8153
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 2586
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 828
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 54
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 3818
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 725
🏁 Script executed:
Repository: RealDevSquad/website-backend
Length of output: 1447
Address message validation inconsistency between endpoints.
The
selfStatusSchema(line 103) allows clearing the message with.allow(""), but the primary validation invalidateUserStatusData(lines 37-59) enforces that OOO status lasting more than 3 days requires a message. A user can set long OOO with a required message via the main endpoint, then clear it via the/selfendpoint, creating an inconsistency.Either disallow empty strings in the self endpoint by changing line 103 to
message: Joi.string().min(1).optional(), or enforce the message requirement in the controller after the user's stored status is checked.🤖 Prompt for AI Agents