import { IUserRepository } from '../../../Domain/IRepository/IUserRepository'; import { IOrganizationRepository } from '../../../Domain/IRepository/IOrganizationRepository'; import { LoginCommand } from './LoginCommand'; import { ShortUserDto } from '../../DTOs/UserDto'; import { UserMapper } from '../../DTOs/Mappers/UserMapper'; import { PasswordService } from '../../Services/PasswordService'; import { JWTService } from '../../Services/JWTService'; import { UserState } from '../../../Domain/User/UserAggregate'; import { logAuth, logDatabase, logError, logWarning } from '../../Services/Logger'; import { Response } from 'express'; export interface LoginResponse { user: ShortUserDto; token: string; requiresOrgReauth?: boolean; orgLoginUrl?: string; organizationName?: string; } export class LoginCommandHandler { constructor( private readonly userRepo: IUserRepository, private readonly jwtService: JWTService, private readonly orgRepo: IOrganizationRepository ) {} async execute(cmd: LoginCommand, res?: Response): Promise { const startTime = Date.now(); try { logAuth('Login attempt', undefined, { username: cmd.username }); const user = await this.userRepo.findByUsername(cmd.username) || await this.userRepo.findByEmail(cmd.username); logDatabase('User lookup completed', undefined, Date.now() - startTime, { found: !!user, searchBy: cmd.username.includes('@') ? 'email' : 'username' }); if (!user) { logAuth('Login failed - User not found', undefined, { username: cmd.username }); return null; } // Check if user account state allows login const restrictedStates = [ UserState.REGISTERED_NOT_VERIFIED, UserState.SOFT_DELETE, UserState.DEACTIVATED ]; if (restrictedStates.includes(user.state)) { let stateDescription = ''; switch (user.state) { case UserState.REGISTERED_NOT_VERIFIED: stateDescription = 'Email not verified'; break; case UserState.SOFT_DELETE: stateDescription = 'Account deleted'; break; case UserState.DEACTIVATED: stateDescription = 'Account deactivated'; break; } logAuth('Login failed - Account state restriction', user.id, { username: cmd.username, userState: user.state, stateDescription }); return null; } try { const passwordStartTime = Date.now(); const isPasswordValid = await PasswordService.verifyPassword(cmd.password, user.password); logAuth('Password verification completed', user.id, { valid: isPasswordValid, verificationTime: Date.now() - passwordStartTime }); if (!isPasswordValid) { logWarning('Login failed - Invalid password', { userId: user.id, username: cmd.username }); return null; } } catch (error) { logError('Password verification error', error as Error); return null; } const mockRes = { cookie: () => {} } as any; const tokenPayload = { userId: user.id, authLevel: (user.state === UserState.ADMIN ? 1 : 0) as 0 | 1, userStatus: user.state, orgId: user.orgid || '' }; try { // Use the real response object if provided, otherwise use mock const responseObj = res || mockRes; const token = this.jwtService.create(tokenPayload, responseObj); // Check if user belongs to an organization and needs reauthentication let requiresOrgReauth = false; let orgLoginUrl: string | undefined; let organizationName: string | undefined; if (user.orgid) { const organization = await this.orgRepo.findById(user.orgid); if (organization) { organizationName = organization.name; // Check if user has logged in to organization within the last month const oneMonthAgo = new Date(); oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); const needsReauth = !user.Orglogindate || user.Orglogindate < oneMonthAgo; if (needsReauth && organization.url) { requiresOrgReauth = true; orgLoginUrl = organization.url; logAuth('User requires organization reauthentication', user.id, { organizationId: user.orgid, organizationName: organization.name, lastOrgLogin: user.Orglogindate?.toISOString() || 'never', orgLoginUrl: organization.url }); } } } logAuth('Login successful', user.id, { authLevel: tokenPayload.authLevel, userStatus: tokenPayload.userStatus, orgId: tokenPayload.orgId, requiresOrgReauth, organizationName, totalLoginTime: Date.now() - startTime }); const response: LoginResponse = { user: UserMapper.toShortDto(user), token }; if (requiresOrgReauth) { response.requiresOrgReauth = true; response.orgLoginUrl = orgLoginUrl; response.organizationName = organizationName; } return response; } catch (error) { logError('Token creation failed during login', error as Error); throw new Error('Login failed due to internal error'); } } catch (error) { if (error instanceof Error) { logError('Login handler error', error); // Handle database connection errors if (error.message.includes('database connection')) { logDatabase('Database connection error during login', undefined, Date.now() - startTime); throw new Error('Database connection error'); } // If it's already a properly formatted error, re-throw it if (error.message === 'Login failed due to internal error' || error.message === 'Database connection error') { throw error; } } // Default database error handling logDatabase('Unexpected database error during login', undefined, Date.now() - startTime); throw new Error('Database connection error'); } } }