fix: case-insensitive email matching in auth and password reset
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m10s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m10s
Email lookups used findUnique (case-sensitive on PostgreSQL) but user input was lowercased, causing login failures for users with mixed-case emails stored in the DB (e.g. Laurent_Faure@dietsmann.com). Also normalized 7 affected emails to lowercase on the production DB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,8 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ exists: false }, { status: 400 })
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: email.toLowerCase().trim() },
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { email: { equals: email.toLowerCase().trim(), mode: 'insensitive' } },
|
||||
select: { status: true },
|
||||
})
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
// Send magic links to any existing, non-suspended user.
|
||||
// This is the primary first-login path for applicants seeded from CSV
|
||||
// (status NONE) who have no password yet.
|
||||
const existingUser = await prisma.user.findUnique({
|
||||
where: { email: email.toLowerCase().trim() },
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: { email: { equals: email.toLowerCase().trim(), mode: 'insensitive' } },
|
||||
select: { id: true, status: true },
|
||||
})
|
||||
if (!existingUser || existingUser.status === 'SUSPENDED') {
|
||||
@@ -164,9 +164,9 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
throw new Error('Account temporarily locked due to too many failed attempts. Try again later.')
|
||||
}
|
||||
|
||||
// Find user by email
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
// Find user by email (case-insensitive — DB may store mixed-case emails)
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { email: { equals: email, mode: 'insensitive' } },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
@@ -371,8 +371,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
async signIn({ user, account }) {
|
||||
// For email provider (magic link), check user status and get password info
|
||||
if (account?.provider === 'email') {
|
||||
const dbUser = await prisma.user.findUnique({
|
||||
where: { email: user.email! },
|
||||
const dbUser = await prisma.user.findFirst({
|
||||
where: { email: { equals: user.email!, mode: 'insensitive' } },
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
@@ -404,22 +404,24 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
|
||||
// Update last login time on actual sign-in, and activate INVITED users on login
|
||||
if (user.email) {
|
||||
const loginUser = await prisma.user.findUnique({
|
||||
where: { email: user.email },
|
||||
select: { status: true },
|
||||
const loginUser = await prisma.user.findFirst({
|
||||
where: { email: { equals: user.email, mode: 'insensitive' } },
|
||||
select: { id: true, status: true },
|
||||
})
|
||||
if (loginUser) {
|
||||
await prisma.user.update({
|
||||
where: { email: user.email },
|
||||
where: { id: loginUser.id },
|
||||
data: {
|
||||
lastLoginAt: new Date(),
|
||||
// If user is still INVITED but successfully logged in, activate them
|
||||
...(loginUser && loginUser.status === 'INVITED'
|
||||
...(loginUser.status === 'INVITED'
|
||||
? { status: 'ACTIVE' }
|
||||
: {}),
|
||||
},
|
||||
}).catch(() => {
|
||||
// Ignore errors from updating last login
|
||||
})
|
||||
}
|
||||
|
||||
// Log successful login
|
||||
await prisma.auditLog.create({
|
||||
|
||||
@@ -1595,9 +1595,9 @@ export const userRouter = router({
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const email = input.email.toLowerCase().trim()
|
||||
|
||||
// Find user by email
|
||||
const user = await ctx.prisma.user.findUnique({
|
||||
where: { email },
|
||||
// Find user by email (case-insensitive — DB may store mixed-case emails)
|
||||
const user = await ctx.prisma.user.findFirst({
|
||||
where: { email: { equals: email, mode: 'insensitive' } },
|
||||
select: { id: true, email: true, name: true, status: true },
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user