tududi/backend/services/permissionsService.js
Chris f5c3bf7e69
Prevent admin from seeing all tasks (#477)
* Prevent admin from seeing all tasks

* fixup! Prevent admin from seeing all tasks
2025-11-04 09:40:33 +02:00

195 lines
6.5 KiB
JavaScript

const { Op } = require('sequelize');
const { Project, Task, Note, Permission } = require('../models');
const { isAdmin } = require('./rolesService');
const ACCESS = { NONE: 'none', RO: 'ro', RW: 'rw', ADMIN: 'admin' };
async function getSharedUidsForUser(resourceType, userId) {
const rows = await Permission.findAll({
where: { user_id: userId, resource_type: resourceType },
attributes: ['resource_uid'],
raw: true,
});
const set = new Set(rows.map((r) => r.resource_uid));
return Array.from(set);
}
async function getAccess(userId, resourceType, resourceUid) {
if (await isAdmin(userId)) return ACCESS.ADMIN;
// ownership via model
if (resourceType === 'project') {
const proj = await Project.findOne({
where: { uid: resourceUid },
attributes: ['user_id'],
raw: true,
});
if (!proj) return ACCESS.NONE;
if (proj.user_id === userId) return ACCESS.RW;
} else if (resourceType === 'task') {
const t = await Task.findOne({
where: { uid: resourceUid },
attributes: ['user_id', 'project_id'],
raw: true,
});
if (!t) return ACCESS.NONE;
if (t.user_id === userId) return ACCESS.RW;
// Check if user has access through the parent project
if (t.project_id) {
const project = await Project.findOne({
where: { id: t.project_id },
attributes: ['uid'],
raw: true,
});
if (project) {
const projectAccess = await getAccess(
userId,
'project',
project.uid
);
if (projectAccess !== ACCESS.NONE) {
return projectAccess; // Inherit access from project
}
}
}
} else if (resourceType === 'note') {
const n = await Note.findOne({
where: { uid: resourceUid },
attributes: ['user_id', 'project_id'],
raw: true,
});
if (!n) return ACCESS.NONE;
if (n.user_id === userId) return ACCESS.RW;
// Check if user has access through the parent project
if (n.project_id) {
const project = await Project.findOne({
where: { id: n.project_id },
attributes: ['uid'],
raw: true,
});
if (project) {
const projectAccess = await getAccess(
userId,
'project',
project.uid
);
if (projectAccess !== ACCESS.NONE) {
return projectAccess; // Inherit access from project
}
}
}
}
// shared
const perm = await Permission.findOne({
where: {
user_id: userId,
resource_type: resourceType,
resource_uid: resourceUid,
},
attributes: ['access_level'],
raw: true,
});
return perm ? perm.access_level : ACCESS.NONE;
}
async function ownershipOrPermissionWhere(resourceType, userId) {
// Build WHERE clause for resource queries based on ownership and sharing permissions
// Note: isAdmin expects a UID, but we might receive a numeric ID
// Get the user's UID if we received a numeric ID
let userUid = userId;
if (typeof userId === 'number' || !isNaN(parseInt(userId))) {
const { User } = require('../models');
const user = await User.findByPk(userId, {
attributes: ['uid', 'email'],
});
if (user) {
userUid = user.uid;
console.log(
`[PERMISSIONS DEBUG] User lookup: ID=${userId}, UID=${userUid}, Email=${user.email}`
);
}
}
const isUserAdmin = await isAdmin(userUid);
console.log(
`[PERMISSIONS DEBUG] Resource: ${resourceType}, UserId: ${userId}, IsAdmin: ${isUserAdmin}`
);
// Admin users should NOT see all resources automatically
// They should only see their own resources and shared resources, like regular users
// If admin-level system-wide visibility is needed, it should be via dedicated admin endpoints
// if (isUserAdmin) {
// console.log(
// `[PERMISSIONS DEBUG] User is admin, returning empty where clause (all resources visible)`
// );
// return {}; // empty where clause = no restriction
// }
const sharedUids = await getSharedUidsForUser(resourceType, userId);
console.log(
`[PERMISSIONS DEBUG] Shared ${resourceType} UIDs for user ${userId}:`,
sharedUids
);
// For tasks and notes, also include items from shared projects
if (resourceType === 'task' || resourceType === 'note') {
const sharedProjectUids = await getSharedUidsForUser('project', userId);
console.log(
`[PERMISSIONS DEBUG] Shared project UIDs for user ${userId}:`,
sharedProjectUids
);
// Get the project IDs for shared projects
let sharedProjectIds = [];
if (sharedProjectUids.length > 0) {
const projects = await Project.findAll({
where: { uid: { [Op.in]: sharedProjectUids } },
attributes: ['id'],
raw: true,
});
sharedProjectIds = projects.map((p) => p.id);
console.log(
`[PERMISSIONS DEBUG] Shared project IDs for user ${userId}:`,
sharedProjectIds
);
}
const conditions = [
{ user_id: userId }, // Items owned by user
];
if (sharedUids.length > 0) {
conditions.push({ uid: { [Op.in]: sharedUids } }); // Items directly shared with user
}
if (sharedProjectIds.length > 0) {
conditions.push({ project_id: { [Op.in]: sharedProjectIds } }); // Items in shared projects
}
console.log(
`[PERMISSIONS DEBUG] Final where conditions for ${resourceType}:`,
JSON.stringify(conditions)
);
return { [Op.or]: conditions };
}
// For other resource types (projects, etc.), use the original logic
return {
[Op.or]: [
{ user_id: userId },
sharedUids.length
? { uid: { [Op.in]: sharedUids } }
: { uid: null },
],
};
}
module.exports = {
ACCESS,
getAccess,
ownershipOrPermissionWhere,
getSharedUidsForUser,
};