{"record":{"id":"97072d93fc214430","repo":"immich-app/immich","slug":"incorrect-email-or-password","errorCode":null,"errorMessage":"Incorrect email or password","messagePattern":"Incorrect email or password","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":72,"sourceCode":"  };\n};\n\n@Injectable()\nexport class AuthService extends BaseService {\n  async login(dto: LoginCredentialDto, details: LoginDetails) {\n    const config = await this.getConfig({ withCache: false });\n    if (!config.passwordLogin.enabled) {\n      throw new UnauthorizedException('Password login has been disabled');\n    }\n\n    const user = await this.userRepository.getByEmail(dto.email, { withPassword: true });\n    // Always run bcrypt so response time is constant regardless of whether the email\n    // is registered, preventing timing-based user enumeration.\n    const isAuthenticated = this.cryptoRepository.compareBcrypt(dto.password, user?.password ?? LOGIN_DUMMY_HASH);\n\n    if (!user || !user.password || !isAuthenticated) {\n      this.logger.warn(`Failed login attempt for user ${dto.email} from ip address ${details.clientIp}`);\n      throw new UnauthorizedException('Incorrect email or password');\n    }\n\n    return this.createLoginResponse(user, details);\n  }\n\n  async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {\n    let oauthBearerToken: string | undefined;\n    if (auth.session) {\n      const session = await this.sessionRepository.get(auth.session.id);\n      oauthBearerToken = session?.oauthBearerToken ?? undefined;\n      await this.sessionRepository.delete(auth.session.id);\n      await this.eventRepository.emit('SessionDelete', { sessionId: auth.session.id });\n    }\n\n    return {\n      successful: true,\n      redirectUri: await this.getLogoutEndpoint(authType, oauthBearerToken),\n    };","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/auth.service.ts#L54-L90","documentation":"Thrown by AuthService.login when authentication fails: the user is not found, has no password set, or the bcrypt comparison fails. The check runs a dummy bcrypt hash against a constant (LOGIN_DUMMY_HASH) when the user is missing so response time stays constant and prevents user enumeration. The email is logged at warn level with the client IP, and a 401 Unauthorized is returned.","triggerScenarios":"POST /auth/login with an unregistered email; correct email but wrong password; user exists but has no password (OAuth-only account) yet tries password login.","commonSituations":"Typos in email or password; user created via OAuth trying password login; migrated users without passwords; caps-lock / wrong keyboard layout.","solutions":["Verify the email is registered and the password is correct.","If the account is OAuth-only, use the OAuth login flow instead.","Use the password reset flow if the password is forgotten.","Check server logs for the warn line to confirm whether the email itself is unknown."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Light client-side validation; full validation happens server-side.\nif (!/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(email) || !password) {\n  throw new Error('Enter a valid email and password.');\n}\nawait api.post('/auth/login', { email, password });","typeGuard":null,"tryCatchPattern":"try {\n  await api.post('/auth/login', { email, password });\n} catch (e) {\n  if (e.response?.status === 401) {\n    showCredentialError(); // do NOT reveal whether email vs password was wrong\n  } else throw e;\n}","preventionTips":["Validate email format client-side, but never reveal email-vs-password specifics.","Offer OAuth or password reset when login repeatedly fails.","Confirm OAuth-only accounts are not attempting password login."],"tags":["auth","login","credentials","unauthorized","security"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}