UNPKG

codalware-auth

Version:

Complete authentication system with enterprise security, attack protection, team workspaces, waitlist, billing, UI components, 2FA, and account recovery - production-ready in 5 minutes. Enhanced CLI with verification, rollback, and App Router scaffolding.

168 lines (139 loc) 4.72 kB
/* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-nocheck - Template file, not compiled in package build import bcrypt from 'bcryptjs'; import { randomBytes, createHash } from 'crypto'; import { v4 as uuidv4 } from 'uuid'; import speakeasy from 'speakeasy'; import QRCode from 'qrcode'; import { config } from '../../../config'; export class PasswordUtils { static async hash(password: string): Promise<string> { return bcrypt.hash(password, 12); } static async verify(password: string, hash: string): Promise<boolean> { return bcrypt.compare(password, hash); } static validate(password: string): { valid: boolean; errors: string[] } { const errors: string[] = []; if (password.length < config.PASSWORD_MIN_LENGTH) { errors.push(`Password must be at least ${config.PASSWORD_MIN_LENGTH} characters long`); } if (config.PASSWORD_REQUIRE_UPPERCASE && !/[A-Z]/.test(password)) { errors.push('Password must contain at least one uppercase letter'); } if (config.PASSWORD_REQUIRE_LOWERCASE && !/[a-z]/.test(password)) { errors.push('Password must contain at least one lowercase letter'); } if (config.PASSWORD_REQUIRE_NUMBERS && !/\d/.test(password)) { errors.push('Password must contain at least one number'); } if (config.PASSWORD_REQUIRE_SYMBOLS && (password.match(/[!@#$%^&*(),.?":{}|<>]/g) || []).length < 2) { errors.push('Password must contain at least two symbols'); } return { valid: errors.length === 0, errors, }; } } export class TokenUtils { static generate(): string { return randomBytes(32).toString('hex'); } static generateSecure(): string { return createHash('sha256').update(randomBytes(32)).digest('hex'); } static generateUuid(): string { return uuidv4(); } static isExpired(expiresAt: Date): boolean { return new Date() > expiresAt; } static createExpiration(milliseconds: number): Date { return new Date(Date.now() + milliseconds); } static generateNumericCode(length: number = 6): string { const digits = '0123456789'; const buffer = randomBytes(length); let code = ''; for (let i = 0; i < length; i++) { const index = buffer[i] % digits.length; code += digits[index]; } return code; } } export class TwoFactorUtils { static generateSecret(name: string, issuer: string = config.APP_NAME): { secret: string; otpauthUrl: string; } { const secret = speakeasy.generateSecret({ name, issuer, length: 32, }); return { secret: secret.base32, otpauthUrl: secret.otpauth_url!, }; } static async generateQRCode(otpauthUrl: string): Promise<string> { return QRCode.toDataURL(otpauthUrl); } static verifyToken(secret: string, token: string, window: number = 1): boolean { return speakeasy.totp.verify({ secret, encoding: 'base32', token, window, }); } static generateBackupCodes(count: number = 10): string[] { const codes: string[] = []; for (let i = 0; i < count; i++) { const code = randomBytes(4).toString('hex').toUpperCase(); codes.push(`${code.slice(0, 4)}-${code.slice(4)}`); } return codes; } } export class ValidationUtils { static isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } static isValidDomain(domain: string): boolean { const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/; return domainRegex.test(domain); } static isValidSubdomain(subdomain: string): boolean { const subdomainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/; return subdomainRegex.test(subdomain); } static sanitizeInput(input: string): string { return input.trim().toLowerCase(); } } export class DomainUtils { static extractDomain(url: string): string { try { const urlObj = new URL(url); return urlObj.hostname; } catch { return url; } } static extractSubdomain(domain: string): { subdomain?: string; rootDomain: string } { const parts = domain.split('.'); if (parts.length > 2) { const subdomain = parts[0]; const rootDomain = parts.slice(1).join('.'); return { subdomain, rootDomain }; } return { rootDomain: domain }; } static buildUrl(protocol: string, domain: string, path: string = ''): string { return `${protocol}://${domain}${path}`; } }