toeverything/AFFiNE · error · WrongSignInCredentials

wrong_sign_in_credentials

wrong_sign_in_credentials

Error message

Wrong user email or password: ${email}

What it means

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'.

Solutions

  1. Verify the email is registered and enabled in users (case-insensitive match)
  2. Trim and normalize the email before calling signIn
  3. If the account exists only via OAuth, sign in with that provider instead
  4. Register the account first if it genuinely does not exist

Example fix

// before
await user.signIn(email, password);

// after
await user.signIn(email.trim().toLowerCase(), password);
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await user.getUserByEmail(email.trim().toLowerCase());
if (!existing) {
  // show 'wrong email or password' or prompt signup - do not reveal which part failed
}

Try / catch

try {
  await user.signIn(email, password);
} catch (e) {
  if (e instanceof WrongSignInCredentials) {
    return res.status(401).json({ error: 'Wrong user email or password' }); // uniform response
  }
  throw e;
}

Prevention

When it happens

Trigger: signIn(email, password) where no enabled users row matches lower(email): unregistered address, typo, leading/trailing whitespace, or a disabled account.

Common situations: 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.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/4bb4270ba269e473. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/models/user.ts:146

  async getUserByEmail(
    email: string,
    filter: UserFilter = {}
  ): Promise<User | null> {
    const rows = await this.db.$queryRaw<User[]>`
      SELECT id, name, email, password, registered, email_verified as "emailVerifiedAt", avatar_url as "avatarUrl", registered, created_at as "createdAt", disabled
      FROM "users"
      WHERE lower("email") = lower(${email})
      ${Prisma.raw(filter.withDisabled ? '' : 'AND disabled = false')}
    `;

    return rows[0] ?? null;
  }

  async signIn(email: string, password: string): Promise<User> {
    const user = await this.getUserByEmail(email);

    if (!user) {
      throw new WrongSignInCredentials({ email });
    }

    if (!user.password) {
      throw new WrongSignInMethod();
    }

    const passwordMatches = await this.crypto.verifyPassword(
      password,
      user.password
    );

    if (!passwordMatches) {
      throw new WrongSignInCredentials({ email });
    }

    return user;
  }

View on GitHub (pinned to b4c8548c09)