feat: implement automation key management with middleware and routes for organization API keys, invite improvements
This commit is contained in:
@@ -1,37 +1,22 @@
|
||||
/**
|
||||
* Server-side file type validation utilities for preventing malicious uploads
|
||||
* Specifically designed to prevent SVG XSS attacks and other security issues
|
||||
*/
|
||||
import { ALLOWED_IMAGE_TYPES } from '../validation/constants';
|
||||
|
||||
export const ALLOWED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'] as const;
|
||||
|
||||
/**
|
||||
* Validate file type by MIME type
|
||||
* @param file - File object to validate
|
||||
* @returns boolean indicating if file type is allowed
|
||||
*/
|
||||
export function validateImageType(file: File): boolean {
|
||||
type FileLike = {
|
||||
type: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export function validateImageType(file: FileLike): boolean {
|
||||
return ALLOWED_IMAGE_TYPES.includes(file.type as (typeof ALLOWED_IMAGE_TYPES)[number]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file extension
|
||||
* @param filename - Filename to validate
|
||||
* @returns boolean indicating if extension is allowed
|
||||
*/
|
||||
export function validateImageExtension(filename: string): boolean {
|
||||
const extension = filename.toLowerCase().substring(filename.lastIndexOf('.'));
|
||||
return ALLOWED_EXTENSIONS.includes(extension as (typeof ALLOWED_EXTENSIONS)[number]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprehensive file validation
|
||||
* @param file - File object to validate
|
||||
* @returns object with validation result and error message
|
||||
*/
|
||||
export function validateImageUpload(file: File): { isValid: boolean; error?: string } {
|
||||
// Check file type
|
||||
export function validateImageUpload(file: FileLike): { isValid: boolean; error?: string } {
|
||||
if (!validateImageType(file)) {
|
||||
return {
|
||||
isValid: false,
|
||||
@@ -39,7 +24,6 @@ export function validateImageUpload(file: File): { isValid: boolean; error?: str
|
||||
};
|
||||
}
|
||||
|
||||
// Check file extension (double check for security)
|
||||
if (!validateImageExtension(file.name)) {
|
||||
return {
|
||||
isValid: false,
|
||||
@@ -47,7 +31,6 @@ export function validateImageUpload(file: File): { isValid: boolean; error?: str
|
||||
};
|
||||
}
|
||||
|
||||
// Additional security checks
|
||||
if (file.name.toLowerCase().includes('.svg')) {
|
||||
return {
|
||||
isValid: false,
|
||||
@@ -58,23 +41,15 @@ export function validateImageUpload(file: File): { isValid: boolean; error?: str
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize filename to prevent directory traversal and other issues
|
||||
* @param filename - Original filename
|
||||
* @returns sanitized filename
|
||||
*/
|
||||
export function sanitizeFilename(filename: string): string {
|
||||
// Remove path traversal attempts
|
||||
let sanitized = filename.replace(/[/\\]/g, '');
|
||||
|
||||
// Remove special characters that could cause issues
|
||||
let sanitized = filename.replace(/[\/\\]/g, '');
|
||||
sanitized = sanitized.replace(/[<>:"|?*]/g, '');
|
||||
|
||||
// Limit length
|
||||
if (sanitized.length > 255) {
|
||||
const ext = sanitized.substring(sanitized.lastIndexOf('.'));
|
||||
const name = sanitized.substring(0, sanitized.lastIndexOf('.'));
|
||||
sanitized = name.substring(0, 255 - ext.length) + ext;
|
||||
const extIndex = sanitized.lastIndexOf('.');
|
||||
const ext = extIndex !== -1 ? sanitized.substring(extIndex) : '';
|
||||
const name = extIndex !== -1 ? sanitized.substring(0, extIndex) : sanitized;
|
||||
sanitized = `${name.substring(0, 255 - ext.length)}${ext}`;
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './fileValidation';
|
||||
export * from './array';
|
||||
export * from './fileValidation';
|
||||
export * from './slug';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export type GenerateSlugOptions = {
|
||||
fallback?: string;
|
||||
appendTimestamp?: boolean;
|
||||
};
|
||||
|
||||
export function generateSlug(source: string | null | undefined, options: GenerateSlugOptions = {}): string {
|
||||
const { fallback = 'item', appendTimestamp = false } = options;
|
||||
|
||||
const base =
|
||||
(source ?? fallback)
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '') || fallback;
|
||||
|
||||
return appendTimestamp ? `${base}-${Date.now()}` : base;
|
||||
}
|
||||
Reference in New Issue
Block a user