Initial commit: StreamFlow IPTV platform

This commit is contained in:
aiulian25 2025-12-17 00:42:43 +00:00
commit 73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions

View file

@ -0,0 +1,360 @@
/**
* Validation Middleware
* Provides reusable validation middleware for common request patterns
*/
const {
validateUsername,
validateEmail,
validateUrl,
validatePlaylistName,
validateChannelName,
validateDescription,
validateFilename,
validateSettingKey,
validateInteger,
validateBoolean,
validateJSON,
sanitizeObject
} = require('../utils/inputValidator');
const logger = require('../utils/logger');
/**
* Generic validation middleware factory
*/
function createValidationMiddleware(validators) {
return (req, res, next) => {
const errors = {};
const sanitized = {};
let hasErrors = false;
// Validate body parameters
if (validators.body) {
for (const [field, validator] of Object.entries(validators.body)) {
const value = req.body[field];
const result = validator(value);
if (!result.valid) {
errors[field] = result.errors;
hasErrors = true;
} else if (result.sanitized !== undefined) {
sanitized[field] = result.sanitized;
}
}
}
// Validate query parameters
if (validators.query) {
for (const [field, validator] of Object.entries(validators.query)) {
const value = req.query[field];
const result = validator(value);
if (!result.valid) {
errors[`query.${field}`] = result.errors;
hasErrors = true;
} else if (result.sanitized !== undefined) {
if (!req.sanitizedQuery) req.sanitizedQuery = {};
req.sanitizedQuery[field] = result.sanitized;
}
}
}
// Validate params
if (validators.params) {
for (const [field, validator] of Object.entries(validators.params)) {
const value = req.params[field];
const result = validator(value);
if (!result.valid) {
errors[`params.${field}`] = result.errors;
hasErrors = true;
} else if (result.sanitized !== undefined) {
if (!req.sanitizedParams) req.sanitizedParams = {};
req.sanitizedParams[field] = result.sanitized;
}
}
}
if (hasErrors) {
logger.warn('Validation failed:', { errors, path: req.path, ip: req.ip });
return res.status(400).json({
error: 'Validation failed',
details: errors
});
}
// Replace request data with sanitized versions
if (Object.keys(sanitized).length > 0) {
req.body = { ...req.body, ...sanitized };
}
next();
};
}
/**
* Validate playlist creation/update
*/
const validatePlaylist = createValidationMiddleware({
body: {
name: validatePlaylistName,
url: (value) => validateUrl(value, false),
category: (value) => {
if (!value) return { valid: true, errors: [], sanitized: null };
return validateDescription(value);
},
type: (value) => {
if (!value) return { valid: true, errors: [], sanitized: 'live' };
const allowed = ['live', 'vod', 'series', 'radio'];
if (!allowed.includes(value)) {
return { valid: false, errors: ['Invalid playlist type'], sanitized: null };
}
return { valid: true, errors: [], sanitized: value };
}
}
});
/**
* Validate channel update
*/
const validateChannelUpdate = createValidationMiddleware({
body: {
name: (value) => {
if (!value) return { valid: true, errors: [], sanitized: undefined };
return validateChannelName(value);
},
group_name: (value) => {
if (!value) return { valid: true, errors: [], sanitized: undefined };
return validateDescription(value);
}
}
});
/**
* Validate settings
*/
const validateSettings = createValidationMiddleware({
params: {
key: validateSettingKey
},
body: {
value: (value) => {
// Settings can be strings, numbers, booleans, or JSON objects
if (value === undefined || value === null) {
return { valid: false, errors: ['Value is required'], sanitized: null };
}
// If it's an object, validate as JSON
if (typeof value === 'object') {
return validateJSON(value, 100000);
}
return { valid: true, errors: [], sanitized: value };
}
}
});
/**
* Validate ID parameter
*/
const validateIdParam = createValidationMiddleware({
params: {
id: (value) => validateInteger(value, 1, Number.MAX_SAFE_INTEGER)
}
});
/**
* Validate channelId parameter
*/
const validateChannelIdParam = createValidationMiddleware({
params: {
channelId: (value) => validateInteger(value, 1, Number.MAX_SAFE_INTEGER)
}
});
/**
* Validate pagination parameters
*/
const validatePagination = createValidationMiddleware({
query: {
limit: (value) => {
if (!value) return { valid: true, errors: [], sanitized: 100 };
return validateInteger(value, 1, 1000);
},
offset: (value) => {
if (!value) return { valid: true, errors: [], sanitized: 0 };
return validateInteger(value, 0, Number.MAX_SAFE_INTEGER);
}
}
});
/**
* Validate search query
*/
const validateSearch = createValidationMiddleware({
query: {
search: (value) => {
if (!value) return { valid: true, errors: [], sanitized: undefined };
return validateDescription(value);
},
q: (value) => {
if (!value) return { valid: true, errors: [], sanitized: undefined };
return validateDescription(value);
}
}
});
/**
* Validate user creation
*/
const validateUserCreation = createValidationMiddleware({
body: {
username: validateUsername,
email: validateEmail,
password: (value) => {
if (!value || typeof value !== 'string') {
return { valid: false, errors: ['Password is required'], sanitized: null };
}
if (value.length < 8) {
return { valid: false, errors: ['Password must be at least 8 characters'], sanitized: null };
}
if (value.length > 128) {
return { valid: false, errors: ['Password must not exceed 128 characters'], sanitized: null };
}
return { valid: true, errors: [], sanitized: value };
},
role: (value) => {
const allowed = ['user', 'admin'];
if (!allowed.includes(value)) {
return { valid: false, errors: ['Invalid role'], sanitized: null };
}
return { valid: true, errors: [], sanitized: value };
}
}
});
/**
* Validate user update
*/
const validateUserUpdate = createValidationMiddleware({
params: {
id: (value) => validateInteger(value, 1, Number.MAX_SAFE_INTEGER)
},
body: {
username: (value) => {
if (!value) return { valid: true, errors: [], sanitized: undefined };
return validateUsername(value);
},
email: (value) => {
if (!value) return { valid: true, errors: [], sanitized: undefined };
return validateEmail(value);
},
role: (value) => {
if (!value) return { valid: true, errors: [], sanitized: undefined };
const allowed = ['user', 'admin'];
if (!allowed.includes(value)) {
return { valid: false, errors: ['Invalid role'], sanitized: null };
}
return { valid: true, errors: [], sanitized: value };
},
is_active: (value) => {
if (value === undefined) return { valid: true, errors: [], sanitized: undefined };
return validateBoolean(value);
}
}
});
/**
* Validate filename for file operations
*/
const validateFileOperation = createValidationMiddleware({
params: {
filename: validateFilename
}
});
/**
* Validate bulk delete operations
*/
const validateBulkDelete = createValidationMiddleware({
body: {
ids: (value) => {
if (!Array.isArray(value)) {
return { valid: false, errors: ['IDs must be an array'], sanitized: null };
}
if (value.length === 0) {
return { valid: false, errors: ['At least one ID required'], sanitized: null };
}
if (value.length > 1000) {
return { valid: false, errors: ['Cannot delete more than 1000 items at once'], sanitized: null };
}
const errors = [];
const sanitized = [];
for (let i = 0; i < value.length; i++) {
const result = validateInteger(value[i], 1, Number.MAX_SAFE_INTEGER);
if (!result.valid) {
errors.push(`Invalid ID at index ${i}`);
} else {
sanitized.push(result.sanitized);
}
}
if (errors.length > 0) {
return { valid: false, errors, sanitized: null };
}
return { valid: true, errors: [], sanitized };
}
}
});
/**
* Generic text field validator
*/
function validateTextField(maxLength = 1000, required = true) {
return (value) => {
if (!value || value === '') {
if (required) {
return { valid: false, errors: ['This field is required'], sanitized: null };
}
return { valid: true, errors: [], sanitized: '' };
}
if (typeof value !== 'string') {
return { valid: false, errors: ['Must be a string'], sanitized: null };
}
const trimmed = value.trim();
if (required && trimmed.length === 0) {
return { valid: false, errors: ['This field is required'], sanitized: null };
}
if (trimmed.length > maxLength) {
return { valid: false, errors: [`Must not exceed ${maxLength} characters`], sanitized: null };
}
const result = validateDescription(trimmed);
return result;
};
}
module.exports = {
createValidationMiddleware,
validatePlaylist,
validateChannelUpdate,
validateSettings,
validateIdParam,
validateChannelIdParam,
validatePagination,
validateSearch,
validateUserCreation,
validateUserUpdate,
validateFileOperation,
validateBulkDelete,
validateTextField
};