feat: Add creator info, team management for companies, and member access control
- Add creator info (username) to companies, projects, and notes responses - Add company_users table for team management on companies - Add resourceAccessMiddleware for member access control - Members can only see resources they are directly assigned to - Companies, projects, and todos are now filtered by user assignments - Add personal contacts feature 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
160
src/middlewares/auth/resourceAccessMiddleware.js
Normal file
160
src/middlewares/auth/resourceAccessMiddleware.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import { db } from '../../config/database.js';
|
||||
import { companyUsers, projectUsers, todoUsers } from '../../db/schema.js';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
|
||||
/**
|
||||
* Univerzálny middleware pre kontrolu prístupu k resources
|
||||
* Admin má prístup vždy, member len ak je priradený k resource
|
||||
*
|
||||
* Použitie:
|
||||
* checkResourceAccess('company', 'companyId') // pre /companies/:companyId
|
||||
* checkResourceAccess('project', 'projectId') // pre /projects/:projectId
|
||||
* checkResourceAccess('todo', 'todoId') // pre /todos/:todoId
|
||||
*/
|
||||
|
||||
// Mapovanie resource typu na junction tabuľku a stĺpce
|
||||
const resourceConfig = {
|
||||
company: {
|
||||
table: companyUsers,
|
||||
resourceIdColumn: 'companyId',
|
||||
},
|
||||
project: {
|
||||
table: projectUsers,
|
||||
resourceIdColumn: 'projectId',
|
||||
},
|
||||
todo: {
|
||||
table: todoUsers,
|
||||
resourceIdColumn: 'todoId',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Skontroluje či user má prístup k danému resource
|
||||
* @param {string} resourceType - Typ resource ('company', 'project', atď.)
|
||||
* @param {string} userId - ID používateľa
|
||||
* @param {string} resourceId - ID resource
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export const hasAccessToResource = async (resourceType, userId, resourceId) => {
|
||||
const config = resourceConfig[resourceType];
|
||||
if (!config) {
|
||||
throw new Error(`Unknown resource type: ${resourceType}`);
|
||||
}
|
||||
|
||||
const { table, resourceIdColumn } = config;
|
||||
|
||||
const [assignment] = await db
|
||||
.select()
|
||||
.from(table)
|
||||
.where(and(
|
||||
eq(table[resourceIdColumn], resourceId),
|
||||
eq(table.userId, userId)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
return !!assignment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware factory pre kontrolu prístupu k resource
|
||||
* @param {string} resourceType - Typ resource ('company', 'project')
|
||||
* @param {string} paramName - Názov parametra v URL (napr. 'companyId', 'projectId')
|
||||
*/
|
||||
export const checkResourceAccess = (resourceType, paramName) => {
|
||||
return async (req, res, next) => {
|
||||
// Skontroluj či je user autentifikovaný
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Musíte byť prihlásený',
|
||||
statusCode: 401,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Admin má prístup vždy
|
||||
if (req.user.role === 'admin') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const resourceId = req.params[paramName];
|
||||
if (!resourceId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: `Chýba parameter ${paramName}`,
|
||||
statusCode: 400,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const hasAccess = await hasAccessToResource(resourceType, req.user.id, resourceId);
|
||||
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Nemáte prístup k tomuto zdroju',
|
||||
statusCode: 403,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Resource access check error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Chyba pri overovaní prístupu',
|
||||
statusCode: 500,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper funkcie pre bežné prípady
|
||||
*/
|
||||
export const checkCompanyAccess = checkResourceAccess('company', 'companyId');
|
||||
export const checkProjectAccess = checkResourceAccess('project', 'projectId');
|
||||
export const checkTodoAccess = checkResourceAccess('todo', 'todoId');
|
||||
|
||||
/**
|
||||
* Získa zoznam resource IDs ku ktorým má user prístup
|
||||
* Užitočné pre filtrovanie v service vrstvách
|
||||
* @param {string} resourceType - Typ resource ('company', 'project')
|
||||
* @param {string} userId - ID používateľa
|
||||
* @returns {Promise<string[]>} - Zoznam resource IDs
|
||||
*/
|
||||
export const getAccessibleResourceIds = async (resourceType, userId) => {
|
||||
const config = resourceConfig[resourceType];
|
||||
if (!config) {
|
||||
throw new Error(`Unknown resource type: ${resourceType}`);
|
||||
}
|
||||
|
||||
const { table, resourceIdColumn } = config;
|
||||
|
||||
const assignments = await db
|
||||
.select({ resourceId: table[resourceIdColumn] })
|
||||
.from(table)
|
||||
.where(eq(table.userId, userId));
|
||||
|
||||
return assignments.map(a => a.resourceId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Skontroluje prístup a vráti boolean (pre použitie v services)
|
||||
* @param {string} resourceType
|
||||
* @param {string} userId
|
||||
* @param {string} resourceId
|
||||
* @param {string} userRole
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export const canAccessResource = async (resourceType, userId, resourceId, userRole) => {
|
||||
if (userRole === 'admin') return true;
|
||||
return hasAccessToResource(resourceType, userId, resourceId);
|
||||
};
|
||||
Reference in New Issue
Block a user