This commit is contained in:
Flatlogic Bot 2026-05-05 23:19:25 +00:00
parent 1fd0a1b094
commit e20241ff74
15 changed files with 243 additions and 32 deletions

View File

@ -3,6 +3,7 @@ const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const { isRestrictedPayrollUser } = require('../../security/payrollAccess');
@ -205,12 +206,17 @@ module.exports = class Employee_pay_typesDBApi {
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const currentUser = options?.currentUser;
const employee_pay_types = await db.employee_pay_types.findOne(
{ where },
{ transaction },
);
if (employee_pay_types && isRestrictedPayrollUser(currentUser) && employee_pay_types.employeeId !== currentUser.id) {
return null;
}
if (!employee_pay_types) {
return employee_pay_types;
}
@ -249,6 +255,8 @@ module.exports = class Employee_pay_typesDBApi {
filter,
options
) {
const currentUser = options?.currentUser;
const effectiveEmployeeFilter = isRestrictedPayrollUser(currentUser) ? currentUser.id : filter.employee;
const limit = filter.limit || 0;
let offset = 0;
let where = {};
@ -270,12 +278,12 @@ module.exports = class Employee_pay_typesDBApi {
model: db.users,
as: 'employee',
where: filter.employee ? {
where: effectiveEmployeeFilter ? {
[Op.or]: [
{ id: { [Op.in]: filter.employee.split('|').map(term => Utils.uuid(term)) } },
{ id: { [Op.in]: effectiveEmployeeFilter.split('|').map(term => Utils.uuid(term)) } },
{
firstName: {
[Op.or]: filter.employee.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
[Op.or]: effectiveEmployeeFilter.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
}
},
]
@ -447,7 +455,8 @@ module.exports = class Employee_pay_typesDBApi {
}
}
static async findAllAutocomplete(query, limit, offset, ) {
static async findAllAutocomplete(query, limit, offset, options) {
const currentUser = options?.currentUser;
let where = {};
@ -465,6 +474,13 @@ module.exports = class Employee_pay_typesDBApi {
};
}
if (isRestrictedPayrollUser(currentUser)) {
where = {
...where,
employeeId: currentUser.id,
};
}
const records = await db.employee_pay_types.findAll({
attributes: [ 'id', 'active' ],
where,

View File

@ -3,6 +3,7 @@ const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const { isRestrictedPayrollUser } = require('../../security/payrollAccess');
@ -320,12 +321,17 @@ module.exports = class Job_logsDBApi {
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const currentUser = options?.currentUser;
const job_logs = await db.job_logs.findOne(
{ where },
{ transaction },
);
if (job_logs && isRestrictedPayrollUser(currentUser) && job_logs.employeeId !== currentUser.id) {
return null;
}
if (!job_logs) {
return job_logs;
}
@ -382,6 +388,7 @@ module.exports = class Job_logsDBApi {
filter,
options
) {
const currentUser = options?.currentUser;
const limit = filter.limit || 0;
let offset = 0;
let where = {};
@ -486,6 +493,13 @@ module.exports = class Job_logsDBApi {
];
if (isRestrictedPayrollUser(currentUser)) {
where = {
...where,
employeeId: currentUser.id,
};
}
if (filter) {
if (filter.id) {
where = {
@ -752,7 +766,8 @@ module.exports = class Job_logsDBApi {
}
}
static async findAllAutocomplete(query, limit, offset, ) {
static async findAllAutocomplete(query, limit, offset, options) {
const currentUser = options?.currentUser;
let where = {};
@ -770,6 +785,13 @@ module.exports = class Job_logsDBApi {
};
}
if (isRestrictedPayrollUser(currentUser)) {
where = {
...where,
employeeId: currentUser.id,
};
}
const records = await db.job_logs.findAll({
attributes: [ 'id', 'job_address' ],
where,

View File

@ -3,6 +3,7 @@ const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const { isRestrictedPayrollUser } = require('../../security/payrollAccess');
@ -218,9 +219,20 @@ module.exports = class Pay_typesDBApi {
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const currentUser = options?.currentUser;
const accessInclude = isRestrictedPayrollUser(currentUser) ? [{
model: db.employee_pay_types,
as: 'employee_pay_types_pay_type',
required: true,
attributes: [],
where: {
employeeId: currentUser.id,
active: true,
},
}] : undefined;
const pay_types = await db.pay_types.findOne(
{ where },
{ where, include: accessInclude },
{ transaction },
);
@ -238,13 +250,15 @@ module.exports = class Pay_typesDBApi {
output.employee_pay_types_pay_type = await pay_types.getEmployee_pay_types_pay_type({
transaction
transaction,
where: isRestrictedPayrollUser(currentUser) ? { employeeId: currentUser.id, active: true } : undefined,
});
output.job_logs_pay_type = await pay_types.getJob_logs_pay_type({
transaction
transaction,
where: isRestrictedPayrollUser(currentUser) ? { employeeId: currentUser.id } : undefined,
});
@ -260,6 +274,7 @@ module.exports = class Pay_typesDBApi {
filter,
options
) {
const currentUser = options?.currentUser;
const limit = filter.limit || 0;
let offset = 0;
let where = {};
@ -281,6 +296,22 @@ module.exports = class Pay_typesDBApi {
];
if (isRestrictedPayrollUser(currentUser)) {
include = [
{
model: db.employee_pay_types,
as: 'employee_pay_types_pay_type',
required: true,
attributes: [],
where: {
employeeId: currentUser.id,
active: true,
},
},
...include,
];
}
if (filter) {
if (filter.id) {
where = {
@ -449,7 +480,8 @@ module.exports = class Pay_typesDBApi {
}
}
static async findAllAutocomplete(query, limit, offset, ) {
static async findAllAutocomplete(query, limit, offset, options) {
const currentUser = options?.currentUser;
let where = {};
@ -470,6 +502,16 @@ module.exports = class Pay_typesDBApi {
const records = await db.pay_types.findAll({
attributes: [ 'id', 'name' ],
where,
include: isRestrictedPayrollUser(currentUser) ? [{
model: db.employee_pay_types,
as: 'employee_pay_types_pay_type',
required: true,
attributes: [],
where: {
employeeId: currentUser.id,
active: true,
},
}] : undefined,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']],

View File

@ -3,6 +3,7 @@ const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
const { isRestrictedPayrollUser } = require('../../security/payrollAccess');
const bcrypt = require('bcrypt');
const config = require('../../config');
@ -387,9 +388,19 @@ module.exports = class UsersDBApi {
static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined;
const currentUser = options?.currentUser;
const effectiveWhere = { ...(where || {}) };
if (isRestrictedPayrollUser(currentUser)) {
if (effectiveWhere.id && Utils.uuid(effectiveWhere.id) !== currentUser.id) {
return null;
}
effectiveWhere.id = currentUser.id;
}
const users = await db.users.findOne(
{ where },
{ where: effectiveWhere },
{ transaction },
);
@ -408,6 +419,7 @@ module.exports = class UsersDBApi {
output.employee_pay_types_employee = await users.getEmployee_pay_types_employee({
transaction,
where: isRestrictedPayrollUser(currentUser) ? { employeeId: currentUser.id, active: true } : undefined,
include: [
{
model: db.pay_types,
@ -460,6 +472,7 @@ module.exports = class UsersDBApi {
filter,
options
) {
const currentUser = options?.currentUser;
const limit = filter.limit || 0;
let offset = 0;
let where = {};
@ -524,6 +537,13 @@ module.exports = class UsersDBApi {
];
if (isRestrictedPayrollUser(currentUser)) {
where = {
...where,
id: currentUser.id,
};
}
if (filter) {
if (filter.id) {
where = {
@ -783,7 +803,8 @@ module.exports = class UsersDBApi {
}
}
static async findAllAutocomplete(query, limit, offset, ) {
static async findAllAutocomplete(query, limit, offset, options) {
const currentUser = options?.currentUser;
let where = {};
@ -801,6 +822,13 @@ module.exports = class UsersDBApi {
};
}
if (isRestrictedPayrollUser(currentUser)) {
where = {
...where,
id: currentUser.id,
};
}
const records = await db.users.findAll({
attributes: [ 'id', 'firstName' ],
where,

View File

@ -335,7 +335,6 @@ router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Employee_pay_typesDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
@ -373,7 +372,7 @@ router.get('/autocomplete', async (req, res) => {
req.query.query,
req.query.limit,
req.query.offset,
{ currentUser: req.currentUser },
);
res.status(200).send(payload);
@ -414,6 +413,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Employee_pay_typesDBApi.findBy(
{ id: req.params.id },
{ currentUser: req.currentUser },
);

View File

@ -355,7 +355,6 @@ router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Job_logsDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
@ -393,7 +392,7 @@ router.get('/autocomplete', async (req, res) => {
req.query.query,
req.query.limit,
req.query.offset,
{ currentUser: req.currentUser },
);
res.status(200).send(payload);
@ -434,6 +433,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Job_logsDBApi.findBy(
{ id: req.params.id },
{ currentUser: req.currentUser },
);

View File

@ -348,7 +348,6 @@ router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await Pay_typesDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
@ -386,7 +385,7 @@ router.get('/autocomplete', async (req, res) => {
req.query.query,
req.query.limit,
req.query.offset,
{ currentUser: req.currentUser },
);
res.status(200).send(payload);
@ -427,6 +426,7 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Pay_typesDBApi.findBy(
{ id: req.params.id },
{ currentUser: req.currentUser },
);

View File

@ -347,7 +347,6 @@ router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser;
const payload = await UsersDBApi.findAll(
req.query,
null,
{ countOnly: true, currentUser }
);
@ -385,7 +384,7 @@ router.get('/autocomplete', async (req, res) => {
req.query.query,
req.query.limit,
req.query.offset,
{ currentUser: req.currentUser },
);
res.status(200).send(payload);
@ -426,11 +425,12 @@ router.get('/autocomplete', async (req, res) => {
router.get('/:id', wrapAsync(async (req, res) => {
const payload = await UsersDBApi.findBy(
{ id: req.params.id },
{ currentUser: req.currentUser },
);
if (payload) {
delete payload.password;
}
res.status(200).send(payload);
}));

View File

@ -0,0 +1,19 @@
const PRIVILEGED_PAYROLL_ROLE_NAMES = new Set([
'Administrator',
'System Owner',
'Payroll Manager',
'Operations Manager',
]);
function isRestrictedPayrollUser(currentUser) {
if (!currentUser?.id) {
return false;
}
return !PRIVILEGED_PAYROLL_ROLE_NAMES.has(currentUser?.app_role?.name);
}
module.exports = {
PRIVILEGED_PAYROLL_ROLE_NAMES,
isRestrictedPayrollUser,
};

View File

@ -7,8 +7,28 @@ const csv = require('csv-parser');
const axios = require('axios');
const config = require('../config');
const stream = require('stream');
const { isRestrictedPayrollUser } = require('../security/payrollAccess');
module.exports = class Job_logsService {
static async ensureRestrictedPayTypeAccess(payTypeId, currentUser, transaction) {
if (!isRestrictedPayrollUser(currentUser) || !payTypeId) {
return;
}
const assignment = await db.employee_pay_types.findOne({
where: {
employeeId: currentUser.id,
pay_typeId: payTypeId,
active: true,
},
transaction,
});
if (!assignment) {
throw new ValidationError('errors.forbidden.message');
}
}
static async create(data, currentUser) {
const transaction = await db.sequelize.transaction();
try {
@ -23,8 +43,16 @@ module.exports = class Job_logsService {
customerId = customer.id;
}
const jobPayload = { ...data, customer: customerId };
if (isRestrictedPayrollUser(currentUser)) {
jobPayload.employee = currentUser.id;
}
await Job_logsService.ensureRestrictedPayTypeAccess(jobPayload.pay_type, currentUser, transaction);
const createdJob = await Job_logsDBApi.create(
{ ...data, customer: customerId },
jobPayload,
{
currentUser,
transaction,
@ -93,7 +121,10 @@ module.exports = class Job_logsService {
try {
let job_logs = await Job_logsDBApi.findBy(
{id},
{transaction},
{
transaction,
currentUser,
},
);
if (!job_logs) {
@ -101,6 +132,10 @@ module.exports = class Job_logsService {
'job_logsNotFound',
);
}
if (isRestrictedPayrollUser(currentUser) && job_logs.employeeId !== currentUser.id) {
throw new ValidationError('errors.forbidden.message');
}
let customerId = data.customer;
@ -113,9 +148,21 @@ module.exports = class Job_logsService {
customerId = customer.id;
}
const jobPayload = { ...data, customer: customerId };
if (isRestrictedPayrollUser(currentUser)) {
jobPayload.employee = currentUser.id;
}
await Job_logsService.ensureRestrictedPayTypeAccess(
jobPayload.pay_type !== undefined ? jobPayload.pay_type : job_logs.pay_typeId,
currentUser,
transaction,
);
const updatedJob_logs = await Job_logsDBApi.update(
id,
{ ...data, customer: customerId },
jobPayload,
{
currentUser,
transaction,

View File

@ -3,6 +3,7 @@ import { MenuAsideItem } from '../interfaces'
import AsideMenuItem from './AsideMenuItem'
import {useAppSelector} from "../stores/hooks";
import {hasPermission} from "../helpers/userPermissions";
import { isBlockedWorkerRoute, isRestrictedPayrollUser } from '../helpers/accessControl';
type Props = {
menu: MenuAsideItem[]
@ -18,9 +19,10 @@ export default function AsideMenuList({ menu, isDropdownList = false, className
return (
<ul className={className}>
{menu.map((item, index) => {
if (!hasPermission(currentUser, item.permissions)) return null;
if (!hasPermission(currentUser, item.permissions)) return null;
if (isRestrictedPayrollUser(currentUser) && isBlockedWorkerRoute(item.href)) return null;
return (
<div key={index}>
<AsideMenuItem

View File

@ -0,0 +1,24 @@
const PRIVILEGED_PAYROLL_ROLE_NAMES = new Set([
'Administrator',
'System Owner',
'Payroll Manager',
'Operations Manager',
]);
const BLOCKED_WORKER_ROUTE_PREFIXES = ['/users', '/pay_types', '/employee_pay_types'];
export function isRestrictedPayrollUser(user: any) {
if (!user?.id) {
return false;
}
return !PRIVILEGED_PAYROLL_ROLE_NAMES.has(user?.app_role?.name);
}
export function isBlockedWorkerRoute(pathname?: string) {
if (!pathname) {
return false;
}
return BLOCKED_WORKER_ROUTE_PREFIXES.some((prefix) => pathname.startsWith(prefix));
}

View File

@ -14,6 +14,7 @@ import { useRouter } from 'next/router'
import {findMe, logoutUser} from "../stores/authSlice";
import {hasPermission} from "../helpers/userPermissions";
import { isBlockedWorkerRoute, isRestrictedPayrollUser } from '../helpers/accessControl';
type Props = {
@ -61,7 +62,15 @@ export default function LayoutAuthenticated({
if (!permission || !currentUser) return;
if (!hasPermission(currentUser, permission)) router.push('/error');
}, [currentUser, permission]);
}, [currentUser, permission, router]);
useEffect(() => {
if (!currentUser) return;
if (isRestrictedPayrollUser(currentUser) && isBlockedWorkerRoute(router.pathname)) {
router.replace('/my-logs');
}
}, [currentUser, router, router.pathname]);
const darkMode = useAppSelector((state) => state.style.darkMode)

View File

@ -11,6 +11,7 @@ import { getPageTitle } from '../config'
import Link from "next/link";
import { hasPermission } from "../helpers/userPermissions";
import { isRestrictedPayrollUser } from '../helpers/accessControl';
import { fetchWidgets } from '../stores/roles/rolesSlice';
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
@ -157,7 +158,7 @@ const Dashboard = () => {
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
{!isRestrictedPayrollUser(currentUser) && hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
@ -297,7 +298,7 @@ const Dashboard = () => {
</div>
</Link>}
{hasPermission(currentUser, 'READ_PAY_TYPES') && <Link href={'/pay_types/pay_types-list'}>
{!isRestrictedPayrollUser(currentUser) && hasPermission(currentUser, 'READ_PAY_TYPES') && <Link href={'/pay_types/pay_types-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
@ -325,7 +326,7 @@ const Dashboard = () => {
</div>
</Link>}
{hasPermission(currentUser, 'READ_EMPLOYEE_PAY_TYPES') && <Link href={'/employee_pay_types/employee_pay_types-list'}>
{!isRestrictedPayrollUser(currentUser) && hasPermission(currentUser, 'READ_EMPLOYEE_PAY_TYPES') && <Link href={'/employee_pay_types/employee_pay_types-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>

File diff suppressed because one or more lines are too long