{"record":{"id":"4bb4270ba269e473","repo":"toeverything/AFFiNE","slug":"wrong-sign-in-credentials-4bb427","errorCode":"wrong_sign_in_credentials","errorMessage":"Wrong user email or password: ${email}","messagePattern":"Wrong user email or password: (.+?)","errorType":"exception","errorClass":"WrongSignInCredentials","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/models/user.ts","lineNumber":146,"sourceCode":"  async getUserByEmail(\n    email: string,\n    filter: UserFilter = {}\n  ): Promise<User | null> {\n    const rows = await this.db.$queryRaw<User[]>`\n      SELECT id, name, email, password, registered, email_verified as \"emailVerifiedAt\", avatar_url as \"avatarUrl\", registered, created_at as \"createdAt\", disabled\n      FROM \"users\"\n      WHERE lower(\"email\") = lower(${email})\n      ${Prisma.raw(filter.withDisabled ? '' : 'AND disabled = false')}\n    `;\n\n    return rows[0] ?? null;\n  }\n\n  async signIn(email: string, password: string): Promise<User> {\n    const user = await this.getUserByEmail(email);\n\n    if (!user) {\n      throw new WrongSignInCredentials({ email });\n    }\n\n    if (!user.password) {\n      throw new WrongSignInMethod();\n    }\n\n    const passwordMatches = await this.crypto.verifyPassword(\n      password,\n      user.password\n    );\n\n    if (!passwordMatches) {\n      throw new WrongSignInCredentials({ email });\n    }\n\n    return user;\n  }\n","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/models/user.ts#L128-L164","documentation":"WrongSignInCredentials (message embeds the email), thrown in the first branch of UserModel.signIn (user.ts:139-148): getUserByEmail - a case-insensitive lower(email) raw query that excludes disabled accounts - returned no row. The same error class is reused later for password mismatch, deliberately preventing callers from distinguishing 'no such user' from 'wrong password'.","triggerScenarios":"signIn(email, password) where no enabled users row matches lower(email): unregistered address, typo, leading/trailing whitespace, or a disabled account.","commonSituations":"Sign-in attempts before signup; client pointed at the wrong environment/database after a migration; account disabled by an admin; casing or copy-paste artifacts in the email.","solutions":["Verify the email is registered and enabled in users (case-insensitive match)","Trim and normalize the email before calling signIn","If the account exists only via OAuth, sign in with that provider instead","Register the account first if it genuinely does not exist"],"exampleFix":"// before\nawait user.signIn(email, password);\n\n// after\nawait user.signIn(email.trim().toLowerCase(), password);","handlingStrategy":"try-catch","validationCode":"const existing = await user.getUserByEmail(email.trim().toLowerCase());\nif (!existing) {\n  // show 'wrong email or password' or prompt signup - do not reveal which part failed\n}","typeGuard":null,"tryCatchPattern":"try {\n  await user.signIn(email, password);\n} catch (e) {\n  if (e instanceof WrongSignInCredentials) {\n    return res.status(401).json({ error: 'Wrong user email or password' }); // uniform response\n  }\n  throw e;\n}","preventionTips":["Trim and lowercase the email before sign-in","Return one generic message for unknown email and wrong password (matches the model's design)","Check disabled-account status separately when you need to surface it"],"tags":["auth","sign-in","credentials","user"],"backgroundTag":"invalid-credentials","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}