Extract shared auth logic and validation rules to shared/auth.ts Introduce utility functions for HTTP errors and user input parsing Standardize error messages and date formatting across the app
95 lines
2.3 KiB
TypeScript
95 lines
2.3 KiB
TypeScript
export const DEFAULT_USER_PASSWORD = '123456'
|
|
export const MIN_PASSWORD_LENGTH = 8
|
|
export const MIN_FULL_NAME_LENGTH = 2
|
|
export const USERNAME_PATTERN = /^[a-z0-9._-]{3,32}$/
|
|
export const PHONE_NUMBER_PATTERN = /^\+?\d{8,15}$/
|
|
|
|
export type UserRole = 'super_admin' | 'staff'
|
|
|
|
export function normalizeUsername(value: string) {
|
|
return value.trim().toLowerCase()
|
|
}
|
|
|
|
export function normalizeFullName(value: string) {
|
|
return value.trim()
|
|
}
|
|
|
|
export function normalizePhoneNumber(value: string) {
|
|
const trimmed = value.trim()
|
|
|
|
if (!trimmed) {
|
|
return ''
|
|
}
|
|
|
|
const hasPlusPrefix = trimmed.startsWith('+')
|
|
const digitsOnly = trimmed.replace(/\D/g, '')
|
|
|
|
return hasPlusPrefix ? `+${digitsOnly}` : digitsOnly
|
|
}
|
|
|
|
export function isValidPhoneNumber(value: string) {
|
|
return PHONE_NUMBER_PATTERN.test(normalizePhoneNumber(value))
|
|
}
|
|
|
|
export function isValidUsername(value: string) {
|
|
return USERNAME_PATTERN.test(normalizeUsername(value))
|
|
}
|
|
|
|
export function hasValidFullName(value: string) {
|
|
return normalizeFullName(value).length >= MIN_FULL_NAME_LENGTH
|
|
}
|
|
|
|
export function isUserRole(value: string | null | undefined): value is UserRole {
|
|
return value === 'super_admin' || value === 'staff'
|
|
}
|
|
|
|
export interface AuthUser {
|
|
id: string
|
|
username: string
|
|
fullName: string
|
|
phoneNumber: string | null
|
|
role: UserRole
|
|
isActive: boolean
|
|
mustChangePassword: boolean
|
|
needsPasskeySetup: boolean
|
|
passkeyCount: number
|
|
createdAt: string
|
|
lastLoginAt: string | null
|
|
}
|
|
|
|
export interface ManagedUser extends AuthUser {
|
|
createdBy: string | null
|
|
}
|
|
|
|
export interface PublicContact {
|
|
id: string
|
|
fullName: string
|
|
phoneNumber: string
|
|
role: UserRole
|
|
}
|
|
|
|
export interface PasskeySummary {
|
|
id: string
|
|
label: string
|
|
createdAt: string
|
|
lastUsedAt: string | null
|
|
deviceType: 'singleDevice' | 'multiDevice'
|
|
backedUp: boolean
|
|
}
|
|
|
|
export function needsUserOnboarding(
|
|
user: Pick<AuthUser, 'mustChangePassword' | 'needsPasskeySetup'> | null | undefined
|
|
) {
|
|
return Boolean(user && (user.mustChangePassword || user.needsPasskeySetup))
|
|
}
|
|
|
|
export function getDefaultAuthenticatedPath(
|
|
user: Pick<AuthUser, 'role' | 'mustChangePassword' | 'needsPasskeySetup'>
|
|
) {
|
|
if (needsUserOnboarding(user)) {
|
|
return '/security'
|
|
}
|
|
|
|
return user.role === 'super_admin' ? '/management/users' : '/security'
|
|
}
|