WellNuo/utils/wifiValidation.ts
Sergei 5a6c80533e Add comprehensive WiFi credentials validation
Implement WiFi SSID and password validation following IEEE 802.11 standards:

Features:
- SSID validation: length (max 32 bytes), control characters, whitespace warnings
- Password validation for WPA/WPA2/WPA3 (8-63 chars), WEP, and Open networks
- Weak password detection with warnings
- Non-ASCII character warnings
- Input sanitization (trim SSID, preserve password spaces)
- User-friendly error messages

Integration:
- Added validation to wifi-setup.tsx (ESP provisioning flow)
- Added validation to setup-wifi.tsx (batch sensor setup flow)
- Pre-provisioning validation with error/warning alerts
- Password requirements hints in UI

Tests:
- Comprehensive test suite with 42 test cases
- 100% coverage of validation logic
- Tests for all auth types (WPA, WEP, Open)
- Edge cases and warning scenarios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 16:55:27 -08:00

236 lines
6.5 KiB
TypeScript

/**
* WiFi Credentials Validation Utility
*
* Provides comprehensive validation for WiFi SSID and password
* following IEEE 802.11 standards and common router implementations.
*/
export interface WiFiValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}
export interface WiFiCredentials {
ssid: string;
password: string;
}
/**
* Validate WiFi SSID
*
* Rules:
* - Length: 1-32 characters (IEEE 802.11 standard)
* - Can contain: letters, numbers, spaces, and special characters
* - Cannot be empty
* - Should not contain control characters
*/
export function validateSSID(ssid: string): WiFiValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Check for empty SSID
if (!ssid || ssid.trim().length === 0) {
errors.push('Network name (SSID) cannot be empty');
return { valid: false, errors, warnings };
}
// Check length (IEEE 802.11 standard: max 32 bytes)
// Note: UTF-8 characters can be multiple bytes
const byteLength = new Blob([ssid]).size;
if (byteLength > 32) {
errors.push('Network name is too long (max 32 bytes)');
}
// Check for control characters (ASCII 0-31)
if (/[\x00-\x1F]/.test(ssid)) {
errors.push('Network name contains invalid control characters');
}
// Warnings for potentially problematic SSIDs
if (ssid.startsWith(' ') || ssid.endsWith(' ')) {
warnings.push('Network name has leading or trailing spaces - this may cause connection issues');
}
if (/\s{2,}/.test(ssid)) {
warnings.push('Network name has multiple consecutive spaces');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate WiFi Password
*
* Rules based on WPA/WPA2/WPA3 standards:
* - WPA/WPA2-PSK: 8-63 characters (ASCII)
* - Open networks: no password required
* - WEP: 5 or 13 characters (ASCII) or 10/26 hex digits (deprecated)
*
* Additional checks:
* - Should not be empty for secured networks
* - Should not contain control characters
* - Common weak passwords warning
*/
export function validatePassword(
password: string,
authType?: string
): WiFiValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Open networks don't need password
if (authType === 'Open') {
return { valid: true, errors, warnings };
}
// Check for empty password on secured networks
if (!password || password.length === 0) {
errors.push('Password is required for secured networks');
return { valid: false, errors, warnings };
}
// WPA/WPA2/WPA3 validation (most common)
if (!authType || authType.includes('WPA')) {
// Length check: 8-63 characters
if (password.length < 8) {
errors.push('Password must be at least 8 characters for WPA/WPA2/WPA3 networks');
}
if (password.length > 63) {
errors.push('Password cannot exceed 63 characters for WPA/WPA2/WPA3 networks');
}
// Check for ASCII only (non-ASCII can cause issues)
if (!/^[\x20-\x7E]*$/.test(password)) {
warnings.push('Password contains non-ASCII characters - this may cause issues on some devices');
}
}
// WEP validation (deprecated but still exists)
if (authType === 'WEP') {
const isValidWEP =
password.length === 5 || // 40-bit WEP
password.length === 13 || // 104-bit WEP
(password.length === 10 && /^[0-9A-Fa-f]+$/.test(password)) || // 40-bit hex
(password.length === 26 && /^[0-9A-Fa-f]+$/.test(password)); // 104-bit hex
if (!isValidWEP) {
errors.push('WEP password must be 5 or 13 ASCII characters, or 10/26 hexadecimal digits');
}
warnings.push('WEP is deprecated and insecure - consider upgrading your router to WPA2/WPA3');
}
// Check for control characters
if (/[\x00-\x1F]/.test(password)) {
errors.push('Password contains invalid control characters');
}
// Weak password warnings
if (password.length < 12 && !errors.length) {
warnings.push('Password is weak - consider using at least 12 characters');
}
// Common weak passwords
const weakPasswords = [
'password', '12345678', 'qwerty123', 'abc123456',
'password1', 'password123', '11111111', '00000000'
];
if (weakPasswords.includes(password.toLowerCase())) {
warnings.push('This is a commonly used weak password');
}
// Check for only numbers
if (/^\d+$/.test(password) && password.length === 8) {
warnings.push('Password contains only numbers - consider adding letters and symbols');
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate complete WiFi credentials (SSID + password)
*/
export function validateWiFiCredentials(
ssid: string,
password: string,
authType?: string
): WiFiValidationResult {
const ssidResult = validateSSID(ssid);
const passwordResult = validatePassword(password, authType);
return {
valid: ssidResult.valid && passwordResult.valid,
errors: [...ssidResult.errors, ...passwordResult.errors],
warnings: [...ssidResult.warnings, ...passwordResult.warnings],
};
}
/**
* Check if password is strong enough for production use
* More strict than basic validation
*/
export function isStrongPassword(password: string): boolean {
if (password.length < 12) return false;
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[^A-Za-z0-9]/.test(password);
// Require at least 3 of 4 character types
const typesCount = [hasUppercase, hasLowercase, hasNumber, hasSpecial].filter(Boolean).length;
return typesCount >= 3;
}
/**
* Sanitize WiFi credentials before sending to device
* Trims whitespace and performs basic cleanup
*/
export function sanitizeWiFiCredentials(credentials: WiFiCredentials): WiFiCredentials {
return {
ssid: credentials.ssid.trim(),
password: credentials.password, // Don't trim password - spaces might be intentional
};
}
/**
* Get user-friendly error message for validation result
*/
export function getValidationErrorMessage(result: WiFiValidationResult): string {
if (result.valid) return '';
if (result.errors.length > 0) {
return result.errors[0]; // Return first error
}
return 'Invalid WiFi credentials';
}
/**
* Get combined error and warning message
*/
export function getValidationMessage(result: WiFiValidationResult): string {
const messages: string[] = [];
if (result.errors.length > 0) {
messages.push(...result.errors);
}
if (result.warnings.length > 0) {
messages.push(...result.warnings);
}
return messages.join('\n');
}