Apply full refactor updates plus pipeline/email UX confirmations
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m33s

This commit is contained in:
Matt
2026-02-14 15:26:42 +01:00
parent e56e143a40
commit b5425e705e
374 changed files with 116737 additions and 111969 deletions

View File

@@ -1,93 +1,93 @@
import bcrypt from 'bcryptjs'
const SALT_ROUNDS = 12
/**
* Hash a password using bcrypt
*/
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS)
}
/**
* Verify a password against a hash
*/
export async function verifyPassword(
password: string,
hash: string
): Promise<boolean> {
return bcrypt.compare(password, hash)
}
interface PasswordValidation {
valid: boolean
errors: string[]
}
/**
* Validate password meets requirements:
* - Minimum 8 characters
* - At least one uppercase letter
* - At least one lowercase letter
* - At least one number
*/
export function validatePassword(password: string): PasswordValidation {
const errors: string[] = []
if (password.length < 8) {
errors.push('Password must be at least 8 characters long')
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter')
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter')
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain at least one number')
}
return {
valid: errors.length === 0,
errors,
}
}
/**
* Get password strength score (0-4)
* 0 = very weak, 4 = very strong
*/
export function getPasswordStrength(password: string): {
score: number
label: 'Very Weak' | 'Weak' | 'Fair' | 'Strong' | 'Very Strong'
} {
let score = 0
// Length
if (password.length >= 8) score++
if (password.length >= 12) score++
// Character variety
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score++
if (/[0-9]/.test(password)) score++
if (/[^a-zA-Z0-9]/.test(password)) score++
// Normalize to 0-4
const normalizedScore = Math.min(4, score)
const labels: Record<number, 'Very Weak' | 'Weak' | 'Fair' | 'Strong' | 'Very Strong'> = {
0: 'Very Weak',
1: 'Weak',
2: 'Fair',
3: 'Strong',
4: 'Very Strong',
}
return {
score: normalizedScore,
label: labels[normalizedScore],
}
}
import bcrypt from 'bcryptjs'
const SALT_ROUNDS = 12
/**
* Hash a password using bcrypt
*/
export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS)
}
/**
* Verify a password against a hash
*/
export async function verifyPassword(
password: string,
hash: string
): Promise<boolean> {
return bcrypt.compare(password, hash)
}
interface PasswordValidation {
valid: boolean
errors: string[]
}
/**
* Validate password meets requirements:
* - Minimum 8 characters
* - At least one uppercase letter
* - At least one lowercase letter
* - At least one number
*/
export function validatePassword(password: string): PasswordValidation {
const errors: string[] = []
if (password.length < 8) {
errors.push('Password must be at least 8 characters long')
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter')
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter')
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain at least one number')
}
return {
valid: errors.length === 0,
errors,
}
}
/**
* Get password strength score (0-4)
* 0 = very weak, 4 = very strong
*/
export function getPasswordStrength(password: string): {
score: number
label: 'Very Weak' | 'Weak' | 'Fair' | 'Strong' | 'Very Strong'
} {
let score = 0
// Length
if (password.length >= 8) score++
if (password.length >= 12) score++
// Character variety
if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score++
if (/[0-9]/.test(password)) score++
if (/[^a-zA-Z0-9]/.test(password)) score++
// Normalize to 0-4
const normalizedScore = Math.min(4, score)
const labels: Record<number, 'Very Weak' | 'Weak' | 'Fair' | 'Strong' | 'Very Strong'> = {
0: 'Very Weak',
1: 'Weak',
2: 'Fair',
3: 'Strong',
4: 'Very Strong',
}
return {
score: normalizedScore,
label: labels[normalizedScore],
}
}