fix(auth): match email addresses case-insensitively everywhere

Login and the OIDC fallback match emails case-insensitively, but the
password reset lookup and the admin create and edit checks compared
exactly. An account stored as Foo@x.com never got a reset mail, and an
admin could create bob@x next to Bob@x, after which login picked
whichever row came first. The signup collision check and the first-user
count also move inside the signup transaction.
This commit is contained in:
Maurice
2026-10-07 16:07:23 +02:00
parent e7ab0fc21e
commit 63f7eab4bb
5 changed files with 53 additions and 45 deletions
+9 -8
View File
@@ -479,20 +479,21 @@ export class AuthService {
return { error: 'Invalid email format', status: 400 };
}
// Ignore guests (#1362): their synthetic username/email must never block a real signup.
const existingUserId = await this.usersRepo.findIdByEmailOrUsernameCI(email, username);
if (existingUserId !== null) {
return { error: 'Registration failed. Please try different credentials.', status: 409 };
}
const password_hash = bcrypt.hashSync(password, BCRYPT_COST);
const isFirstUser = userCount === 0;
const role = isFirstUser ? 'admin' : 'user';
try {
// One transaction for the whole signup: a mid-sequence throw (invite
// bookkeeping, trip auto-join) must not leave a half-registered user.
// The collision check and the first-user count run inside it too, so two
// signups at once cannot both pass the check or both become admin.
return await this.uow.transactional(async () => {
// Ignore guests (#1362): their synthetic username/email must never block a real signup.
const existingUserId = await this.usersRepo.findIdByEmailOrUsernameCI(email, username);
if (existingUserId !== null) {
return { error: 'Registration failed. Please try different credentials.', status: 409 };
}
const isFirstUser = (await this.usersRepo.countNonGuest()) === 0;
const role = isFirstUser ? 'admin' : 'user';
const inserted = await this.usersRepo.insertUser({
username, email, password_hash, role, first_seen_version: readEnv().app.appVersion || '0.0.0',
});