-
Notifications
You must be signed in to change notification settings - Fork 290
Description
Summary
The belongsToWorkspace middleware in users/index.ts validates that the requesting user is a member of the workspace in the URL path, but does NOT verify that the target userId (also in the URL path) belongs to that same workspace. This allows a workspace admin to reset passwords for users in other workspaces.
Affected Endpoint
POST /api/v1/workspaces/:workspaceId/users/:userId/reset-password
File: apps/api/src/v1/workspaces/workspace/users/user.ts, lines 169-210
The handler fetches the target user by raw ID without workspace scoping:
const user = await prisma().user.findUnique({
where: { id: userId }, // only checks userId, NOT workspace membership
select: { email: true },
});
// ... generates new password and updates userMiddleware Gap
File: apps/api/src/v1/workspaces/workspace/users/index.ts, lines 100-119
async function belongsToWorkspace(req, res, next) {
const workspaceId = getParam(req, 'workspaceId')
const userId = getParam(req, 'userId')
const uw = req.session.userWorkspaces[workspaceId]
if (!uw) {
res.status(403).end()
return
}
next() // never checks if :userId belongs to this workspace
}Secure Pattern (for comparison)
The PUT / (update user) endpoint correctly uses compound workspace scoping:
const user = await prisma().userWorkspace.update({
where: { userId_workspaceId: { userId, workspaceId } }, // secure
...
})Also Affected
DELETE /:componentId/instances/:blockId in components.ts - deletes by blockId alone without workspace ownership check (while DELETE /:componentId correctly verifies component.document.workspaceId !== workspaceId).
Recommended Fix
Add workspace membership verification for the target userId:
const memberCheck = await prisma().userWorkspace.findUnique({
where: { userId_workspaceId: { userId, workspaceId } }
});
if (!memberCheck) return res.sendStatus(404);Reported responsibly. Happy to assist with fixes.