initialize git, basic setup for crm
This commit is contained in:
82
src/utils/errors.js
Normal file
82
src/utils/errors.js
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Custom error classes pre aplikáciu
|
||||
*/
|
||||
|
||||
export class AppError extends Error {
|
||||
constructor(message, statusCode = 500, details = null) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.details = details;
|
||||
this.isOperational = true;
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
export class ValidationError extends AppError {
|
||||
constructor(message, details = null) {
|
||||
super(message, 400, details);
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthenticationError extends AppError {
|
||||
constructor(message = 'Neautorizovaný prístup') {
|
||||
super(message, 401);
|
||||
this.name = 'AuthenticationError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbiddenError extends AppError {
|
||||
constructor(message = 'Prístup zamietnutý') {
|
||||
super(message, 403);
|
||||
this.name = 'ForbiddenError';
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends AppError {
|
||||
constructor(message = 'Nenájdené') {
|
||||
super(message, 404);
|
||||
this.name = 'NotFoundError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictError extends AppError {
|
||||
constructor(message = 'Konflikt') {
|
||||
super(message, 409);
|
||||
this.name = 'ConflictError';
|
||||
}
|
||||
}
|
||||
|
||||
export class RateLimitError extends AppError {
|
||||
constructor(message = 'Príliš veľa požiadaviek') {
|
||||
super(message, 429);
|
||||
this.name = 'RateLimitError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error response formatter
|
||||
* @param {Error} error
|
||||
* @param {boolean} includeStack - Či má zahrnúť stack trace (len development)
|
||||
* @returns {Object} Formatted error response
|
||||
*/
|
||||
export const formatErrorResponse = (error, includeStack = false) => {
|
||||
const response = {
|
||||
success: false,
|
||||
error: {
|
||||
message: error.message || 'Interná chyba servera',
|
||||
statusCode: error.statusCode || 500,
|
||||
},
|
||||
};
|
||||
|
||||
if (error.details) {
|
||||
response.error.details = error.details;
|
||||
}
|
||||
|
||||
if (includeStack && process.env.NODE_ENV === 'development') {
|
||||
response.error.stack = error.stack;
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
85
src/utils/jwt.js
Normal file
85
src/utils/jwt.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
/**
|
||||
* Generuje access JWT token
|
||||
* @param {Object} payload - User data (id, username, role)
|
||||
* @returns {string} JWT token
|
||||
*/
|
||||
export const generateAccessToken = (payload) => {
|
||||
return jwt.sign(payload, process.env.JWT_SECRET, {
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '1h',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generuje refresh JWT token
|
||||
* @param {Object} payload - User data (id)
|
||||
* @returns {string} Refresh token
|
||||
*/
|
||||
export const generateRefreshToken = (payload) => {
|
||||
return jwt.sign(payload, process.env.JWT_REFRESH_SECRET, {
|
||||
expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Overí access JWT token
|
||||
* @param {string} token - JWT token
|
||||
* @returns {Object} Decoded payload
|
||||
* @throws {Error} Ak je token neplatný alebo expirovaný
|
||||
*/
|
||||
export const verifyAccessToken = (token) => {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_SECRET);
|
||||
} catch (error) {
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
throw new Error('Token expiroval');
|
||||
}
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
throw new Error('Neplatný token');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Overí refresh JWT token
|
||||
* @param {string} token - Refresh token
|
||||
* @returns {Object} Decoded payload
|
||||
* @throws {Error} Ak je token neplatný alebo expirovaný
|
||||
*/
|
||||
export const verifyRefreshToken = (token) => {
|
||||
try {
|
||||
return jwt.verify(token, process.env.JWT_REFRESH_SECRET);
|
||||
} catch (error) {
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
throw new Error('Refresh token expiroval');
|
||||
}
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
throw new Error('Neplatný refresh token');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Vytvorí obidva tokeny (access + refresh)
|
||||
* @param {Object} user - User object
|
||||
* @returns {Object} { accessToken, refreshToken }
|
||||
*/
|
||||
export const generateTokenPair = (user) => {
|
||||
const payload = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
};
|
||||
|
||||
const refreshPayload = {
|
||||
id: user.id,
|
||||
};
|
||||
|
||||
return {
|
||||
accessToken: generateAccessToken(payload),
|
||||
refreshToken: generateRefreshToken(refreshPayload),
|
||||
};
|
||||
};
|
||||
66
src/utils/logger.js
Normal file
66
src/utils/logger.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Simple logger utility
|
||||
*/
|
||||
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
};
|
||||
|
||||
const getTimestamp = () => {
|
||||
return new Date().toISOString();
|
||||
};
|
||||
|
||||
export const logger = {
|
||||
info: (message, ...args) => {
|
||||
console.log(
|
||||
`${colors.blue}[INFO]${colors.reset} ${getTimestamp()} - ${message}`,
|
||||
...args
|
||||
);
|
||||
},
|
||||
|
||||
success: (message, ...args) => {
|
||||
console.log(
|
||||
`${colors.green}[SUCCESS]${colors.reset} ${getTimestamp()} - ${message}`,
|
||||
...args
|
||||
);
|
||||
},
|
||||
|
||||
warn: (message, ...args) => {
|
||||
console.warn(
|
||||
`${colors.yellow}[WARN]${colors.reset} ${getTimestamp()} - ${message}`,
|
||||
...args
|
||||
);
|
||||
},
|
||||
|
||||
error: (message, error, ...args) => {
|
||||
console.error(
|
||||
`${colors.red}[ERROR]${colors.reset} ${getTimestamp()} - ${message}`,
|
||||
...args
|
||||
);
|
||||
if (error && process.env.NODE_ENV === 'development') {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
|
||||
debug: (message, ...args) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(
|
||||
`${colors.cyan}[DEBUG]${colors.reset} ${getTimestamp()} - ${message}`,
|
||||
...args
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
audit: (message, ...args) => {
|
||||
console.log(
|
||||
`${colors.magenta}[AUDIT]${colors.reset} ${getTimestamp()} - ${message}`,
|
||||
...args
|
||||
);
|
||||
},
|
||||
};
|
||||
104
src/utils/password.js
Normal file
104
src/utils/password.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import crypto from 'crypto';
|
||||
|
||||
/**
|
||||
* Hashuje heslo pomocou bcrypt
|
||||
* @param {string} password - Plain text heslo
|
||||
* @returns {Promise<string>} Hashované heslo
|
||||
*/
|
||||
export const hashPassword = async (password) => {
|
||||
const rounds = parseInt(process.env.BCRYPT_ROUNDS) || 12;
|
||||
return bcrypt.hash(password, rounds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Porovná plain text heslo s hash
|
||||
* @param {string} password - Plain text heslo
|
||||
* @param {string} hash - Hashované heslo
|
||||
* @returns {Promise<boolean>} True ak sa zhodujú
|
||||
*/
|
||||
export const comparePassword = async (password, hash) => {
|
||||
return bcrypt.compare(password, hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generuje náhodné dočasné heslo
|
||||
* @param {number} length - Dĺžka hesla (default: 12)
|
||||
* @returns {string} Náhodné heslo
|
||||
*/
|
||||
export const generateTempPassword = (length = 12) => {
|
||||
const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
const lowercase = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const numbers = '0123456789';
|
||||
const special = '!@#$%^&*';
|
||||
const all = uppercase + lowercase + numbers + special;
|
||||
|
||||
let password = '';
|
||||
|
||||
// Zaručí aspoň jeden znak z každej kategórie
|
||||
password += uppercase[Math.floor(Math.random() * uppercase.length)];
|
||||
password += lowercase[Math.floor(Math.random() * lowercase.length)];
|
||||
password += numbers[Math.floor(Math.random() * numbers.length)];
|
||||
password += special[Math.floor(Math.random() * special.length)];
|
||||
|
||||
// Zvyšok náhodne
|
||||
for (let i = password.length; i < length; i++) {
|
||||
password += all[Math.floor(Math.random() * all.length)];
|
||||
}
|
||||
|
||||
// Zamieša znaky
|
||||
return password
|
||||
.split('')
|
||||
.sort(() => Math.random() - 0.5)
|
||||
.join('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Generuje náhodný verification token
|
||||
* @returns {string} UUID token
|
||||
*/
|
||||
export const generateVerificationToken = () => {
|
||||
return crypto.randomUUID();
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypt emailPassword using AES-256-GCM
|
||||
* @param {string} text - Plain text password
|
||||
* @returns {string} Encrypted password in format: iv:authTag:encrypted
|
||||
*/
|
||||
export const encryptPassword = (text) => {
|
||||
const algorithm = 'aes-256-gcm';
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET || 'default-secret', 'salt', 32);
|
||||
const iv = crypto.randomBytes(16);
|
||||
|
||||
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt emailPassword
|
||||
* @param {string} encryptedText - Encrypted password in format: iv:authTag:encrypted
|
||||
* @returns {string} Plain text password
|
||||
*/
|
||||
export const decryptPassword = (encryptedText) => {
|
||||
const algorithm = 'aes-256-gcm';
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET || 'default-secret', 'salt', 32);
|
||||
|
||||
const parts = encryptedText.split(':');
|
||||
const iv = Buffer.from(parts[0], 'hex');
|
||||
const authTag = Buffer.from(parts[1], 'hex');
|
||||
const encrypted = parts[2];
|
||||
|
||||
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return decrypted;
|
||||
};
|
||||
Reference in New Issue
Block a user