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
text/typescript
/* 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}`;
}
}