refactor: Move audit logging from controllers into services
Add auditContext parameter to service mutating functions. Services now
call audit log functions internally when auditContext is provided.
Controllers pass { userId, ipAddress, userAgent } and no longer import
audit service or fetch extra data for audit purposes.
Files modified:
- 10 service files: added audit imports and auditContext parameter
- 9 controller files: removed audit imports and calls
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import * as adminService from '../services/admin.service.js';
|
import * as adminService from '../services/admin.service.js';
|
||||||
import * as statusService from '../services/status.service.js';
|
import * as statusService from '../services/status.service.js';
|
||||||
import { logUserCreation, logRoleChange, logUserDeleted } from '../services/audit.service.js';
|
|
||||||
import { triggerEventNotifications } from '../cron/index.js';
|
import { triggerEventNotifications } from '../cron/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,17 +20,8 @@ export const createUser = async (req, res, next) => {
|
|||||||
lastName,
|
lastName,
|
||||||
role,
|
role,
|
||||||
email,
|
email,
|
||||||
emailPassword
|
emailPassword,
|
||||||
);
|
{ userId: adminId, ipAddress, userAgent }
|
||||||
|
|
||||||
// Log user creation
|
|
||||||
await logUserCreation(
|
|
||||||
adminId,
|
|
||||||
result.user.id,
|
|
||||||
username,
|
|
||||||
result.user.role,
|
|
||||||
ipAddress,
|
|
||||||
userAgent
|
|
||||||
);
|
);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
@@ -110,10 +100,7 @@ export const changeUserRole = async (req, res, next) => {
|
|||||||
const userAgent = req.headers['user-agent'];
|
const userAgent = req.headers['user-agent'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await adminService.changeUserRole(userId, role);
|
const result = await adminService.changeUserRole(userId, role, { userId: adminId, ipAddress, userAgent });
|
||||||
|
|
||||||
// Log role change
|
|
||||||
await logRoleChange(adminId, userId, result.oldRole, result.newRole, ipAddress, userAgent);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -177,13 +164,7 @@ export const deleteUser = async (req, res, next) => {
|
|||||||
const userAgent = req.headers['user-agent'];
|
const userAgent = req.headers['user-agent'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get user info before deletion for audit
|
const result = await adminService.deleteUser(userId, { userId: adminId, ipAddress, userAgent });
|
||||||
const userToDelete = await adminService.getUserById(userId);
|
|
||||||
|
|
||||||
const result = await adminService.deleteUser(userId);
|
|
||||||
|
|
||||||
// Log user deletion
|
|
||||||
await logUserDeleted(adminId, userId, userToDelete.username, ipAddress, userAgent);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import * as authService from '../services/auth.service.js';
|
import * as authService from '../services/auth.service.js';
|
||||||
import {
|
|
||||||
logLoginAttempt,
|
|
||||||
logPasswordChange,
|
|
||||||
logEmailLink,
|
|
||||||
logLogin,
|
|
||||||
logLogout,
|
|
||||||
} from '../services/audit.service.js';
|
|
||||||
import { verifyRefreshToken, generateAccessToken } from '../utils/jwt.js';
|
import { verifyRefreshToken, generateAccessToken } from '../utils/jwt.js';
|
||||||
import { getUserById } from '../services/auth.service.js';
|
import { getUserById } from '../services/auth.service.js';
|
||||||
|
|
||||||
@@ -26,10 +19,6 @@ export const login = async (req, res, next) => {
|
|||||||
userAgent
|
userAgent
|
||||||
);
|
);
|
||||||
|
|
||||||
// Log successful login
|
|
||||||
await logLoginAttempt(username, true, ipAddress, userAgent);
|
|
||||||
await logLogin(result.user.id, username, ipAddress, userAgent);
|
|
||||||
|
|
||||||
// Nastav cookie s access tokenom (httpOnly, secure)
|
// Nastav cookie s access tokenom (httpOnly, secure)
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
res.cookie('accessToken', result.tokens.accessToken, {
|
res.cookie('accessToken', result.tokens.accessToken, {
|
||||||
@@ -60,9 +49,6 @@ export const login = async (req, res, next) => {
|
|||||||
message: 'Prihlásenie úspešné',
|
message: 'Prihlásenie úspešné',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log failed login
|
|
||||||
await logLoginAttempt(username, false, ipAddress, userAgent, error.message);
|
|
||||||
|
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -141,10 +127,7 @@ export const setPassword = async (req, res, next) => {
|
|||||||
const userAgent = req.headers['user-agent'];
|
const userAgent = req.headers['user-agent'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await authService.setNewPassword(userId, newPassword);
|
const result = await authService.setNewPassword(userId, newPassword, { userId, ipAddress, userAgent });
|
||||||
|
|
||||||
// Log password change
|
|
||||||
await logPasswordChange(userId, ipAddress, userAgent);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -168,10 +151,7 @@ export const linkEmail = async (req, res, next) => {
|
|||||||
const userAgent = req.headers['user-agent'];
|
const userAgent = req.headers['user-agent'];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await authService.linkEmail(userId, email, emailPassword);
|
const result = await authService.linkEmail(userId, email, emailPassword, { userId, ipAddress, userAgent });
|
||||||
|
|
||||||
// Log email link
|
|
||||||
await logEmailLink(userId, email, ipAddress, userAgent);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -218,10 +198,7 @@ export const logout = async (req, res, next) => {
|
|||||||
const ipAddress = req.ip || req.connection.remoteAddress;
|
const ipAddress = req.ip || req.connection.remoteAddress;
|
||||||
const userAgent = req.headers['user-agent'];
|
const userAgent = req.headers['user-agent'];
|
||||||
|
|
||||||
// Log logout event
|
const result = await authService.logout({ userId, ipAddress, userAgent });
|
||||||
await logLogout(userId, ipAddress, userAgent);
|
|
||||||
|
|
||||||
const result = await authService.logout();
|
|
||||||
|
|
||||||
// Vymaž cookies
|
// Vymaž cookies
|
||||||
res.clearCookie('accessToken');
|
res.clearCookie('accessToken');
|
||||||
|
|||||||
@@ -2,16 +2,6 @@ import * as companyService from '../services/company.service.js';
|
|||||||
import * as noteService from '../services/note.service.js';
|
import * as noteService from '../services/note.service.js';
|
||||||
import * as companyReminderService from '../services/company-reminder.service.js';
|
import * as companyReminderService from '../services/company-reminder.service.js';
|
||||||
import * as companyEmailService from '../services/company-email.service.js';
|
import * as companyEmailService from '../services/company-email.service.js';
|
||||||
import {
|
|
||||||
logCompanyCreated,
|
|
||||||
logCompanyDeleted,
|
|
||||||
logCompanyUpdated,
|
|
||||||
logCompanyUserAssigned,
|
|
||||||
logCompanyUserRemoved,
|
|
||||||
logCompanyReminderCreated,
|
|
||||||
logCompanyReminderUpdated,
|
|
||||||
logCompanyReminderDeleted,
|
|
||||||
} from '../services/audit.service.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all companies
|
* Get all companies
|
||||||
@@ -125,10 +115,7 @@ export const createCompany = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
const company = await companyService.createCompany(userId, data);
|
const company = await companyService.createCompany(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyCreated(userId, company.id, company.name, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -151,20 +138,7 @@ export const updateCompany = async (req, res, next) => {
|
|||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
// Get old company for audit
|
const company = await companyService.updateCompany(companyId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const oldCompany = await companyService.getCompanyById(companyId);
|
|
||||||
|
|
||||||
const company = await companyService.updateCompany(companyId, data);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyUpdated(
|
|
||||||
userId,
|
|
||||||
companyId,
|
|
||||||
{ name: oldCompany.name },
|
|
||||||
{ name: company.name },
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -185,14 +159,7 @@ export const deleteCompany = async (req, res, next) => {
|
|||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
|
|
||||||
// Get company info before deleting
|
const result = await companyService.deleteCompany(companyId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const company = await companyService.getCompanyById(companyId);
|
|
||||||
const companyName = company?.name;
|
|
||||||
|
|
||||||
const result = await companyService.deleteCompany(companyId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyDeleted(userId, companyId, companyName, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -318,10 +285,7 @@ export const createCompanyReminder = async (req, res, next) => {
|
|||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
const { description, dueDate, isChecked } = req.body;
|
const { description, dueDate, isChecked } = req.body;
|
||||||
|
|
||||||
const reminder = await companyReminderService.createReminder(companyId, { description, dueDate, isChecked });
|
const reminder = await companyReminderService.createReminder(companyId, { description, dueDate, isChecked }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyReminderCreated(userId, reminder.id, companyId, dueDate, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -339,13 +303,7 @@ export const updateCompanyReminder = async (req, res, next) => {
|
|||||||
const { companyId, reminderId } = req.params;
|
const { companyId, reminderId } = req.params;
|
||||||
const { description, dueDate, isChecked } = req.body;
|
const { description, dueDate, isChecked } = req.body;
|
||||||
|
|
||||||
// Get old reminder for audit
|
const reminder = await companyReminderService.updateReminder(companyId, reminderId, { description, dueDate, isChecked }, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const oldReminder = await companyReminderService.getReminderById(reminderId);
|
|
||||||
|
|
||||||
const reminder = await companyReminderService.updateReminder(companyId, reminderId, { description, dueDate, isChecked });
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyReminderUpdated(userId, reminderId, companyId, oldReminder?.dueDate, dueDate, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -362,13 +320,7 @@ export const deleteCompanyReminder = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const { companyId, reminderId } = req.params;
|
const { companyId, reminderId } = req.params;
|
||||||
|
|
||||||
// Get reminder for audit before deletion
|
const result = await companyReminderService.deleteReminder(companyId, reminderId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const reminder = await companyReminderService.getReminderById(reminderId);
|
|
||||||
|
|
||||||
const result = await companyReminderService.deleteReminder(companyId, reminderId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyReminderDeleted(userId, reminderId, companyId, reminder?.dueDate, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -453,20 +405,7 @@ export const assignUserToCompany = async (req, res, next) => {
|
|||||||
const { companyId } = req.params;
|
const { companyId } = req.params;
|
||||||
const { userId, role } = req.body;
|
const { userId, role } = req.body;
|
||||||
|
|
||||||
// Get company name for audit
|
const assignment = await companyService.assignUserToCompany(companyId, userId, currentUserId, role, { userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const company = await companyService.getCompanyById(companyId);
|
|
||||||
|
|
||||||
const assignment = await companyService.assignUserToCompany(companyId, userId, currentUserId, role);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyUserAssigned(
|
|
||||||
currentUserId,
|
|
||||||
companyId,
|
|
||||||
userId,
|
|
||||||
company.name,
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -487,20 +426,7 @@ export const removeUserFromCompany = async (req, res, next) => {
|
|||||||
const currentUserId = req.userId;
|
const currentUserId = req.userId;
|
||||||
const { companyId, userId } = req.params;
|
const { companyId, userId } = req.params;
|
||||||
|
|
||||||
// Get company name for audit
|
const result = await companyService.removeUserFromCompany(companyId, userId, { userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const company = await companyService.getCompanyById(companyId);
|
|
||||||
|
|
||||||
const result = await companyService.removeUserFromCompany(companyId, userId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyUserRemoved(
|
|
||||||
currentUserId,
|
|
||||||
companyId,
|
|
||||||
userId,
|
|
||||||
company.name,
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as contactService from '../services/contact.service.js';
|
import * as contactService from '../services/contact.service.js';
|
||||||
import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap/index.js';
|
import { discoverContactsFromJMAP, getJmapConfigFromAccount } from '../services/jmap/index.js';
|
||||||
import * as emailAccountService from '../services/email-account.service.js';
|
import * as emailAccountService from '../services/email-account.service.js';
|
||||||
import { logContactLinkedToCompany, logCompanyCreatedFromContact } from '../services/audit.service.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all contacts for an email account
|
* Get all contacts for an email account
|
||||||
@@ -248,17 +247,7 @@ export const linkCompanyToContact = async (req, res, next) => {
|
|||||||
// Verify user has access to this email account
|
// Verify user has access to this email account
|
||||||
await emailAccountService.getEmailAccountById(accountId, userId);
|
await emailAccountService.getEmailAccountById(accountId, userId);
|
||||||
|
|
||||||
const updated = await contactService.linkCompanyToContact(contactId, accountId, companyId);
|
const updated = await contactService.linkCompanyToContact(contactId, accountId, companyId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logContactLinkedToCompany(
|
|
||||||
userId,
|
|
||||||
contactId,
|
|
||||||
companyId,
|
|
||||||
updated.company?.name || companyId,
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -330,17 +319,7 @@ export const createCompanyFromContact = async (req, res, next) => {
|
|||||||
// Verify user has access to this email account
|
// Verify user has access to this email account
|
||||||
await emailAccountService.getEmailAccountById(accountId, userId);
|
await emailAccountService.getEmailAccountById(accountId, userId);
|
||||||
|
|
||||||
const result = await contactService.createCompanyFromContact(contactId, accountId, userId, companyData);
|
const result = await contactService.createCompanyFromContact(contactId, accountId, userId, companyData, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logCompanyCreatedFromContact(
|
|
||||||
userId,
|
|
||||||
contactId,
|
|
||||||
result.company?.id,
|
|
||||||
result.company?.name,
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as noteService from '../services/note.service.js';
|
import * as noteService from '../services/note.service.js';
|
||||||
import { logNoteCreated, logNoteUpdated, logNoteDeleted } from '../services/audit.service.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all notes
|
* Get all notes
|
||||||
@@ -58,10 +57,7 @@ export const createNote = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
const note = await noteService.createNote(userId, data);
|
const note = await noteService.createNote(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logNoteCreated(userId, note.id, note.content, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -84,13 +80,7 @@ export const updateNote = async (req, res, next) => {
|
|||||||
const { noteId } = req.params;
|
const { noteId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
// Get old note for audit
|
const note = await noteService.updateNote(noteId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const oldNote = await noteService.getNoteById(noteId);
|
|
||||||
|
|
||||||
const note = await noteService.updateNote(noteId, data);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logNoteUpdated(userId, noteId, oldNote.content, note.content, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -111,13 +101,7 @@ export const deleteNote = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const { noteId } = req.params;
|
const { noteId } = req.params;
|
||||||
|
|
||||||
// Get note for audit before deletion
|
const result = await noteService.deleteNote(noteId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const note = await noteService.getNoteById(noteId);
|
|
||||||
|
|
||||||
const result = await noteService.deleteNote(noteId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logNoteDeleted(userId, noteId, note.content, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import * as projectService from '../services/project.service.js';
|
import * as projectService from '../services/project.service.js';
|
||||||
import * as noteService from '../services/note.service.js';
|
import * as noteService from '../services/note.service.js';
|
||||||
import {
|
|
||||||
logProjectCreated,
|
|
||||||
logProjectDeleted,
|
|
||||||
logProjectUpdated,
|
|
||||||
logProjectUserAssigned,
|
|
||||||
logProjectUserRemoved,
|
|
||||||
} from '../services/audit.service.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all projects
|
* Get all projects
|
||||||
@@ -79,10 +72,7 @@ export const createProject = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
const project = await projectService.createProject(userId, data);
|
const project = await projectService.createProject(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logProjectCreated(userId, project.id, project.name, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -105,20 +95,7 @@ export const updateProject = async (req, res, next) => {
|
|||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
// Get old project for audit
|
const project = await projectService.updateProject(projectId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const oldProject = await projectService.getProjectById(projectId);
|
|
||||||
|
|
||||||
const project = await projectService.updateProject(projectId, data);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logProjectUpdated(
|
|
||||||
userId,
|
|
||||||
projectId,
|
|
||||||
{ name: oldProject.name },
|
|
||||||
{ name: project.name },
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -139,14 +116,7 @@ export const deleteProject = async (req, res, next) => {
|
|||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
|
|
||||||
// Get project info before deleting
|
const result = await projectService.deleteProject(projectId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const project = await projectService.getProjectById(projectId);
|
|
||||||
const projectName = project?.name;
|
|
||||||
|
|
||||||
const result = await projectService.deleteProject(projectId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logProjectDeleted(userId, projectId, projectName, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -277,24 +247,12 @@ export const assignUserToProject = async (req, res, next) => {
|
|||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
const { userId, role } = req.body;
|
const { userId, role } = req.body;
|
||||||
|
|
||||||
// Get project name for audit
|
|
||||||
const project = await projectService.getProjectById(projectId);
|
|
||||||
|
|
||||||
const assignment = await projectService.assignUserToProject(
|
const assignment = await projectService.assignUserToProject(
|
||||||
projectId,
|
projectId,
|
||||||
userId,
|
userId,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
role
|
role,
|
||||||
);
|
{ userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] }
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logProjectUserAssigned(
|
|
||||||
currentUserId,
|
|
||||||
projectId,
|
|
||||||
userId,
|
|
||||||
project.name,
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
@@ -316,20 +274,7 @@ export const removeUserFromProject = async (req, res, next) => {
|
|||||||
const currentUserId = req.userId;
|
const currentUserId = req.userId;
|
||||||
const { projectId, userId } = req.params;
|
const { projectId, userId } = req.params;
|
||||||
|
|
||||||
// Get project name for audit
|
const result = await projectService.removeUserFromProject(projectId, userId, { userId: currentUserId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const project = await projectService.getProjectById(projectId);
|
|
||||||
|
|
||||||
const result = await projectService.removeUserFromProject(projectId, userId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logProjectUserRemoved(
|
|
||||||
currentUserId,
|
|
||||||
projectId,
|
|
||||||
userId,
|
|
||||||
project.name,
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import * as timeTrackingService from '../services/time-tracking.service.js';
|
import * as timeTrackingService from '../services/time-tracking.service.js';
|
||||||
import {
|
|
||||||
logTimerStarted,
|
|
||||||
logTimerStopped,
|
|
||||||
logTimerPaused,
|
|
||||||
logTimerResumed,
|
|
||||||
logTimeEntryUpdated,
|
|
||||||
logTimeEntryDeleted,
|
|
||||||
} from '../services/audit.service.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new time entry
|
* Start a new time entry
|
||||||
@@ -22,10 +14,7 @@ export const startTimeEntry = async (req, res, next) => {
|
|||||||
todoId,
|
todoId,
|
||||||
companyId,
|
companyId,
|
||||||
description,
|
description,
|
||||||
});
|
}, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTimerStarted(userId, entry.id, description || 'Timer', req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -52,10 +41,7 @@ export const stopTimeEntry = async (req, res, next) => {
|
|||||||
todoId,
|
todoId,
|
||||||
companyId,
|
companyId,
|
||||||
description,
|
description,
|
||||||
});
|
}, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTimerStopped(userId, entry.id, description || 'Timer', entry.duration, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -76,9 +62,7 @@ export const pauseTimeEntry = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const { entryId } = req.params;
|
const { entryId } = req.params;
|
||||||
|
|
||||||
const entry = await timeTrackingService.pauseTimeEntry(entryId, userId);
|
const entry = await timeTrackingService.pauseTimeEntry(entryId, userId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
await logTimerPaused(userId, entry.id, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -99,9 +83,7 @@ export const resumeTimeEntry = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const { entryId } = req.params;
|
const { entryId } = req.params;
|
||||||
|
|
||||||
const entry = await timeTrackingService.resumeTimeEntry(entryId, userId);
|
const entry = await timeTrackingService.resumeTimeEntry(entryId, userId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
await logTimerResumed(userId, entry.id, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -280,9 +262,6 @@ export const updateTimeEntry = async (req, res, next) => {
|
|||||||
const { entryId } = req.params;
|
const { entryId } = req.params;
|
||||||
const { startTime, endTime, projectId, todoId, companyId, description } = req.body;
|
const { startTime, endTime, projectId, todoId, companyId, description } = req.body;
|
||||||
|
|
||||||
// Get old entry for audit
|
|
||||||
const oldEntry = await timeTrackingService.getTimeEntryById(entryId);
|
|
||||||
|
|
||||||
const entry = await timeTrackingService.updateTimeEntry(entryId, {
|
const entry = await timeTrackingService.updateTimeEntry(entryId, {
|
||||||
userId,
|
userId,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
@@ -293,17 +272,7 @@ export const updateTimeEntry = async (req, res, next) => {
|
|||||||
todoId,
|
todoId,
|
||||||
companyId,
|
companyId,
|
||||||
description,
|
description,
|
||||||
});
|
}, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTimeEntryUpdated(
|
|
||||||
userId,
|
|
||||||
entryId,
|
|
||||||
{ description: oldEntry.description, duration: oldEntry.duration },
|
|
||||||
{ description: entry.description, duration: entry.duration },
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -324,23 +293,10 @@ export const deleteTimeEntry = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const { entryId } = req.params;
|
const { entryId } = req.params;
|
||||||
|
|
||||||
// Get entry for audit before deletion
|
|
||||||
const entry = await timeTrackingService.getTimeEntryById(entryId);
|
|
||||||
|
|
||||||
const result = await timeTrackingService.deleteTimeEntry(entryId, {
|
const result = await timeTrackingService.deleteTimeEntry(entryId, {
|
||||||
userId,
|
userId,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
});
|
}, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTimeEntryDeleted(
|
|
||||||
userId,
|
|
||||||
entryId,
|
|
||||||
entry.description || 'Time entry',
|
|
||||||
entry.duration,
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json(result);
|
res.status(200).json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as timesheetService from '../services/timesheet.service.js';
|
import * as timesheetService from '../services/timesheet.service.js';
|
||||||
import { ForbiddenError } from '../utils/errors.js';
|
import { ForbiddenError } from '../utils/errors.js';
|
||||||
import { logTimesheetUploaded, logTimesheetDeleted } from '../services/audit.service.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload timesheet
|
* Upload timesheet
|
||||||
@@ -26,11 +25,9 @@ export const uploadTimesheet = async (req, res, next) => {
|
|||||||
year,
|
year,
|
||||||
month,
|
month,
|
||||||
file: req.file,
|
file: req.file,
|
||||||
|
auditContext: { userId: req.userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTimesheetUploaded(req.userId, timesheet.id, year, month, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: { timesheet },
|
data: { timesheet },
|
||||||
@@ -112,16 +109,10 @@ export const deleteTimesheet = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const { timesheetId } = req.params;
|
const { timesheetId } = req.params;
|
||||||
|
|
||||||
// Get timesheet for audit before deletion
|
|
||||||
const timesheet = await timesheetService.getTimesheetById(timesheetId);
|
|
||||||
|
|
||||||
await timesheetService.deleteTimesheet(timesheetId, {
|
await timesheetService.deleteTimesheet(timesheetId, {
|
||||||
userId,
|
userId,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
});
|
}, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTimesheetDeleted(userId, timesheetId, timesheet?.year, timesheet?.month, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import * as todoService from '../services/todo.service.js';
|
import * as todoService from '../services/todo.service.js';
|
||||||
import {
|
|
||||||
logTodoCreated,
|
|
||||||
logTodoDeleted,
|
|
||||||
logTodoCompleted,
|
|
||||||
logTodoUpdated,
|
|
||||||
logTodoUncompleted,
|
|
||||||
} from '../services/audit.service.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all todos
|
* Get all todos
|
||||||
@@ -119,10 +112,7 @@ export const createTodo = async (req, res, next) => {
|
|||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
const todo = await todoService.createTodo(userId, data);
|
const todo = await todoService.createTodo(userId, data, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTodoCreated(userId, todo.id, todo.title, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -145,20 +135,7 @@ export const updateTodo = async (req, res, next) => {
|
|||||||
const { todoId } = req.params;
|
const { todoId } = req.params;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
// Get old todo for audit
|
const todo = await todoService.updateTodo(todoId, data, userId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const oldTodo = await todoService.getTodoById(todoId);
|
|
||||||
|
|
||||||
const todo = await todoService.updateTodo(todoId, data, userId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTodoUpdated(
|
|
||||||
userId,
|
|
||||||
todoId,
|
|
||||||
{ title: oldTodo.title },
|
|
||||||
{ title: todo.title },
|
|
||||||
req.ip,
|
|
||||||
req.headers['user-agent']
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -179,14 +156,7 @@ export const deleteTodo = async (req, res, next) => {
|
|||||||
const { todoId } = req.params;
|
const { todoId } = req.params;
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
|
|
||||||
// Get todo info before deleting
|
const result = await todoService.deleteTodo(todoId, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
const todo = await todoService.getTodoById(todoId);
|
|
||||||
const todoTitle = todo?.title;
|
|
||||||
|
|
||||||
const result = await todoService.deleteTodo(todoId);
|
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
await logTodoDeleted(userId, todoId, todoTitle, req.ip, req.headers['user-agent']);
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -213,14 +183,7 @@ export const toggleTodo = async (req, res, next) => {
|
|||||||
// Toggle completed status
|
// Toggle completed status
|
||||||
const updated = await todoService.updateTodo(todoId, {
|
const updated = await todoService.updateTodo(todoId, {
|
||||||
status: wasCompleted ? 'pending' : 'completed',
|
status: wasCompleted ? 'pending' : 'completed',
|
||||||
});
|
}, null, { userId, ipAddress: req.ip, userAgent: req.headers['user-agent'] });
|
||||||
|
|
||||||
// Log audit event
|
|
||||||
if (!wasCompleted) {
|
|
||||||
await logTodoCompleted(userId, todoId, todo.title, req.ip, req.headers['user-agent']);
|
|
||||||
} else {
|
|
||||||
await logTodoUncompleted(userId, todoId, todo.title, req.ip, req.headers['user-agent']);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { hashPassword, generateTempPassword } from '../utils/password.js';
|
|||||||
import { ConflictError, NotFoundError } from '../utils/errors.js';
|
import { ConflictError, NotFoundError } from '../utils/errors.js';
|
||||||
import * as emailAccountService from './email-account.service.js';
|
import * as emailAccountService from './email-account.service.js';
|
||||||
import { logger } from '../utils/logger.js';
|
import { logger } from '../utils/logger.js';
|
||||||
|
import { logUserCreation, logRoleChange, logUserDeleted } from './audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skontroluj či username už neexistuje
|
* Skontroluj či username už neexistuje
|
||||||
@@ -22,7 +23,7 @@ export const checkUsernameExists = async (username) => {
|
|||||||
/**
|
/**
|
||||||
* Vytvorenie nového usera s automatic temporary password
|
* Vytvorenie nového usera s automatic temporary password
|
||||||
*/
|
*/
|
||||||
export const createUser = async (username, firstName, lastName, role, email, emailPassword) => {
|
export const createUser = async (username, firstName, lastName, role, email, emailPassword, auditContext = null) => {
|
||||||
// Skontroluj či username už neexistuje
|
// Skontroluj či username už neexistuje
|
||||||
if (await checkUsernameExists(username)) {
|
if (await checkUsernameExists(username)) {
|
||||||
throw new ConflictError('Username už existuje');
|
throw new ConflictError('Username už existuje');
|
||||||
@@ -72,6 +73,10 @@ export const createUser = async (username, firstName, lastName, role, email, ema
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logUserCreation(auditContext.userId, newUser.id, username, newUser.role, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: newUser,
|
user: newUser,
|
||||||
tempPassword,
|
tempPassword,
|
||||||
@@ -136,7 +141,7 @@ export const getUserById = async (userId) => {
|
|||||||
/**
|
/**
|
||||||
* Zmena role usera
|
* Zmena role usera
|
||||||
*/
|
*/
|
||||||
export const changeUserRole = async (userId, newRole) => {
|
export const changeUserRole = async (userId, newRole, auditContext = null) => {
|
||||||
// Získaj starú rolu
|
// Získaj starú rolu
|
||||||
const [user] = await db
|
const [user] = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -159,6 +164,10 @@ export const changeUserRole = async (userId, newRole) => {
|
|||||||
})
|
})
|
||||||
.where(eq(users.id, userId));
|
.where(eq(users.id, userId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logRoleChange(auditContext.userId, userId, oldRole, newRole, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { userId, oldRole, newRole };
|
return { userId, oldRole, newRole };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -233,7 +242,7 @@ export const resetUserPassword = async (userId) => {
|
|||||||
/**
|
/**
|
||||||
* Zmazanie usera
|
* Zmazanie usera
|
||||||
*/
|
*/
|
||||||
export const deleteUser = async (userId) => {
|
export const deleteUser = async (userId, auditContext = null) => {
|
||||||
const [user] = await db
|
const [user] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(users)
|
.from(users)
|
||||||
@@ -264,6 +273,9 @@ export const deleteUser = async (userId) => {
|
|||||||
|
|
||||||
const emailAccountIds = userEmailAccountLinks.map(link => link.emailAccountId);
|
const emailAccountIds = userEmailAccountLinks.map(link => link.emailAccountId);
|
||||||
|
|
||||||
|
// Save username before deletion for audit
|
||||||
|
const deletedUsername = user.username;
|
||||||
|
|
||||||
// Delete user (cascades userEmailAccounts links)
|
// Delete user (cascades userEmailAccounts links)
|
||||||
await db.delete(users).where(eq(users.id, userId));
|
await db.delete(users).where(eq(users.id, userId));
|
||||||
|
|
||||||
@@ -281,5 +293,9 @@ export const deleteUser = async (userId) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logUserDeleted(auditContext.userId, userId, deletedUsername, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { deletedEmailAccounts };
|
return { deletedEmailAccounts };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,73 +8,84 @@ import {
|
|||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
} from '../utils/errors.js';
|
} from '../utils/errors.js';
|
||||||
|
import { logLoginAttempt, logLogin, logLogout, logPasswordChange, logEmailLink } from './audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KROK 1: Login s temporary password
|
* KROK 1: Login s temporary password
|
||||||
*/
|
*/
|
||||||
export const loginWithTempPassword = async (username, password, ipAddress, userAgent) => {
|
export const loginWithTempPassword = async (username, password, ipAddress, userAgent) => {
|
||||||
// Najdi usera
|
try {
|
||||||
const [user] = await db
|
// Najdi usera
|
||||||
.select()
|
const [user] = await db
|
||||||
.from(users)
|
.select()
|
||||||
.where(eq(users.username, username))
|
.from(users)
|
||||||
.limit(1);
|
.where(eq(users.username, username))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new AuthenticationError('Nesprávne prihlasovacie údaje');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ak už user zmenil heslo, použije sa permanentné heslo
|
|
||||||
if (user.changedPassword && user.password) {
|
|
||||||
const isValid = await comparePassword(password, user.password);
|
|
||||||
if (!isValid) {
|
|
||||||
throw new AuthenticationError('Nesprávne prihlasovacie údaje');
|
throw new AuthenticationError('Nesprávne prihlasovacie údaje');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Ak ešte nezmenil heslo, použije sa temporary password
|
// Ak už user zmenil heslo, použije sa permanentné heslo
|
||||||
if (!user.tempPassword) {
|
if (user.changedPassword && user.password) {
|
||||||
throw new AuthenticationError('Účet nie je správne nastavený');
|
const isValid = await comparePassword(password, user.password);
|
||||||
|
if (!isValid) {
|
||||||
|
throw new AuthenticationError('Nesprávne prihlasovacie údaje');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ak ešte nezmenil heslo, použije sa temporary password
|
||||||
|
if (!user.tempPassword) {
|
||||||
|
throw new AuthenticationError('Účet nie je správne nastavený');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary password môže byť plain text alebo hash (závisí od seeding)
|
||||||
|
// Pre bezpečnosť ho budeme hashovať pri vytvorení
|
||||||
|
const isValid = await comparePassword(password, user.tempPassword);
|
||||||
|
if (!isValid) {
|
||||||
|
throw new AuthenticationError('Nesprávne prihlasovacie údaje');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary password môže byť plain text alebo hash (závisí od seeding)
|
// Update last login
|
||||||
// Pre bezpečnosť ho budeme hashovať pri vytvorení
|
await db
|
||||||
const isValid = await comparePassword(password, user.tempPassword);
|
.update(users)
|
||||||
if (!isValid) {
|
.set({ lastLogin: new Date() })
|
||||||
throw new AuthenticationError('Nesprávne prihlasovacie údaje');
|
.where(eq(users.id, user.id));
|
||||||
}
|
|
||||||
|
// Generuj JWT tokeny
|
||||||
|
const tokens = generateTokenPair(user);
|
||||||
|
|
||||||
|
// Check if user has email accounts (many-to-many)
|
||||||
|
const userEmailAccounts = await emailAccountService.getUserEmailAccounts(user.id);
|
||||||
|
|
||||||
|
// Log successful login
|
||||||
|
await logLoginAttempt(username, true, ipAddress, userAgent);
|
||||||
|
await logLogin(user.id, username, ipAddress, userAgent);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
role: user.role,
|
||||||
|
changedPassword: user.changedPassword,
|
||||||
|
},
|
||||||
|
tokens,
|
||||||
|
needsPasswordChange: !user.changedPassword,
|
||||||
|
needsEmailSetup: userEmailAccounts.length === 0,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// Log failed login attempt
|
||||||
|
await logLoginAttempt(username, false, ipAddress, userAgent, error.message);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last login
|
|
||||||
await db
|
|
||||||
.update(users)
|
|
||||||
.set({ lastLogin: new Date() })
|
|
||||||
.where(eq(users.id, user.id));
|
|
||||||
|
|
||||||
// Generuj JWT tokeny
|
|
||||||
const tokens = generateTokenPair(user);
|
|
||||||
|
|
||||||
// Check if user has email accounts (many-to-many)
|
|
||||||
const userEmailAccounts = await emailAccountService.getUserEmailAccounts(user.id);
|
|
||||||
|
|
||||||
return {
|
|
||||||
user: {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
firstName: user.firstName,
|
|
||||||
lastName: user.lastName,
|
|
||||||
role: user.role,
|
|
||||||
changedPassword: user.changedPassword,
|
|
||||||
},
|
|
||||||
tokens,
|
|
||||||
needsPasswordChange: !user.changedPassword,
|
|
||||||
needsEmailSetup: userEmailAccounts.length === 0,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KROK 2: Nastavenie nového hesla
|
* KROK 2: Nastavenie nového hesla
|
||||||
*/
|
*/
|
||||||
export const setNewPassword = async (userId, newPassword) => {
|
export const setNewPassword = async (userId, newPassword, auditContext = null) => {
|
||||||
const [user] = await db
|
const [user] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(users)
|
.from(users)
|
||||||
@@ -104,6 +115,10 @@ export const setNewPassword = async (userId, newPassword) => {
|
|||||||
})
|
})
|
||||||
.where(eq(users.id, userId));
|
.where(eq(users.id, userId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logPasswordChange(auditContext.userId, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Heslo úspešne nastavené',
|
message: 'Heslo úspešne nastavené',
|
||||||
@@ -114,7 +129,7 @@ export const setNewPassword = async (userId, newPassword) => {
|
|||||||
* KROK 3: Pripojenie emailu s JMAP validáciou
|
* KROK 3: Pripojenie emailu s JMAP validáciou
|
||||||
* Používa many-to-many vzťah cez userEmailAccounts
|
* Používa many-to-many vzťah cez userEmailAccounts
|
||||||
*/
|
*/
|
||||||
export const linkEmail = async (userId, email, emailPassword) => {
|
export const linkEmail = async (userId, email, emailPassword, auditContext = null) => {
|
||||||
const [user] = await db
|
const [user] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(users)
|
.from(users)
|
||||||
@@ -132,6 +147,10 @@ export const linkEmail = async (userId, email, emailPassword) => {
|
|||||||
emailPassword
|
emailPassword
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logEmailLink(auditContext.userId, email, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
accountId: newEmailAccount.jmapAccountId,
|
accountId: newEmailAccount.jmapAccountId,
|
||||||
@@ -167,7 +186,11 @@ export const skipEmailSetup = async (userId) => {
|
|||||||
/**
|
/**
|
||||||
* Logout - clear tokens (handled on client side)
|
* Logout - clear tokens (handled on client side)
|
||||||
*/
|
*/
|
||||||
export const logout = async () => {
|
export const logout = async (auditContext = null) => {
|
||||||
|
if (auditContext) {
|
||||||
|
await logLogout(auditContext.userId, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Úspešne odhlásený',
|
message: 'Úspešne odhlásený',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { companies, companyReminders } from '../db/schema.js';
|
|||||||
import { eq, desc, sql, and, lte, gte, isNull, or, inArray } from 'drizzle-orm';
|
import { eq, desc, sql, and, lte, gte, isNull, or, inArray } from 'drizzle-orm';
|
||||||
import { NotFoundError, BadRequestError } from '../utils/errors.js';
|
import { NotFoundError, BadRequestError } from '../utils/errors.js';
|
||||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||||
|
import { logCompanyReminderCreated, logCompanyReminderUpdated, logCompanyReminderDeleted } from './audit.service.js';
|
||||||
|
|
||||||
const ensureCompanyExists = async (companyId) => {
|
const ensureCompanyExists = async (companyId) => {
|
||||||
const [company] = await db
|
const [company] = await db
|
||||||
@@ -44,7 +45,7 @@ export const getRemindersByCompanyId = async (companyId) => {
|
|||||||
return reminders;
|
return reminders;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createReminder = async (companyId, data) => {
|
export const createReminder = async (companyId, data, auditContext = null) => {
|
||||||
await ensureCompanyExists(companyId);
|
await ensureCompanyExists(companyId);
|
||||||
|
|
||||||
const description = data.description?.trim();
|
const description = data.description?.trim();
|
||||||
@@ -62,10 +63,14 @@ export const createReminder = async (companyId, data) => {
|
|||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyReminderCreated(auditContext.userId, reminder.id, companyId, data.dueDate, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return reminder;
|
return reminder;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateReminder = async (companyId, reminderId, data) => {
|
export const updateReminder = async (companyId, reminderId, data, auditContext = null) => {
|
||||||
const reminder = await getReminderById(reminderId);
|
const reminder = await getReminderById(reminderId);
|
||||||
|
|
||||||
if (reminder.companyId !== companyId) {
|
if (reminder.companyId !== companyId) {
|
||||||
@@ -91,10 +96,14 @@ export const updateReminder = async (companyId, reminderId, data) => {
|
|||||||
.where(eq(companyReminders.id, reminderId))
|
.where(eq(companyReminders.id, reminderId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyReminderUpdated(auditContext.userId, reminderId, companyId, reminder.dueDate, data.dueDate, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updatedReminder;
|
return updatedReminder;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteReminder = async (companyId, reminderId) => {
|
export const deleteReminder = async (companyId, reminderId, auditContext = null) => {
|
||||||
const reminder = await getReminderById(reminderId);
|
const reminder = await getReminderById(reminderId);
|
||||||
|
|
||||||
if (reminder.companyId !== companyId) {
|
if (reminder.companyId !== companyId) {
|
||||||
@@ -103,6 +112,10 @@ export const deleteReminder = async (companyId, reminderId) => {
|
|||||||
|
|
||||||
await db.delete(companyReminders).where(eq(companyReminders.id, reminderId));
|
await db.delete(companyReminders).where(eq(companyReminders.id, reminderId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyReminderDeleted(auditContext.userId, reminderId, companyId, reminder.dueDate, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Reminder bol odstránený' };
|
return { success: true, message: 'Reminder bol odstránený' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { companies, projects, todos, notes, companyReminders, companyUsers, user
|
|||||||
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
||||||
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
||||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||||
|
import {
|
||||||
|
logCompanyCreated, logCompanyDeleted, logCompanyUpdated,
|
||||||
|
logCompanyUserAssigned, logCompanyUserRemoved,
|
||||||
|
} from './audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all companies
|
* Get all companies
|
||||||
@@ -111,7 +115,7 @@ export const getCompanyById = async (companyId) => {
|
|||||||
/**
|
/**
|
||||||
* Create new company
|
* Create new company
|
||||||
*/
|
*/
|
||||||
export const createCompany = async (userId, data) => {
|
export const createCompany = async (userId, data, auditContext = null) => {
|
||||||
const { name, description, address, city, postalCode, country, phone, email, website, status } = data;
|
const { name, description, address, city, postalCode, country, phone, email, website, status } = data;
|
||||||
|
|
||||||
// Check if company with same name already exists
|
// Check if company with same name already exists
|
||||||
@@ -150,13 +154,17 @@ export const createCompany = async (userId, data) => {
|
|||||||
addedBy: userId,
|
addedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyCreated(auditContext.userId, newCompany.id, newCompany.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return newCompany;
|
return newCompany;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update company
|
* Update company
|
||||||
*/
|
*/
|
||||||
export const updateCompany = async (companyId, data) => {
|
export const updateCompany = async (companyId, data, auditContext = null) => {
|
||||||
const company = await getCompanyById(companyId);
|
const company = await getCompanyById(companyId);
|
||||||
|
|
||||||
const { name, description, address, city, postalCode, country, phone, email, website, status } = data;
|
const { name, description, address, city, postalCode, country, phone, email, website, status } = data;
|
||||||
@@ -192,17 +200,25 @@ export const updateCompany = async (companyId, data) => {
|
|||||||
.where(eq(companies.id, companyId))
|
.where(eq(companies.id, companyId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyUpdated(auditContext.userId, companyId, { name: company.name }, { name: updated.name }, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete company
|
* Delete company
|
||||||
*/
|
*/
|
||||||
export const deleteCompany = async (companyId) => {
|
export const deleteCompany = async (companyId, auditContext = null) => {
|
||||||
await getCompanyById(companyId); // Check if exists
|
const company = await getCompanyById(companyId); // Check if exists
|
||||||
|
|
||||||
await db.delete(companies).where(eq(companies.id, companyId));
|
await db.delete(companies).where(eq(companies.id, companyId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyDeleted(auditContext.userId, companyId, company.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Firma bola odstránená' };
|
return { success: true, message: 'Firma bola odstránená' };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -281,8 +297,8 @@ export const getCompanyUsers = async (companyId) => {
|
|||||||
/**
|
/**
|
||||||
* Assign user to company
|
* Assign user to company
|
||||||
*/
|
*/
|
||||||
export const assignUserToCompany = async (companyId, userId, addedByUserId, role = null) => {
|
export const assignUserToCompany = async (companyId, userId, addedByUserId, role = null, auditContext = null) => {
|
||||||
await getCompanyById(companyId); // Verify company exists
|
const company = await getCompanyById(companyId); // Verify company exists
|
||||||
|
|
||||||
// Verify user exists
|
// Verify user exists
|
||||||
const [user] = await db
|
const [user] = await db
|
||||||
@@ -325,6 +341,10 @@ export const assignUserToCompany = async (companyId, userId, addedByUserId, role
|
|||||||
.where(eq(companyUsers.id, assignment.id))
|
.where(eq(companyUsers.id, assignment.id))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyUserAssigned(auditContext.userId, companyId, userId, company.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: row.company_users.id,
|
id: row.company_users.id,
|
||||||
userId: row.company_users.userId,
|
userId: row.company_users.userId,
|
||||||
@@ -343,8 +363,8 @@ export const assignUserToCompany = async (companyId, userId, addedByUserId, role
|
|||||||
/**
|
/**
|
||||||
* Remove user from company
|
* Remove user from company
|
||||||
*/
|
*/
|
||||||
export const removeUserFromCompany = async (companyId, userId) => {
|
export const removeUserFromCompany = async (companyId, userId, auditContext = null) => {
|
||||||
await getCompanyById(companyId); // Verify company exists
|
const company = await getCompanyById(companyId); // Verify company exists
|
||||||
|
|
||||||
// Check if user is assigned
|
// Check if user is assigned
|
||||||
const [existing] = await db
|
const [existing] = await db
|
||||||
@@ -362,6 +382,10 @@ export const removeUserFromCompany = async (companyId, userId) => {
|
|||||||
.delete(companyUsers)
|
.delete(companyUsers)
|
||||||
.where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)));
|
.where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyUserRemoved(auditContext.userId, companyId, userId, company.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Používateľ bol odstránený z firmy' };
|
return { success: true, message: 'Používateľ bol odstránený z firmy' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { contacts, emails, companies, emailAccounts } from '../db/schema.js';
|
|||||||
import { eq, and, desc, or, ne } from 'drizzle-orm';
|
import { eq, and, desc, or, ne } from 'drizzle-orm';
|
||||||
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
||||||
import { syncEmailsFromSender } from './jmap/index.js';
|
import { syncEmailsFromSender } from './jmap/index.js';
|
||||||
|
import { logContactLinkedToCompany, logCompanyCreatedFromContact } from './audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get contacts with related data (emailAccount, company) using joins
|
* Get contacts with related data (emailAccount, company) using joins
|
||||||
@@ -185,7 +186,7 @@ export const updateContact = async (contactId, emailAccountId, { name, notes })
|
|||||||
/**
|
/**
|
||||||
* Link company to contact
|
* Link company to contact
|
||||||
*/
|
*/
|
||||||
export const linkCompanyToContact = async (contactId, emailAccountId, companyId) => {
|
export const linkCompanyToContact = async (contactId, emailAccountId, companyId, auditContext = null) => {
|
||||||
const contact = await getContactById(contactId, emailAccountId);
|
const contact = await getContactById(contactId, emailAccountId);
|
||||||
|
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
@@ -206,6 +207,10 @@ export const linkCompanyToContact = async (contactId, emailAccountId, companyId)
|
|||||||
})
|
})
|
||||||
.where(eq(emails.contactId, contactId));
|
.where(eq(emails.contactId, contactId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logContactLinkedToCompany(auditContext.userId, contactId, companyId, updated.company?.name || companyId, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,7 +245,7 @@ export const unlinkCompanyFromContact = async (contactId, emailAccountId) => {
|
|||||||
* Create company from contact
|
* Create company from contact
|
||||||
* Creates a new company using contact's information and links it
|
* Creates a new company using contact's information and links it
|
||||||
*/
|
*/
|
||||||
export const createCompanyFromContact = async (contactId, emailAccountId, userId, companyData = {}) => {
|
export const createCompanyFromContact = async (contactId, emailAccountId, userId, companyData = {}, auditContext = null) => {
|
||||||
const contact = await getContactById(contactId, emailAccountId);
|
const contact = await getContactById(contactId, emailAccountId);
|
||||||
|
|
||||||
// Check if company with same name already exists
|
// Check if company with same name already exists
|
||||||
@@ -291,6 +296,10 @@ export const createCompanyFromContact = async (contactId, emailAccountId, userId
|
|||||||
})
|
})
|
||||||
.where(eq(emails.contactId, contactId));
|
.where(eq(emails.contactId, contactId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logCompanyCreatedFromContact(auditContext.userId, contactId, newCompany.id, newCompany.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
company: newCompany,
|
company: newCompany,
|
||||||
contact: updatedContact,
|
contact: updatedContact,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { db } from '../config/database.js';
|
|||||||
import { notes, companies, projects, todos, contacts, users } from '../db/schema.js';
|
import { notes, companies, projects, todos, contacts, users } from '../db/schema.js';
|
||||||
import { eq, desc, ilike, or, and } from 'drizzle-orm';
|
import { eq, desc, ilike, or, and } from 'drizzle-orm';
|
||||||
import { NotFoundError } from '../utils/errors.js';
|
import { NotFoundError } from '../utils/errors.js';
|
||||||
|
import { logNoteCreated, logNoteUpdated, logNoteDeleted } from './audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all notes
|
* Get all notes
|
||||||
@@ -86,7 +87,7 @@ export const getNoteById = async (noteId) => {
|
|||||||
/**
|
/**
|
||||||
* Create new note
|
* Create new note
|
||||||
*/
|
*/
|
||||||
export const createNote = async (userId, data) => {
|
export const createNote = async (userId, data, auditContext = null) => {
|
||||||
const { title, content, companyId, projectId, todoId, contactId, dueDate } = data;
|
const { title, content, companyId, projectId, todoId, contactId, dueDate } = data;
|
||||||
|
|
||||||
// Verify company exists if provided
|
// Verify company exists if provided
|
||||||
@@ -155,13 +156,17 @@ export const createNote = async (userId, data) => {
|
|||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logNoteCreated(auditContext.userId, newNote.id, newNote.content, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return newNote;
|
return newNote;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update note
|
* Update note
|
||||||
*/
|
*/
|
||||||
export const updateNote = async (noteId, data) => {
|
export const updateNote = async (noteId, data, auditContext = null) => {
|
||||||
const note = await getNoteById(noteId);
|
const note = await getNoteById(noteId);
|
||||||
|
|
||||||
const { title, content, companyId, projectId, todoId, contactId, dueDate } = data;
|
const { title, content, companyId, projectId, todoId, contactId, dueDate } = data;
|
||||||
@@ -239,17 +244,25 @@ export const updateNote = async (noteId, data) => {
|
|||||||
.where(eq(notes.id, noteId))
|
.where(eq(notes.id, noteId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logNoteUpdated(auditContext.userId, noteId, note.content, updated.content, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete note
|
* Delete note
|
||||||
*/
|
*/
|
||||||
export const deleteNote = async (noteId) => {
|
export const deleteNote = async (noteId, auditContext = null) => {
|
||||||
await getNoteById(noteId); // Check if exists
|
const note = await getNoteById(noteId); // Check if exists
|
||||||
|
|
||||||
await db.delete(notes).where(eq(notes.id, noteId));
|
await db.delete(notes).where(eq(notes.id, noteId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logNoteDeleted(auditContext.userId, noteId, note.content, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Poznámka bola odstránená' };
|
return { success: true, message: 'Poznámka bola odstránená' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { projects, todos, notes, timesheets, companies, projectUsers, users } fr
|
|||||||
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
||||||
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
||||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||||
|
import {
|
||||||
|
logProjectCreated, logProjectDeleted, logProjectUpdated,
|
||||||
|
logProjectUserAssigned, logProjectUserRemoved,
|
||||||
|
} from './audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all projects
|
* Get all projects
|
||||||
@@ -109,7 +113,7 @@ export const getProjectById = async (projectId) => {
|
|||||||
/**
|
/**
|
||||||
* Create new project
|
* Create new project
|
||||||
*/
|
*/
|
||||||
export const createProject = async (userId, data) => {
|
export const createProject = async (userId, data, auditContext = null) => {
|
||||||
const { name, description, companyId, status, startDate, endDate } = data;
|
const { name, description, companyId, status, startDate, endDate } = data;
|
||||||
|
|
||||||
// If companyId is provided, verify company exists
|
// If companyId is provided, verify company exists
|
||||||
@@ -146,13 +150,17 @@ export const createProject = async (userId, data) => {
|
|||||||
addedBy: userId,
|
addedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logProjectCreated(auditContext.userId, newProject.id, newProject.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return newProject;
|
return newProject;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update project
|
* Update project
|
||||||
*/
|
*/
|
||||||
export const updateProject = async (projectId, data) => {
|
export const updateProject = async (projectId, data, auditContext = null) => {
|
||||||
const project = await getProjectById(projectId);
|
const project = await getProjectById(projectId);
|
||||||
|
|
||||||
const { name, description, companyId, status, startDate, endDate } = data;
|
const { name, description, companyId, status, startDate, endDate } = data;
|
||||||
@@ -187,17 +195,25 @@ export const updateProject = async (projectId, data) => {
|
|||||||
.where(eq(projects.id, projectId))
|
.where(eq(projects.id, projectId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logProjectUpdated(auditContext.userId, projectId, { name: project.name }, { name: updated.name }, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete project
|
* Delete project
|
||||||
*/
|
*/
|
||||||
export const deleteProject = async (projectId) => {
|
export const deleteProject = async (projectId, auditContext = null) => {
|
||||||
await getProjectById(projectId); // Check if exists
|
const project = await getProjectById(projectId); // Check if exists
|
||||||
|
|
||||||
await db.delete(projects).where(eq(projects.id, projectId));
|
await db.delete(projects).where(eq(projects.id, projectId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logProjectDeleted(auditContext.userId, projectId, project.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Projekt bol odstránený' };
|
return { success: true, message: 'Projekt bol odstránený' };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -314,8 +330,8 @@ export const getProjectUsers = async (projectId) => {
|
|||||||
/**
|
/**
|
||||||
* Assign user to project
|
* Assign user to project
|
||||||
*/
|
*/
|
||||||
export const assignUserToProject = async (projectId, userId, addedByUserId, role = null) => {
|
export const assignUserToProject = async (projectId, userId, addedByUserId, role = null, auditContext = null) => {
|
||||||
await getProjectById(projectId); // Verify project exists
|
const project = await getProjectById(projectId); // Verify project exists
|
||||||
|
|
||||||
// Verify user exists
|
// Verify user exists
|
||||||
const [user] = await db
|
const [user] = await db
|
||||||
@@ -358,6 +374,10 @@ export const assignUserToProject = async (projectId, userId, addedByUserId, role
|
|||||||
.where(eq(projectUsers.id, assignment.id))
|
.where(eq(projectUsers.id, assignment.id))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logProjectUserAssigned(auditContext.userId, projectId, userId, project.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: row.project_users.id,
|
id: row.project_users.id,
|
||||||
userId: row.project_users.userId,
|
userId: row.project_users.userId,
|
||||||
@@ -376,8 +396,8 @@ export const assignUserToProject = async (projectId, userId, addedByUserId, role
|
|||||||
/**
|
/**
|
||||||
* Remove user from project
|
* Remove user from project
|
||||||
*/
|
*/
|
||||||
export const removeUserFromProject = async (projectId, userId) => {
|
export const removeUserFromProject = async (projectId, userId, auditContext = null) => {
|
||||||
await getProjectById(projectId); // Verify project exists
|
const project = await getProjectById(projectId); // Verify project exists
|
||||||
|
|
||||||
// Check if user is assigned
|
// Check if user is assigned
|
||||||
const [existing] = await db
|
const [existing] = await db
|
||||||
@@ -395,6 +415,10 @@ export const removeUserFromProject = async (projectId, userId) => {
|
|||||||
.delete(projectUsers)
|
.delete(projectUsers)
|
||||||
.where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId)));
|
.where(and(eq(projectUsers.projectId, projectId), eq(projectUsers.userId, userId)));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logProjectUserRemoved(auditContext.userId, projectId, userId, project.name, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Používateľ bol odstránený z projektu' };
|
return { success: true, message: 'Používateľ bol odstránený z projektu' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { NotFoundError, BadRequestError, ForbiddenError } from '../utils/errors.
|
|||||||
import ExcelJS from 'exceljs';
|
import ExcelJS from 'exceljs';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import {
|
||||||
|
logTimerStarted, logTimerStopped, logTimerPaused, logTimerResumed,
|
||||||
|
logTimeEntryUpdated, logTimeEntryDeleted,
|
||||||
|
} from './audit.service.js';
|
||||||
|
|
||||||
// Helpers to normalize optional payload fields
|
// Helpers to normalize optional payload fields
|
||||||
const normalizeOptionalId = (value) => {
|
const normalizeOptionalId = (value) => {
|
||||||
@@ -216,7 +220,7 @@ const saveTimesheetFile = async (workbook, { userId, year, month, filename }) =>
|
|||||||
/**
|
/**
|
||||||
* Start a new time entry
|
* Start a new time entry
|
||||||
*/
|
*/
|
||||||
export const startTimeEntry = async (userId, data) => {
|
export const startTimeEntry = async (userId, data, auditContext = null) => {
|
||||||
const projectId = normalizeOptionalId(data.projectId);
|
const projectId = normalizeOptionalId(data.projectId);
|
||||||
const todoId = normalizeOptionalId(data.todoId);
|
const todoId = normalizeOptionalId(data.todoId);
|
||||||
const companyId = normalizeOptionalId(data.companyId);
|
const companyId = normalizeOptionalId(data.companyId);
|
||||||
@@ -268,13 +272,17 @@ export const startTimeEntry = async (userId, data) => {
|
|||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimerStarted(auditContext.userId, newEntry.id, data.description || 'Timer', auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return newEntry;
|
return newEntry;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop a running time entry
|
* Stop a running time entry
|
||||||
*/
|
*/
|
||||||
export const stopTimeEntry = async (entryId, userId, data = {}) => {
|
export const stopTimeEntry = async (entryId, userId, data = {}, auditContext = null) => {
|
||||||
const projectId = normalizeOptionalId(data.projectId);
|
const projectId = normalizeOptionalId(data.projectId);
|
||||||
const todoId = normalizeOptionalId(data.todoId);
|
const todoId = normalizeOptionalId(data.todoId);
|
||||||
const companyId = normalizeOptionalId(data.companyId);
|
const companyId = normalizeOptionalId(data.companyId);
|
||||||
@@ -315,13 +323,17 @@ export const stopTimeEntry = async (entryId, userId, data = {}) => {
|
|||||||
.where(eq(timeEntries.id, entryId))
|
.where(eq(timeEntries.id, entryId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimerStopped(auditContext.userId, updated.id, data.description || 'Timer', updated.duration, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pause a running time entry
|
* Pause a running time entry
|
||||||
*/
|
*/
|
||||||
export const pauseTimeEntry = async (entryId, userId) => {
|
export const pauseTimeEntry = async (entryId, userId, auditContext = null) => {
|
||||||
const entry = await getTimeEntryById(entryId);
|
const entry = await getTimeEntryById(entryId);
|
||||||
|
|
||||||
if (entry.userId !== userId) {
|
if (entry.userId !== userId) {
|
||||||
@@ -345,13 +357,17 @@ export const pauseTimeEntry = async (entryId, userId) => {
|
|||||||
.where(eq(timeEntries.id, entryId))
|
.where(eq(timeEntries.id, entryId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimerPaused(auditContext.userId, updated.id, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resume a paused time entry
|
* Resume a paused time entry
|
||||||
*/
|
*/
|
||||||
export const resumeTimeEntry = async (entryId, userId) => {
|
export const resumeTimeEntry = async (entryId, userId, auditContext = null) => {
|
||||||
const entry = await getTimeEntryById(entryId);
|
const entry = await getTimeEntryById(entryId);
|
||||||
|
|
||||||
if (entry.userId !== userId) {
|
if (entry.userId !== userId) {
|
||||||
@@ -380,6 +396,10 @@ export const resumeTimeEntry = async (entryId, userId) => {
|
|||||||
.where(eq(timeEntries.id, entryId))
|
.where(eq(timeEntries.id, entryId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimerResumed(auditContext.userId, updated.id, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -603,7 +623,7 @@ export const generateMonthlyTimesheet = async (userId, year, month) => {
|
|||||||
/**
|
/**
|
||||||
* Update time entry
|
* Update time entry
|
||||||
*/
|
*/
|
||||||
export const updateTimeEntry = async (entryId, actor, data) => {
|
export const updateTimeEntry = async (entryId, actor, data, auditContext = null) => {
|
||||||
const { userId, role } = actor;
|
const { userId, role } = actor;
|
||||||
const entry = await getTimeEntryById(entryId);
|
const entry = await getTimeEntryById(entryId);
|
||||||
|
|
||||||
@@ -652,13 +672,17 @@ export const updateTimeEntry = async (entryId, actor, data) => {
|
|||||||
.where(eq(timeEntries.id, entryId))
|
.where(eq(timeEntries.id, entryId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimeEntryUpdated(auditContext.userId, entryId, { description: entry.description, duration: entry.duration }, { description: updated.description, duration: updated.duration }, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete time entry
|
* Delete time entry
|
||||||
*/
|
*/
|
||||||
export const deleteTimeEntry = async (entryId, actor) => {
|
export const deleteTimeEntry = async (entryId, actor, auditContext = null) => {
|
||||||
const { userId, role } = actor;
|
const { userId, role } = actor;
|
||||||
const entry = await getTimeEntryById(entryId);
|
const entry = await getTimeEntryById(entryId);
|
||||||
|
|
||||||
@@ -672,6 +696,10 @@ export const deleteTimeEntry = async (entryId, actor) => {
|
|||||||
|
|
||||||
await db.delete(timeEntries).where(eq(timeEntries.id, entryId));
|
await db.delete(timeEntries).where(eq(timeEntries.id, entryId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimeEntryDeleted(auditContext.userId, entryId, entry.description || 'Time entry', entry.duration, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Záznam bol odstránený' };
|
return { success: true, message: 'Záznam bol odstránený' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { timesheets, users } from '../db/schema.js';
|
|||||||
import { and, desc, eq } from 'drizzle-orm';
|
import { and, desc, eq } from 'drizzle-orm';
|
||||||
import { BadRequestError, ForbiddenError, NotFoundError } from '../utils/errors.js';
|
import { BadRequestError, ForbiddenError, NotFoundError } from '../utils/errors.js';
|
||||||
import { logger } from '../utils/logger.js';
|
import { logger } from '../utils/logger.js';
|
||||||
|
import { logTimesheetUploaded, logTimesheetDeleted } from './audit.service.js';
|
||||||
|
|
||||||
const ALLOWED_MIME_TYPES = [
|
const ALLOWED_MIME_TYPES = [
|
||||||
'application/pdf',
|
'application/pdf',
|
||||||
@@ -91,7 +92,7 @@ const generateTimesheetFileName = (firstName, lastName, username, year, month, f
|
|||||||
return `${namePrefix}-vykazprace-${year}-${monthStr}${fileExt}`;
|
return `${namePrefix}-vykazprace-${year}-${monthStr}${fileExt}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadTimesheet = async ({ userId, year, month, file }) => {
|
export const uploadTimesheet = async ({ userId, year, month, file, auditContext = null }) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
throw new BadRequestError('Súbor nebol nahraný');
|
throw new BadRequestError('Súbor nebol nahraný');
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,13 @@ export const uploadTimesheet = async ({ userId, year, month, file }) => {
|
|||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
return sanitizeTimesheet(newTimesheet);
|
const sanitized = sanitizeTimesheet(newTimesheet);
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimesheetUploaded(auditContext.userId, newTimesheet.id, parsedYear, parsedMonth, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await safeUnlink(filePath);
|
await safeUnlink(filePath);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -225,10 +232,14 @@ export const getDownloadInfo = async (timesheetId, { userId, role }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteTimesheet = async (timesheetId, { userId, role }) => {
|
export const deleteTimesheet = async (timesheetId, { userId, role }, auditContext = null) => {
|
||||||
const timesheet = await ensureTimesheetExists(timesheetId);
|
const timesheet = await ensureTimesheetExists(timesheetId);
|
||||||
assertAccess(timesheet, { userId, role });
|
assertAccess(timesheet, { userId, role });
|
||||||
|
|
||||||
await safeUnlink(timesheet.filePath);
|
await safeUnlink(timesheet.filePath);
|
||||||
await db.delete(timesheets).where(eq(timesheets.id, timesheetId));
|
await db.delete(timesheets).where(eq(timesheets.id, timesheetId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTimesheetDeleted(auditContext.userId, timesheetId, timesheet.year, timesheet.month, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { NotFoundError } from '../utils/errors.js';
|
|||||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||||
import { sendPushNotificationToUsers } from './push.service.js';
|
import { sendPushNotificationToUsers } from './push.service.js';
|
||||||
import { logger } from '../utils/logger.js';
|
import { logger } from '../utils/logger.js';
|
||||||
|
import {
|
||||||
|
logTodoCreated, logTodoDeleted, logTodoCompleted, logTodoUpdated, logTodoUncompleted,
|
||||||
|
} from './audit.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all todos
|
* Get all todos
|
||||||
@@ -142,7 +145,7 @@ export const getTodoById = async (todoId) => {
|
|||||||
* @param {string} userId - ID of user creating the todo
|
* @param {string} userId - ID of user creating the todo
|
||||||
* @param {object} data - Todo data including assignedUserIds array
|
* @param {object} data - Todo data including assignedUserIds array
|
||||||
*/
|
*/
|
||||||
export const createTodo = async (userId, data) => {
|
export const createTodo = async (userId, data, auditContext = null) => {
|
||||||
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
||||||
|
|
||||||
// Verify project exists if provided
|
// Verify project exists if provided
|
||||||
@@ -228,6 +231,10 @@ export const createTodo = async (userId, data) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTodoCreated(auditContext.userId, newTodo.id, newTodo.title, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return newTodo;
|
return newTodo;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -237,7 +244,7 @@ export const createTodo = async (userId, data) => {
|
|||||||
* @param {object} data - Updated data including assignedUserIds array
|
* @param {object} data - Updated data including assignedUserIds array
|
||||||
* @param {string} updatedByUserId - ID of user making the update (for notifications)
|
* @param {string} updatedByUserId - ID of user making the update (for notifications)
|
||||||
*/
|
*/
|
||||||
export const updateTodo = async (todoId, data, updatedByUserId = null) => {
|
export const updateTodo = async (todoId, data, updatedByUserId = null, auditContext = null) => {
|
||||||
const todo = await getTodoById(todoId);
|
const todo = await getTodoById(todoId);
|
||||||
|
|
||||||
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
||||||
@@ -352,17 +359,32 @@ export const updateTodo = async (todoId, data, updatedByUserId = null) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
// Detect status changes for specific audit events
|
||||||
|
if (status === 'completed' && todo.status !== 'completed') {
|
||||||
|
await logTodoCompleted(auditContext.userId, todoId, todo.title, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
} else if (status && status !== 'completed' && todo.status === 'completed') {
|
||||||
|
await logTodoUncompleted(auditContext.userId, todoId, todo.title, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
} else {
|
||||||
|
await logTodoUpdated(auditContext.userId, todoId, { title: todo.title }, { title: updated.title }, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete todo
|
* Delete todo
|
||||||
*/
|
*/
|
||||||
export const deleteTodo = async (todoId) => {
|
export const deleteTodo = async (todoId, auditContext = null) => {
|
||||||
await getTodoById(todoId); // Check if exists
|
const todo = await getTodoById(todoId); // Check if exists
|
||||||
|
|
||||||
await db.delete(todos).where(eq(todos.id, todoId));
|
await db.delete(todos).where(eq(todos.id, todoId));
|
||||||
|
|
||||||
|
if (auditContext) {
|
||||||
|
await logTodoDeleted(auditContext.userId, todoId, todo.title, auditContext.ipAddress, auditContext.userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
return { success: true, message: 'Todo bolo odstránené' };
|
return { success: true, message: 'Todo bolo odstránené' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user