mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 05:56:53 +00:00
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:
@@ -170,7 +170,7 @@ export class AdminService {
|
||||
const existingUsername = await this.users.findIdByUsernameExact(username);
|
||||
if (existingUsername) return { error: 'Username already taken', status: 409 };
|
||||
|
||||
const existingEmail = await this.users.findIdByEmailExact(email);
|
||||
const existingEmail = await this.users.findIdByEmailCI(email);
|
||||
if (existingEmail) return { error: 'Email already taken', status: 409 };
|
||||
|
||||
const passwordHash = bcrypt.hashSync(password, BCRYPT_COST);
|
||||
@@ -211,7 +211,7 @@ export class AdminService {
|
||||
if (conflict) return { error: 'Username already taken', status: 409 };
|
||||
}
|
||||
if (email && email !== user.email) {
|
||||
const conflict = await this.users.findIdByEmailExactExcluding(email, userId);
|
||||
const conflict = await this.users.findIdByEmailCI(email, userId);
|
||||
if (conflict) return { error: 'Email already taken', status: 409 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user