MEDIUM-002: Remove manual X-Forwarded-For parsing in metrics_protection.go, use c.ClientIP() only (respects SetTrustedProxies) MEDIUM-003: Pin ClamAV Docker image to 1.4 across all compose files MEDIUM-004: Add clampLimit(100) to 15+ handlers that parsed limit directly MEDIUM-006: Remove unsafe-eval from CSP script-src on Swagger routes MEDIUM-007: Pin all GitHub Actions to SHA in 11 workflow files MEDIUM-008: Replace rabbitmq:3-management-alpine with rabbitmq:3-alpine in prod MEDIUM-009: Add trial-already-used check in subscription service MEDIUM-010: Add 60s periodic token re-validation to WebSocket connections MEDIUM-011: Mask email in auth handler logs with maskEmail() helper MEDIUM-012: Add k-anonymity threshold (k=5) to playback analytics stats LOW-001: Align frontend password policy to 12 chars (matching backend) LOW-003: Replace deprecated dotenv with dotenvy crate in Rust stream server LOW-004: Enable xpack.security in Elasticsearch dev/local compose files LOW-005: Accept context.Context in CleanupExpiredSessions instead of Background() LOW-002: Noted — Hyperswitch version update deferred (requires payment integration tests) 29/30 findings remediated. 1 noted (LOW-002). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
2.4 KiB
TypeScript
99 lines
2.4 KiB
TypeScript
/**
|
|
* Password Validator
|
|
* T0197: Validates password strength
|
|
* SECURITY(LOW-001): Minimum 12 characters to match backend policy (validators/password_validator.go)
|
|
*/
|
|
|
|
export interface PasswordValidationResult {
|
|
isValid: boolean;
|
|
errors: string[];
|
|
strength: {
|
|
length: boolean;
|
|
upper: boolean;
|
|
lower: boolean;
|
|
number: boolean;
|
|
special: boolean;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validates password strength according to security rules
|
|
* @param password - The password to validate
|
|
* @returns Validation result with isValid flag, errors array, and strength checks
|
|
*/
|
|
export function validatePasswordStrength(
|
|
password: string,
|
|
): PasswordValidationResult {
|
|
const errors: string[] = [];
|
|
const strength = {
|
|
length: false,
|
|
upper: false,
|
|
lower: false,
|
|
number: false,
|
|
special: false,
|
|
};
|
|
|
|
// SECURITY(LOW-001): Length check — minimum 12 characters (aligned with backend)
|
|
if (password.length < 12) {
|
|
errors.push('password must be at least 12 characters');
|
|
} else {
|
|
strength.length = true;
|
|
}
|
|
|
|
// Maximum length check
|
|
if (password.length > 128) {
|
|
errors.push('password must be less than 128 characters');
|
|
}
|
|
|
|
// Upper case check
|
|
if (!/[A-Z]/.test(password)) {
|
|
errors.push('password must contain at least one uppercase letter');
|
|
} else {
|
|
strength.upper = true;
|
|
}
|
|
|
|
// Lower case check
|
|
if (!/[a-z]/.test(password)) {
|
|
errors.push('password must contain at least one lowercase letter');
|
|
} else {
|
|
strength.lower = true;
|
|
}
|
|
|
|
// Number check
|
|
if (!/[0-9]/.test(password)) {
|
|
errors.push('password must contain at least one number');
|
|
} else {
|
|
strength.number = true;
|
|
}
|
|
|
|
// Special character check
|
|
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
|
errors.push('password must contain at least one special character');
|
|
} else {
|
|
strength.special = true;
|
|
}
|
|
|
|
return {
|
|
isValid: errors.length === 0,
|
|
errors,
|
|
strength,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Calculates password strength score (0-5)
|
|
* @param password - The password to score
|
|
* @returns Score from 0 to 5
|
|
*/
|
|
export function calculatePasswordStrength(password: string): number {
|
|
const result = validatePasswordStrength(password);
|
|
let score = 0;
|
|
|
|
if (result.strength.length) score++;
|
|
if (result.strength.upper) score++;
|
|
if (result.strength.lower) score++;
|
|
if (result.strength.number) score++;
|
|
if (result.strength.special) score++;
|
|
|
|
return score;
|
|
}
|