ajt-validator
Version:
Validation library for JavaScript and TypeScript
281 lines (280 loc) • 12.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PassportValidator = exports.PASSPORT_FORMATS = exports.PassportAuthority = void 0;
const base_1 = require("../base");
/**
* Standard passport issuing countries/authorities
* ISO 3166-1 alpha-3 country codes for most common passport issuers
*/
var PassportAuthority;
(function (PassportAuthority) {
PassportAuthority["USA"] = "USA";
PassportAuthority["GBR"] = "GBR";
PassportAuthority["CAN"] = "CAN";
PassportAuthority["AUS"] = "AUS";
PassportAuthority["NZL"] = "NZL";
PassportAuthority["DEU"] = "DEU";
PassportAuthority["FRA"] = "FRA";
PassportAuthority["ESP"] = "ESP";
PassportAuthority["ITA"] = "ITA";
PassportAuthority["JPN"] = "JPN";
PassportAuthority["CHN"] = "CHN";
PassportAuthority["IND"] = "IND";
PassportAuthority["RUS"] = "RUS";
PassportAuthority["BRA"] = "BRA";
PassportAuthority["ZAF"] = "ZAF";
// Additional entries can be added as needed
})(PassportAuthority || (exports.PassportAuthority = PassportAuthority = {}));
/**
* Common passport formats by country/authority
* Maps country codes to regex patterns for their passport numbers
*/
exports.PASSPORT_FORMATS = {
// United States - 9 digits
[PassportAuthority.USA]: /^[A-Z0-9]{9}$/,
// United Kingdom - 9 digits (new format)
[PassportAuthority.GBR]: /^[0-9]{9}$/,
// Canada - 2 letters followed by 6 digits
[PassportAuthority.CAN]: /^[A-Z]{2}[0-9]{6}$/,
// Australia - 1 letter followed by 7 digits
[PassportAuthority.AUS]: /^[A-Z][0-9]{7}$/,
// New Zealand - 1 letter followed by 7 digits
[PassportAuthority.NZL]: /^[A-Z][0-9]{7}$/,
// Germany - 10 characters (letters and numbers)
[PassportAuthority.DEU]: /^[A-Z0-9]{10}$/,
// France - 9 digits
[PassportAuthority.FRA]: /^[0-9]{9}$/,
// Spain - 3 letters followed by 6 digits
[PassportAuthority.ESP]: /^[A-Z]{3}[0-9]{6}$/,
// Italy - 2 letters followed by 7 digits
[PassportAuthority.ITA]: /^[A-Z]{2}[0-9]{7}$/,
// Japan - 2 letters followed by 7 digits
[PassportAuthority.JPN]: /^[A-Z]{2}[0-9]{7}$/,
// China - 'G' followed by 8 digits or 'E' followed by 8 digits
[PassportAuthority.CHN]: /^[EG][0-9]{8}$/,
// India - 1 letter followed by 7 digits
[PassportAuthority.IND]: /^[A-Z][0-9]{7}$/,
// Russia - 9 digits
[PassportAuthority.RUS]: /^[0-9]{9}$/,
// Brazil - 2 letters followed by 6 digits
[PassportAuthority.BRA]: /^[A-Z]{2}[0-9]{6}$/,
// South Africa - 1 letter followed by 8 digits
[PassportAuthority.ZAF]: /^[A-Z][0-9]{8}$/,
// Generic format for other countries (more permissive)
generic: /^[A-Z0-9]{5,12}$/
};
/**
* Validator for passport number fields
* Supports various passport formats and validation rules by country
*/
class PassportValidator extends base_1.BaseValidator {
/**
* Create a new passport validator
* @param options Configuration options
*/
constructor(options = {}) {
super();
// Initialize default options
this.options = Object.assign({ allowedAuthorities: Object.values(PassportAuthority), normalize: true, validateExpiration: false, minimumValidityDays: 180, validateChecksums: false, maxPassportAge: 10, allowUnknownAuthorities: true }, options);
// Combine standard formats with any additional formats
this.formats = Object.assign(Object.assign({}, exports.PASSPORT_FORMATS), (this.options.additionalFormats || {}));
}
/**
* Validate a passport number and optional expiration date
* @param value Passport number or object with number and expiration
* @returns Validation result with additional metadata
*/
validate(value) {
// Handle null/empty values
if (value === null || value === undefined || value === '') {
return this.createError('PASSPORT_REQUIRED', 'Passport information is required');
}
let passportNumber;
let expirationDate;
let issuingAuthority;
// Handle object input with number and expiration
if (typeof value === 'object') {
passportNumber = value.number;
expirationDate = value.expirationDate ? new Date(value.expirationDate) : undefined;
issuingAuthority = value.authority;
if (!passportNumber) {
return this.createError('PASSPORT_NUMBER_REQUIRED', 'Passport number is required');
}
}
else {
// Simple string input
passportNumber = String(value);
}
let normalizedNumber = passportNumber;
// Normalize if enabled
if (this.options.normalize) {
normalizedNumber = passportNumber.trim().toUpperCase();
}
// Determine the authority if not provided
const authority = issuingAuthority || this.detectAuthority(normalizedNumber);
// Validate against known format if authority is recognized
if (authority) {
// Check if this authority is allowed
if (this.options.allowedAuthorities &&
!this.options.allowedAuthorities.includes(authority)) {
return this.createError('PASSPORT_AUTHORITY_NOT_ALLOWED', `Passport issuing authority ${authority} is not in the list of allowed authorities`);
}
// Validate format
if (!this.validateFormat(normalizedNumber, authority)) {
return this.createError('INVALID_PASSPORT_FORMAT', `Passport number format is invalid for ${authority}`);
}
// Validate checksum if enabled and supported
if (this.options.validateChecksums) {
const checksumValid = this.validateChecksum(normalizedNumber, authority);
if (checksumValid === false) { // Only fail if explicitly invalid, not if unsupported
return this.createError('INVALID_PASSPORT_CHECKSUM', `Passport number checksum validation failed for ${authority}`);
}
}
}
else if (!this.options.allowUnknownAuthorities) {
// Unknown authority and not allowed
return this.createError('UNKNOWN_PASSPORT_AUTHORITY', 'Could not determine passport issuing authority and unknown authorities are not allowed');
}
else {
// Unknown authority but allowed using generic validation
if (!this.validateFormat(normalizedNumber, 'generic')) {
return this.createError('INVALID_PASSPORT_FORMAT', 'Passport number format is invalid for generic validation');
}
}
// Validate expiration if enabled and date provided
let hasValidExpiration = undefined;
let daysToExpiration = undefined;
if (this.options.validateExpiration && expirationDate) {
// Check if date is valid
if (isNaN(expirationDate.getTime())) {
return this.createError('INVALID_EXPIRATION_DATE', 'Passport expiration date is invalid');
}
// Calculate days to expiration
const today = new Date();
const diffTime = expirationDate.getTime() - today.getTime();
daysToExpiration = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
hasValidExpiration = daysToExpiration >= this.options.minimumValidityDays;
if (!hasValidExpiration) {
return this.createError('INSUFFICIENT_PASSPORT_VALIDITY', `Passport must be valid for at least ${this.options.minimumValidityDays} more days`);
}
// Check max passport age if issue date is present
if (this.options.maxPassportAge && value.issueDate) {
const issueDate = new Date(value.issueDate);
if (!isNaN(issueDate.getTime())) {
const passportAgeMs = today.getTime() - issueDate.getTime();
const passportAgeYears = passportAgeMs / (1000 * 60 * 60 * 24 * 365);
if (passportAgeYears > this.options.maxPassportAge) {
return this.createError('PASSPORT_TOO_OLD', `Passport exceeds maximum age of ${this.options.maxPassportAge} years`);
}
}
}
}
// All validations passed
return {
isValid: true,
value: passportNumber,
normalizedValue: normalizedNumber,
authority,
hasValidExpiration,
daysToExpiration,
checksumValid: this.options.validateChecksums ?
this.validateChecksum(normalizedNumber, authority) : undefined
};
}
/**
* Attempt to detect the issuing authority from passport number format
* @param passportNumber Normalized passport number
* @returns Detected authority code or undefined
*/
detectAuthority(passportNumber) {
// Try to match against known formats
for (const [authority, regex] of Object.entries(this.formats)) {
if (authority !== 'generic' && regex.test(passportNumber)) {
return authority;
}
}
// Some specific format detection heuristics
if (/^[0-9]{9}$/.test(passportNumber)) {
// Could be USA, GBR, FRA, RUS - might need additional checks
return undefined; // Too ambiguous
}
// No match found
return undefined;
}
/**
* Validate passport number format against country-specific pattern
* @param passportNumber Normalized passport number
* @param authority Authority code to validate against
* @returns Whether the format is valid
*/
validateFormat(passportNumber, authority) {
const format = this.formats[authority] || this.formats.generic;
return format.test(passportNumber);
}
/**
* Validate passport number checksum (when supported)
* Note: Actual checksum validation varies by country and may require
* complex algorithms that are only available to official systems
*
* @param passportNumber Normalized passport number
* @param authority Authority code
* @returns Boolean if supported, undefined if not supported
*/
validateChecksum(passportNumber, authority) {
// This is a simplified implementation - real implementations would need
// country-specific checksum algorithms
switch (authority) {
case PassportAuthority.USA:
// Simplified example - not the actual algorithm
return this.checkAlphanumericSum(passportNumber);
case PassportAuthority.GBR:
// Simplified example - not the actual algorithm
return this.checkDigitSum(passportNumber);
// Add other country-specific checksum validation as needed
default:
// No checksum validation available for this authority
return undefined;
}
}
/**
* Example checksum validation method (simplified)
* @param value String value to check
* @returns Whether the checksum is valid
*/
checkAlphanumericSum(value) {
// This is a simplified demonstration and not a real passport checksum algorithm
const sum = Array.from(value).reduce((acc, char) => {
return acc + (char.charCodeAt(0) % 10);
}, 0);
return sum % 10 === 0;
}
/**
* Example checksum for numeric values (simplified)
* @param value String of digits
* @returns Whether the checksum is valid
*/
checkDigitSum(value) {
// This is a simplified demonstration and not a real passport checksum algorithm
const sum = Array.from(value).reduce((acc, digit) => {
return acc + parseInt(digit, 10);
}, 0);
return sum % 10 === 0;
}
/**
* Get all supported passport authorities
* @returns List of supported authority codes
*/
getSupportedAuthorities() {
return Object.keys(this.formats).filter(key => key !== 'generic');
}
/**
* Create an error result for passport validation
*/
createError(code, message) {
return {
isValid: false,
errors: [{ code, message }]
};
}
}
exports.PassportValidator = PassportValidator;