Files

170 lines
4.6 KiB
JavaScript

/**
* UID Validation Utility
*
* Provides comprehensive UID format validation and sanitization
* to ensure all UIDs are properly formatted as email addresses.
*/
export class UidValidator {
constructor() {
// RFC 5322 compliant email regex (basic validation)
this.emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
// Common invalid patterns to check against
this.invalidPatterns = [
/^devuser$/i, // Legacy username pattern
/^user\d+$/i, // Generic user patterns
/^test$/i, // Test user
/^admin$/i, // Admin user
/^\d+$/, // Pure numeric
/^[a-zA-Z]+$/, // Pure alphabetic (no @ symbol)
];
}
/**
* Validate UID format - must be a valid email address
*/
isValidFormat(uid) {
if (!uid || typeof uid !== 'string') {
return {
valid: false,
error: 'UID must be a non-empty string',
code: 'INVALID_TYPE'
};
}
const trimmed = uid.trim();
if (trimmed.length === 0) {
return {
valid: false,
error: 'UID cannot be empty',
code: 'EMPTY_UID'
};
}
// Check against invalid patterns
for (const pattern of this.invalidPatterns) {
if (pattern.test(trimmed)) {
return {
valid: false,
error: `UID matches invalid pattern: ${pattern}`,
code: 'INVALID_PATTERN'
};
}
}
// Validate email format
if (!this.emailRegex.test(trimmed)) {
return {
valid: false,
error: 'UID must be a valid email address',
code: 'INVALID_EMAIL_FORMAT'
};
}
return {
valid: true,
sanitized: trimmed.toLowerCase()
};
}
/**
* Sanitize and validate UID - ensures consistent format
*/
sanitize(uid) {
const validation = this.isValidFormat(uid);
if (!validation.valid) {
console.error('[UID-VALIDATOR] Validation failed:', validation.error, { uid });
return null;
}
return validation.sanitized;
}
/**
* Validate and throw error if invalid
*/
validateOrThrow(uid, context = 'UID') {
const validation = this.isValidFormat(uid);
if (!validation.valid) {
throw new Error(`${context} validation failed: ${validation.error} (${validation.code})`);
}
return validation.sanitized;
}
/**
* Check if a UID needs migration (legacy format)
*/
needsMigration(uid) {
if (!uid || typeof uid !== 'string') {
return false;
}
const trimmed = uid.trim();
// Check if it's already a valid email
if (this.emailRegex.test(trimmed)) {
return false;
}
// Check if it matches known legacy patterns
for (const pattern of this.invalidPatterns) {
if (pattern.test(trimmed)) {
return true;
}
}
return true; // Any non-email format needs migration
}
/**
* Get validation statistics for debugging
*/
getValidationStats(uids) {
const stats = {
total: uids.length,
valid: 0,
invalid: 0,
needsMigration: 0,
errors: {}
};
uids.forEach(uid => {
const validation = this.isValidFormat(uid);
if (validation.valid) {
stats.valid++;
} else {
stats.invalid++;
const code = validation.code || 'UNKNOWN';
stats.errors[code] = (stats.errors[code] || 0) + 1;
}
if (this.needsMigration(uid)) {
stats.needsMigration++;
}
});
return stats;
}
}
// Create singleton instance
export const uidValidator = new UidValidator();
// Legacy exports for backward compatibility
export function validateUidFormat(uid) {
return uidValidator.isValidFormat(uid).valid;
}
export function sanitizeUid(uid) {
return uidValidator.sanitize(uid);
}
export function validateUidOrThrow(uid, context) {
return uidValidator.validateOrThrow(uid, context);
}