UserService Documentation
Comprehensive UserService Implementation Guide
The UserService manages user accounts, authentication, and profile operations. It provides secure user registration, login functionality, password management, and user profile maintenance with comprehensive security measures and audit logging.
Service Overview
Responsibilities
The UserService handles:
- User Registration - Secure user account creation with validation and verification
- Authentication - Password-based login with security measures
- Profile Management - User profile updates, preferences, and account settings
- Password Security - Secure password hashing, validation, and reset functionality
- Account Lifecycle - Account activation, deactivation, and deletion
- Audit Logging - Comprehensive logging of user actions for security and compliance
Architecture
export class UserService {
constructor(
private databaseService: DatabaseService,
private encryptionService: EncryptionService,
private logger: Logger,
private config: UserConfig
) {}
// Core User Methods
async createUser(userData: CreateUserRequest): Promise<User>;
async getUserById(userId: string): Promise<User | null>;
async getUserByEmail(email: string): Promise<User | null>;
async updateUser(userId: string, updates: UpdateUserRequest): Promise<User>;
async deleteUser(userId: string): Promise<void>;
// Authentication Methods
async authenticateUser(email: string, password: string): Promise<AuthResult>;
async hashPassword(password: string): Promise<string>;
async validatePassword(password: string, hash: string): Promise<boolean>;
async changePassword(
userId: string,
currentPassword: string,
newPassword: string
): Promise<void>;
// Profile Management
async updateProfile(userId: string, profile: UserProfile): Promise<void>;
async getProfile(userId: string): Promise<UserProfile | null>;
async updatePreferences(
userId: string,
preferences: UserPreferences
): Promise<void>;
// Security Methods
async lockAccount(userId: string, reason: string): Promise<void>;
async unlockAccount(userId: string): Promise<void>;
async logSecurityEvent(userId: string, event: SecurityEvent): Promise<void>;
}
Core Functionality
User Registration and Management
User Account Creation
The service provides secure user registration with comprehensive validation:
interface CreateUserRequest {
email: string
password: string
name: string
company?: string
role?: UserRole
metadata?: UserMetadata
}
interface User {
id: string
email: string
name: string
company?: string
role: UserRole
status: UserStatus
profile: UserProfile
preferences: UserPreferences
security: UserSecurity
createdAt: Date
updatedAt: Date
lastLoginAt?: Date
}
interface UserRole {
name: 'admin' | 'user' | 'viewer'
permissions: Permission[]
}
interface UserStatus {
active: boolean
verified: boolean
locked: boolean
lockReason?: string
lockedAt?: Date
}
interface UserSecurity {
passwordHash: string
passwordChangedAt: Date
failedLoginAttempts: number
lastFailedLoginAt?: Date
twoFactorEnabled: boolean
recoveryTokens?: string[]
}
async createUser(userData: CreateUserRequest): Promise<User> {
const startTime = Date.now()
try {
// Validate user data
await this.validateUserData(userData)
// Check if user already exists
const existingUser = await this.getUserByEmail(userData.email)
if (existingUser) {
throw new AppError('USER_ALREADY_EXISTS', 'User with this email already exists')
}
// Hash password securely
const passwordHash = await this.hashPassword(userData.password)
// Generate user ID
const userId = this.generateUserId()
// Create user object
const user: User = {
id: userId,
email: userData.email.toLowerCase().trim(),
name: userData.name.trim(),
company: userData.company?.trim(),
role: userData.role || { name: 'user', permissions: this.getDefaultPermissions() },
status: {
active: true,
verified: false,
locked: false
},
profile: {
name: userData.name.trim(),
email: userData.email.toLowerCase().trim(),
company: userData.company?.trim(),
avatar: null,
bio: null,
location: null,
website: null
},
preferences: this.getDefaultPreferences(),
security: {
passwordHash,
passwordChangedAt: new Date(),
failedLoginAttempts: 0,
twoFactorEnabled: false
},
createdAt: new Date(),
updatedAt: new Date()
}
// Save user to database
await this.saveUser(user)
// Log user creation event
await this.logSecurityEvent(userId, {
type: 'user_created',
description: 'User account created',
metadata: {
email: userData.email,
registrationSource: userData.metadata?.source || 'web'
}
})
const operationTime = Date.now() - startTime
this.logger.info('User created successfully', {
userId,
email: userData.email,
operationTime
})
return user
} catch (error) {
const operationTime = Date.now() - startTime
this.logger.error('User creation failed', {
error,
email: userData.email,
operationTime
})
throw error
}
}
private async validateUserData(userData: CreateUserRequest): Promise<void> {
const errors: string[] = []
// Email validation
if (!userData.email || !this.isValidEmail(userData.email)) {
errors.push('Invalid email address')
}
// Password validation
const passwordValidation = this.validatePasswordStrength(userData.password)
if (!passwordValidation.valid) {
errors.push(...passwordValidation.errors)
}
// Name validation
if (!userData.name || userData.name.trim().length < 2) {
errors.push('Name must be at least 2 characters')
}
if (userData.name.trim().length > 100) {
errors.push('Name must be less than 100 characters')
}
// Company validation (if provided)
if (userData.company && userData.company.trim().length > 100) {
errors.push('Company name must be less than 100 characters')
}
if (errors.length > 0) {
throw new AppError('VALIDATION_ERROR', `Validation failed: ${errors.join(', ')}`)
}
}
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email) && email.length <= 255
}
private validatePasswordStrength(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = []
if (!password) {
return { valid: false, errors: ['Password is required'] }
}
if (password.length < 8) {
errors.push('Password must be at least 8 characters')
}
if (password.length > 128) {
errors.push('Password must be less than 128 characters')
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter')
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter')
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain at least one number')
}
if (!/[^a-zA-Z0-9]/.test(password)) {
errors.push('Password must contain at least one special character')
}
// Check for common passwords
if (this.isCommonPassword(password)) {
errors.push('Password is too common, please choose a more unique password')
}
return { valid: errors.length === 0, errors }
}
Authentication and Security
Secure Authentication Implementation
The service provides robust authentication with security measures:
interface AuthResult {
success: boolean
user?: User
token?: string
refreshToken?: string
expiresIn?: number
reason?: string
requiresTwoFactor?: boolean
lockoutRemaining?: number
}
interface SecurityEvent {
type: SecurityEventType
description: string
ipAddress?: string
userAgent?: string
metadata?: Record<string, any>
timestamp?: Date
}
enum SecurityEventType {
USER_CREATED = 'user_created',
LOGIN_SUCCESS = 'login_success',
LOGIN_FAILED = 'login_failed',
PASSWORD_CHANGED = 'password_changed',
ACCOUNT_LOCKED = 'account_locked',
ACCOUNT_UNLOCKED = 'account_unlocked',
PROFILE_UPDATED = 'profile_updated',
PERMISSIONS_CHANGED = 'permissions_changed'
}
async authenticateUser(email: string, password: string, metadata?: AuthMetadata): Promise<AuthResult> {
const startTime = Date.now()
try {
// Get user by email
const user = await this.getUserByEmail(email)
if (!user) {
await this.logFailedLogin(email, 'user_not_found', metadata)
return {
success: false,
reason: 'Invalid email or password'
}
}
// Check account status
if (!user.status.active) {
await this.logFailedLogin(email, 'account_inactive', metadata)
return {
success: false,
reason: 'Account is inactive'
}
}
if (user.status.locked) {
await this.logFailedLogin(email, 'account_locked', metadata)
return {
success: false,
reason: 'Account is locked',
lockoutRemaining: this.calculateLockoutRemaining(user)
}
}
// Check for rate limiting
if (await this.isRateLimited(user.id, metadata?.ipAddress)) {
await this.logFailedLogin(email, 'rate_limited', metadata)
return {
success: false,
reason: 'Too many login attempts, please try again later'
}
}
// Validate password
const validPassword = await this.validatePassword(password, user.security.passwordHash)
if (!validPassword) {
await this.handleFailedLogin(user, metadata)
return {
success: false,
reason: 'Invalid email or password'
}
}
// Check if two-factor authentication is required
if (user.security.twoFactorEnabled) {
return {
success: false,
requiresTwoFactor: true,
reason: 'Two-factor authentication required'
}
}
// Successful authentication
await this.handleSuccessfulLogin(user, metadata)
const operationTime = Date.now() - startTime
this.logger.info('User authenticated successfully', {
userId: user.id,
email: user.email,
operationTime
})
return {
success: true,
user: this.sanitizeUserForResponse(user)
}
} catch (error) {
const operationTime = Date.now() - startTime
this.logger.error('Authentication failed', {
error,
email,
operationTime
})
return {
success: false,
reason: 'Authentication service error'
}
}
}
async hashPassword(password: string): Promise<string> {
try {
const saltRounds = this.config.security.bcryptRounds || 12
const hash = await bcrypt.hash(password, saltRounds)
this.logger.debug('Password hashed successfully', {
saltRounds,
hashLength: hash.length
})
return hash
} catch (error) {
this.logger.error('Password hashing failed', { error })
throw new AppError('PASSWORD_HASH_FAILED', 'Failed to hash password')
}
}
async validatePassword(password: string, hash: string): Promise<boolean> {
try {
const isValid = await bcrypt.compare(password, hash)
this.logger.debug('Password validation completed', {
isValid,
hashLength: hash.length
})
return isValid
} catch (error) {
this.logger.error('Password validation failed', { error })
return false
}
}
private async handleFailedLogin(user: User, metadata?: AuthMetadata): Promise<void> {
// Increment failed login attempts
user.security.failedLoginAttempts += 1
user.security.lastFailedLoginAt = new Date()
// Lock account if too many failed attempts
const maxAttempts = this.config.security.maxFailedLoginAttempts || 5
if (user.security.failedLoginAttempts >= maxAttempts) {
await this.lockAccount(user.id, 'Too many failed login attempts')
}
// Update user security info
await this.updateUserSecurity(user.id, user.security)
// Log security event
await this.logSecurityEvent(user.id, {
type: SecurityEventType.LOGIN_FAILED,
description: 'Failed login attempt',
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent,
metadata: {
failedAttempts: user.security.failedLoginAttempts,
locked: user.security.failedLoginAttempts >= maxAttempts
}
})
}
private async handleSuccessfulLogin(user: User, metadata?: AuthMetadata): Promise<void> {
// Reset failed login attempts
user.security.failedLoginAttempts = 0
user.security.lastFailedLoginAt = undefined
user.lastLoginAt = new Date()
// Update user info
await this.updateUser(user.id, {
lastLoginAt: user.lastLoginAt,
security: user.security
})
// Log security event
await this.logSecurityEvent(user.id, {
type: SecurityEventType.LOGIN_SUCCESS,
description: 'Successful login',
ipAddress: metadata?.ipAddress,
userAgent: metadata?.userAgent
})
}
Profile Management
User Profile and Preferences Management
The service provides comprehensive profile management capabilities:
interface UserProfile {
name: string
email: string
company?: string
avatar?: string
bio?: string
location?: string
website?: string
socialLinks?: SocialLinks
}
interface SocialLinks {
twitter?: string
linkedin?: string
github?: string
}
interface UserPreferences {
theme: 'light' | 'dark' | 'auto'
language: string
timezone: string
notifications: NotificationPreferences
privacy: PrivacyPreferences
search: SearchPreferences
}
interface NotificationPreferences {
email: {
loginAlerts: boolean
securityUpdates: boolean
productUpdates: boolean
marketingEmails: boolean
}
inApp: {
searchNotifications: boolean
systemAlerts: boolean
tips: boolean
}
}
interface SearchPreferences {
defaultSearchMode: 'natural' | 'boolean' | 'semantic'
preferredDatabases: string[]
resultLimit: number
enableCache: boolean
saveSearchHistory: boolean
}
async updateProfile(userId: string, profileData: Partial<UserProfile>): Promise<void> {
const startTime = Date.now()
try {
// Get current user
const user = await this.getUserById(userId)
if (!user) {
throw new AppError('USER_NOT_FOUND', 'User not found')
}
// Validate profile data
await this.validateProfileData(profileData)
// Update profile
const updatedProfile: UserProfile = {
...user.profile,
...profileData,
// Ensure email changes go through proper verification
email: user.profile.email // Don't allow direct email changes
}
// Handle special cases
if (profileData.avatar) {
updatedProfile.avatar = await this.processAvatarUpload(profileData.avatar)
}
if (profileData.website) {
updatedProfile.website = this.normalizeUrl(profileData.website)
}
// Update user in database
await this.updateUser(userId, { profile: updatedProfile })
// Log profile update
await this.logSecurityEvent(userId, {
type: SecurityEventType.PROFILE_UPDATED,
description: 'User profile updated',
metadata: {
updatedFields: Object.keys(profileData)
}
})
const operationTime = Date.now() - startTime
this.logger.info('Profile updated successfully', {
userId,
updatedFields: Object.keys(profileData),
operationTime
})
} catch (error) {
const operationTime = Date.now() - startTime
this.logger.error('Profile update failed', {
error,
userId,
operationTime
})
throw error
}
}
async updatePreferences(userId: string, preferences: Partial<UserPreferences>): Promise<void> {
try {
// Get current user
const user = await this.getUserById(userId)
if (!user) {
throw new AppError('USER_NOT_FOUND', 'User not found')
}
// Validate preferences
await this.validatePreferences(preferences)
// Deep merge preferences
const updatedPreferences: UserPreferences = this.deepMerge(user.preferences, preferences)
// Update user preferences
await this.updateUser(userId, { preferences: updatedPreferences })
this.logger.info('User preferences updated', {
userId,
updatedPreferences: Object.keys(preferences)
})
} catch (error) {
this.logger.error('Preferences update failed', { error, userId })
throw error
}
}
private async validateProfileData(profileData: Partial<UserProfile>): Promise<void> {
const errors: string[] = []
if (profileData.name !== undefined) {
if (!profileData.name || profileData.name.trim().length < 2) {
errors.push('Name must be at least 2 characters')
}
if (profileData.name.trim().length > 100) {
errors.push('Name must be less than 100 characters')
}
}
if (profileData.bio !== undefined && profileData.bio.length > 500) {
errors.push('Bio must be less than 500 characters')
}
if (profileData.website !== undefined && profileData.website) {
if (!this.isValidUrl(profileData.website)) {
errors.push('Invalid website URL')
}
}
if (profileData.location !== undefined && profileData.location.length > 100) {
errors.push('Location must be less than 100 characters')
}
if (errors.length > 0) {
throw new AppError('VALIDATION_ERROR', `Profile validation failed: ${errors.join(', ')}`)
}
}
private getDefaultPreferences(): UserPreferences {
return {
theme: 'light',
language: 'en',
timezone: 'UTC',
notifications: {
email: {
loginAlerts: true,
securityUpdates: true,
productUpdates: true,
marketingEmails: false
},
inApp: {
searchNotifications: true,
systemAlerts: true,
tips: true
}
},
privacy: {
shareAnalytics: true,
shareUsageData: false,
allowPersonalization: true
},
search: {
defaultSearchMode: 'natural',
preferredDatabases: [],
resultLimit: 20,
enableCache: true,
saveSearchHistory: true
}
}
}
Security and Account Management
Account Security and Lifecycle Management
The service provides comprehensive security and account lifecycle management:
interface SecurityConfig {
maxFailedLoginAttempts: number
accountLockoutDuration: number
passwordMinLength: number
passwordMaxAge: number
sessionTimeout: number
bcryptRounds: number
}
interface AccountLockInfo {
locked: boolean
lockReason?: string
lockedAt?: Date
lockExpiration?: Date
canUnlock: boolean
}
async lockAccount(userId: string, reason: string, duration?: number): Promise<void> {
try {
const user = await this.getUserById(userId)
if (!user) {
throw new AppError('USER_NOT_FOUND', 'User not found')
}
const lockDuration = duration || this.config.security.accountLockoutDuration || 1800000 // 30 minutes
const lockExpiration = new Date(Date.now() + lockDuration)
// Update user status
user.status.locked = true
user.status.lockReason = reason
user.status.lockedAt = new Date()
await this.updateUser(userId, { status: user.status })
// Schedule automatic unlock if duration is specified
if (lockDuration > 0) {
setTimeout(async () => {
try {
await this.unlockAccount(userId)
} catch (error) {
this.logger.error('Auto-unlock failed', { error, userId })
}
}, lockDuration)
}
// Log security event
await this.logSecurityEvent(userId, {
type: SecurityEventType.ACCOUNT_LOCKED,
description: `Account locked: ${reason}`,
metadata: {
lockDuration,
lockExpiration: lockExpiration.toISOString()
}
})
this.logger.warn('Account locked', {
userId,
reason,
lockDuration,
lockExpiration
})
} catch (error) {
this.logger.error('Failed to lock account', { error, userId, reason })
throw error
}
}
async unlockAccount(userId: string): Promise<void> {
try {
const user = await this.getUserById(userId)
if (!user) {
throw new AppError('USER_NOT_FOUND', 'User not found')
}
if (!user.status.locked) {
return // Already unlocked
}
// Reset security status
user.status.locked = false
user.status.lockReason = undefined
user.status.lockedAt = undefined
user.security.failedLoginAttempts = 0
user.security.lastFailedLoginAt = undefined
await this.updateUser(userId, {
status: user.status,
security: user.security
})
// Log security event
await this.logSecurityEvent(userId, {
type: SecurityEventType.ACCOUNT_UNLOCKED,
description: 'Account unlocked'
})
this.logger.info('Account unlocked', { userId })
} catch (error) {
this.logger.error('Failed to unlock account', { error, userId })
throw error
}
}
async changePassword(
userId: string,
currentPassword: string,
newPassword: string
): Promise<void> {
try {
const user = await this.getUserById(userId)
if (!user) {
throw new AppError('USER_NOT_FOUND', 'User not found')
}
// Verify current password
const validPassword = await this.validatePassword(currentPassword, user.security.passwordHash)
if (!validPassword) {
await this.logSecurityEvent(userId, {
type: SecurityEventType.PASSWORD_CHANGED,
description: 'Password change failed - invalid current password'
})
throw new AppError('INVALID_PASSWORD', 'Current password is incorrect')
}
// Validate new password
const passwordValidation = this.validatePasswordStrength(newPassword)
if (!passwordValidation.valid) {
throw new AppError('VALIDATION_ERROR', `Password validation failed: ${passwordValidation.errors.join(', ')}`)
}
// Check password history (prevent reuse)
if (await this.isPasswordReused(userId, newPassword)) {
throw new AppError('PASSWORD_REUSED', 'Cannot reuse recent passwords')
}
// Hash new password
const newPasswordHash = await this.hashPassword(newPassword)
// Update user security
user.security.passwordHash = newPasswordHash
user.security.passwordChangedAt = new Date()
user.security.failedLoginAttempts = 0
await this.updateUser(userId, { security: user.security })
// Store password in history for reuse prevention
await this.addPasswordToHistory(userId, newPasswordHash)
// Log security event
await this.logSecurityEvent(userId, {
type: SecurityEventType.PASSWORD_CHANGED,
description: 'Password changed successfully'
})
this.logger.info('Password changed successfully', { userId })
} catch (error) {
this.logger.error('Password change failed', { error, userId })
throw error
}
}
async deleteUser(userId: string, reason?: string): Promise<void> {
try {
const user = await this.getUserById(userId)
if (!user) {
throw new AppError('USER_NOT_FOUND', 'User not found')
}
// Soft delete - mark as inactive and anonymize sensitive data
const deletedUser = {
...user,
email: `deleted_${userId}@example.com`,
name: 'Deleted User',
status: {
...user.status,
active: false
},
profile: {
...user.profile,
email: `deleted_${userId}@example.com`,
name: 'Deleted User',
bio: null,
location: null,
website: null,
avatar: null
}
}
await this.updateUser(userId, deletedUser)
// Log deletion event
await this.logSecurityEvent(userId, {
type: 'user_deleted',
description: `User account deleted: ${reason || 'User request'}`,
metadata: { reason }
})
this.logger.info('User deleted', { userId, reason })
} catch (error) {
this.logger.error('User deletion failed', { error, userId })
throw error
}
}
Security Event Logging
Comprehensive Security Audit Trail
The service provides detailed security event logging for audit and monitoring:
interface SecurityEvent {
id: string
userId: string
type: SecurityEventType
description: string
ipAddress?: string
userAgent?: string
metadata?: Record<string, any>
timestamp: Date
severity: 'low' | 'medium' | 'high' | 'critical'
}
async logSecurityEvent(userId: string, event: Omit<SecurityEvent, 'id' | 'userId' | 'timestamp' | 'severity'>): Promise<void> {
try {
const securityEvent: SecurityEvent = {
id: this.generateEventId(),
userId,
type: event.type,
description: event.description,
ipAddress: event.ipAddress,
userAgent: event.userAgent,
metadata: event.metadata,
timestamp: event.timestamp || new Date(),
severity: this.determineSeverity(event.type)
}
// Store in database
await this.storeSecurityEvent(securityEvent)
// Alert on high/critical events
if (securityEvent.severity === 'high' || securityEvent.severity === 'critical') {
await this.alertSecurityTeam(securityEvent)
}
this.logger.info('Security event logged', {
eventId: securityEvent.id,
userId,
type: event.type,
severity: securityEvent.severity
})
} catch (error) {
this.logger.error('Failed to log security event', { error, userId, eventType: event.type })
}
}
private determineSeverity(eventType: SecurityEventType): 'low' | 'medium' | 'high' | 'critical' {
const severityMap: Record<SecurityEventType, 'low' | 'medium' | 'high' | 'critical'> = {
[SecurityEventType.USER_CREATED]: 'low',
[SecurityEventType.LOGIN_SUCCESS]: 'low',
[SecurityEventType.LOGIN_FAILED]: 'medium',
[SecurityEventType.PASSWORD_CHANGED]: 'medium',
[SecurityEventType.ACCOUNT_LOCKED]: 'high',
[SecurityEventType.ACCOUNT_UNLOCKED]: 'medium',
[SecurityEventType.PROFILE_UPDATED]: 'low',
[SecurityEventType.PERMISSIONS_CHANGED]: 'high'
}
return severityMap[eventType] || 'medium'
}
async getSecurityEvents(
userId: string,
options: {
limit?: number
offset?: number
eventTypes?: SecurityEventType[]
dateRange?: { from: Date; to: Date }
severity?: ('low' | 'medium' | 'high' | 'critical')[]
} = {}
): Promise<{ events: SecurityEvent[]; total: number }> {
try {
const query = this.buildSecurityEventQuery(userId, options)
const events = await this.querySecurityEvents(query)
const total = await this.countSecurityEvents(query)
return { events, total }
} catch (error) {
this.logger.error('Failed to get security events', { error, userId })
throw new AppError('SECURITY_EVENTS_RETRIEVAL_FAILED', error.message)
}
}
Testing and Validation
Unit Testing
describe('UserService', () => {
let userService: UserService;
let mockDatabaseService: jest.Mocked<DatabaseService>;
let mockEncryptionService: jest.Mocked<EncryptionService>;
let mockLogger: jest.Mocked<Logger>;
beforeEach(() => {
mockDatabaseService = {
query: jest.fn(),
transaction: jest.fn(),
};
mockEncryptionService = {
encrypt: jest.fn(),
decrypt: jest.fn(),
};
mockLogger = {
info: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
warn: jest.fn(),
};
userService = new UserService(
mockDatabaseService,
mockEncryptionService,
mockLogger,
testConfig
);
});
describe('createUser', () => {
it('should create user with valid data', async () => {
const userData: CreateUserRequest = {
email: 'test@example.com',
password: 'SecurePassword123!',
name: 'Test User',
};
mockDatabaseService.query.mockResolvedValue([]);
const user = await userService.createUser(userData);
expect(user).toHaveProperty('id');
expect(user.email).toBe(userData.email.toLowerCase());
expect(user.name).toBe(userData.name);
expect(user.status.active).toBe(true);
});
it('should throw error for duplicate email', async () => {
const userData: CreateUserRequest = {
email: 'test@example.com',
password: 'SecurePassword123!',
name: 'Test User',
};
mockDatabaseService.query.mockResolvedValue([{ id: 'existing-user' }]);
await expect(userService.createUser(userData)).rejects.toThrow(
'USER_ALREADY_EXISTS'
);
});
});
describe('authenticateUser', () => {
it('should authenticate user with valid credentials', async () => {
const mockUser = createMockUser();
mockDatabaseService.query.mockResolvedValue([mockUser]);
jest.spyOn(userService, 'validatePassword').mockResolvedValue(true);
const result = await userService.authenticateUser(
'test@example.com',
'password'
);
expect(result.success).toBe(true);
expect(result.user).toBeDefined();
});
it('should fail authentication for invalid password', async () => {
const mockUser = createMockUser();
mockDatabaseService.query.mockResolvedValue([mockUser]);
jest.spyOn(userService, 'validatePassword').mockResolvedValue(false);
const result = await userService.authenticateUser(
'test@example.com',
'wrongpassword'
);
expect(result.success).toBe(false);
expect(result.reason).toBe('Invalid email or password');
});
});
});
Configuration and Best Practices
Security Configuration
interface UserConfig {
security: {
bcryptRounds: number;
maxFailedLoginAttempts: number;
accountLockoutDuration: number;
passwordMinLength: number;
passwordMaxAge: number;
sessionTimeout: number;
requireEmailVerification: boolean;
enableTwoFactor: boolean;
};
validation: {
allowedEmailDomains?: string[];
blockedEmailDomains?: string[];
requireStrongPasswords: boolean;
allowPasswordReset: boolean;
};
features: {
enableRegistration: boolean;
enableProfileUpdates: boolean;
enableAccountDeletion: boolean;
enableSecurityLogging: boolean;
};
}
Best Practices
- Password Security: Use strong hashing (bcrypt) with appropriate salt rounds
- Input Validation: Validate and sanitize all user inputs
- Rate Limiting: Implement login attempt rate limiting
- Security Logging: Log all security-relevant events
- Account Lockout: Implement progressive account lockout policies
- Data Privacy: Follow data protection regulations (GDPR, CCPA)
- Session Management: Implement secure session handling
The UserService provides secure and comprehensive user account management, forming the foundation for authentication and user experience in Altus 4.