interface OtpServiceEntity { length?: number; } export class OtpService { private readonly otpLength: number; constructor({ length = 4 }: OtpServiceEntity) { this.otpLength = Math.max(length, 4); // Minimum of 4 digits } private hasSequentialDigits(str: string): boolean { for (let i = 0; i < str.length - 1; i++) { const current = parseInt(str[i], 10); const next = parseInt(str[i + 1], 10); if (next === current + 1 || next === current - 1) { return true; } } return false; } private hasRepeatedDigits(str: string): boolean { return str.split('').every((char) => char === str[0]); } private isPalindrome(str: string): boolean { return str === str.split('').reverse().join(''); } private hasPartiallyRepeatedDigits(str: string): boolean { const counts: Record = {}; for (const char of str) { counts[char] = (counts[char] || 0) + 1; } // Reject if any digit appears more than twice return Object.values(counts).some((count) => count > 2); } private hasNoMatchLength(str: string) { return str.length !== this.otpLength; } private hasStartWithZero(str: string) { return str.split('')[0] === '0'; } public generateSecureOTP(): string { let otp: string; do { otp = Array.from({ length: this.otpLength }, () => Math.floor(Math.random() * 10).toString(), ).join(''); } while ( this.hasNoMatchLength(otp) || this.hasSequentialDigits(otp) || this.hasRepeatedDigits(otp) || this.isPalindrome(otp) || this.hasPartiallyRepeatedDigits(otp) || this.hasStartWithZero(otp) ); return otp; } }