WellNuo/backend/src/utils/serialValidation.js
Sergei bbb60a9e3f Extract magic numbers to centralized constants module
Created backend/src/config/constants.js to centralize all magic numbers
and configuration values used throughout the backend codebase.

Changes:
- Created constants.js with organized sections for:
  - SECURITY: JWT, rate limiting, password reset
  - AUTH: OTP configuration and rate limiting
  - SERVER: Port, body limits, startup delays
  - MQTT: Connection settings, cache limits
  - NOTIFICATIONS: Push settings, quiet hours, batching
  - SERIAL: Validation patterns and constraints
  - EMAIL: Template settings and defaults
  - CRON: Schedule configurations
  - STORAGE: Avatar storage settings

- Updated files to use constants:
  - index.js: JWT validation, rate limits, startup delays
  - routes/auth.js: OTP generation, rate limits, JWT expiry
  - services/mqtt.js: Connection timeouts, cache size
  - services/notifications.js: Batch size, TTL, quiet hours
  - utils/serialValidation.js: Serial number constraints

- Added comprehensive test suite (30 tests) for constants module
  - All tests passing (93 total including existing tests)
  - Validates reasonable values and consistency between related constants

Benefits:
- Single source of truth for configuration values
- Easier to maintain and update settings
- Better documentation of what each value represents
- Improved code readability by removing hardcoded numbers
- Testable configuration values

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-29 11:52:47 -08:00

136 lines
3.1 KiB
JavaScript

/**
* Serial Number Validation Utilities (Backend)
*
* WellNuo device serial numbers follow formats:
* - Production: WELLNUO-XXXX-XXXX (16 chars with hyphens)
* - Demo: DEMO-00000 or DEMO-1234-5678
* - Legacy: 8+ alphanumeric characters
*/
const { SERIAL } = require('../config/constants');
// Demo serial numbers
const DEMO_SERIALS = new Set([
'DEMO-00000',
'DEMO-1234-5678',
]);
/**
* Validates a serial number and returns detailed result
* @param {string|null|undefined} serial - Serial number to validate
* @returns {{isValid: boolean, format: string, normalized: string, error?: string}}
*/
function validateSerial(serial) {
// Handle empty input
if (!serial || typeof serial !== 'string') {
return {
isValid: false,
format: 'invalid',
normalized: '',
error: 'Serial number is required',
};
}
// Normalize: trim and uppercase
const normalized = serial.trim().toUpperCase();
if (normalized.length === 0) {
return {
isValid: false,
format: 'invalid',
normalized: '',
error: 'Serial number is required',
};
}
// Check for demo serial
if (DEMO_SERIALS.has(normalized)) {
return {
isValid: true,
format: 'demo',
normalized,
};
}
// Production format: WELLNUO-XXXX-XXXX
// Must be exactly 16 characters with hyphens at positions 7 and 12
if (SERIAL.PRODUCTION_PATTERN.test(normalized)) {
return {
isValid: true,
format: 'production',
normalized,
};
}
// Legacy format: minimum 8 alphanumeric characters (no special characters except hyphens)
if (SERIAL.LEGACY_PATTERN.test(normalized)) {
return {
isValid: true,
format: 'legacy',
normalized,
};
}
// Invalid format
let error = 'Invalid serial number format';
if (normalized.length < SERIAL.MIN_LENGTH) {
error = `Serial number must be at least ${SERIAL.MIN_LENGTH} characters`;
} else if (!SERIAL.VALID_CHARS_PATTERN.test(normalized)) {
error = 'Serial number can only contain letters, numbers, and hyphens';
}
return {
isValid: false,
format: 'invalid',
normalized,
error,
};
}
/**
* Quick validation - returns true/false only
* @param {string|null|undefined} serial - Serial number to validate
* @returns {boolean}
*/
function isValidSerial(serial) {
return validateSerial(serial).isValid;
}
/**
* Check if serial is a demo serial
* @param {string|null|undefined} serial - Serial number to check
* @returns {boolean}
*/
function isDemoSerial(serial) {
if (!serial) return false;
const normalized = serial.trim().toUpperCase();
return DEMO_SERIALS.has(normalized);
}
/**
* Get user-friendly error message for invalid serial
* @param {string|null|undefined} serial - Serial number to validate
* @returns {string}
*/
function getSerialErrorMessage(serial) {
const result = validateSerial(serial);
if (result.isValid) {
return '';
}
if (result.error) {
return result.error;
}
return 'Invalid serial number format';
}
module.exports = {
validateSerial,
isValidSerial,
isDemoSerial,
getSerialErrorMessage,
};