feat: implement automation key management with middleware and routes for organization API keys, invite improvements

This commit is contained in:
Rotimi Best
2026-03-13 18:40:07 +01:00
parent 77a1188573
commit 492f8dc5ed
71 changed files with 3655 additions and 456 deletions
+12 -37
View File
@@ -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;
+2 -1
View File
@@ -1,2 +1,3 @@
export * from './fileValidation';
export * from './array';
export * from './fileValidation';
export * from './slug';
+19
View File
@@ -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;
}