170 lines
4.6 KiB
JavaScript
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);
|
|
}
|