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
- 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
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
- 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
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)