* Login with email and password
(credentials: LoginCredentials, request: Request)
| 177 | * Login with email and password |
| 178 | */ |
| 179 | async login(credentials: LoginCredentials, request: Request): Promise<AuthResult> { |
| 180 | try { |
| 181 | // Find user |
| 182 | const user = await this.database |
| 183 | .select() |
| 184 | .from(schema.users) |
| 185 | .where( |
| 186 | and( |
| 187 | eq(schema.users.email, credentials.email.toLowerCase()), |
| 188 | sql`${schema.users.deletedAt} IS NULL` |
| 189 | ) |
| 190 | ) |
| 191 | .get(); |
| 192 | |
| 193 | if (!user || !user.passwordHash) { |
| 194 | await this.logAuthAttempt(credentials.email, 'login', false, request); |
| 195 | throw new SecurityError( |
| 196 | SecurityErrorType.UNAUTHORIZED, |
| 197 | 'Invalid email or password', |
| 198 | 401 |
| 199 | ); |
| 200 | } |
| 201 | |
| 202 | // Verify password |
| 203 | const passwordValid = await this.passwordService.verify( |
| 204 | credentials.password, |
| 205 | user.passwordHash |
| 206 | ); |
| 207 | |
| 208 | if (!passwordValid) { |
| 209 | await this.logAuthAttempt(credentials.email, 'login', false, request); |
| 210 | throw new SecurityError( |
| 211 | SecurityErrorType.UNAUTHORIZED, |
| 212 | 'Invalid email or password', |
| 213 | 401 |
| 214 | ); |
| 215 | } |
| 216 | |
| 217 | // Create session |
| 218 | const { accessToken, session } = await this.sessionService.createSession( |
| 219 | user.id, |
| 220 | request |
| 221 | ); |
| 222 | |
| 223 | // Log successful attempt |
| 224 | await this.logAuthAttempt(credentials.email, 'login', true, request); |
| 225 | |
| 226 | logger.info('User logged in', { userId: user.id, email: user.email }); |
| 227 | |
| 228 | return { |
| 229 | user: mapUserResponse(user), |
| 230 | accessToken, |
| 231 | sessionId: session.sessionId, |
| 232 | expiresAt: session.expiresAt, |
| 233 | }; |
| 234 | } catch (error) { |
| 235 | if (error instanceof SecurityError) { |
| 236 | throw error; |
no test coverage detected