fix: applicant portal — document uploads, round filtering, auth hardening
Fix round-specific document uploads (submittedAt no longer blocks uploads), add view/download buttons for existing files, enforce active-round-only for uploads/deletes. Harden auth layout and set-password page. Filter applicant portal rounds by award track membership. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,19 @@ const LOCKOUT_DURATION_MS = 15 * 60 * 1000 // 15 minutes
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
...authConfig,
|
||||
adapter: PrismaAdapter(prisma),
|
||||
adapter: {
|
||||
...PrismaAdapter(prisma),
|
||||
async useVerificationToken({ identifier, token }: { identifier: string; token: string }) {
|
||||
try {
|
||||
return await prisma.verificationToken.delete({
|
||||
where: { identifier_token: { identifier, token } },
|
||||
})
|
||||
} catch (e) {
|
||||
if ((e as { code?: string }).code === 'P2025') return null
|
||||
throw e
|
||||
}
|
||||
},
|
||||
},
|
||||
providers: [
|
||||
// Email provider for magic links (used for first login and password reset)
|
||||
EmailProvider({
|
||||
@@ -129,7 +141,7 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
},
|
||||
})
|
||||
|
||||
if (!user || user.status === 'SUSPENDED' || !user.passwordHash) {
|
||||
if (!user || user.status === 'SUSPENDED') {
|
||||
// Track failed attempt (don't reveal whether user exists)
|
||||
const current = failedAttempts.get(email) || { count: 0, lockedUntil: 0 }
|
||||
current.count++
|
||||
@@ -139,19 +151,24 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
}
|
||||
failedAttempts.set(email, current)
|
||||
|
||||
// Log failed login
|
||||
// Log failed login — real security event
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: null,
|
||||
action: 'LOGIN_FAILED',
|
||||
entityType: 'User',
|
||||
detailsJson: { email, reason: !user ? 'user_not_found' : user.status === 'SUSPENDED' ? 'suspended' : 'no_password' },
|
||||
detailsJson: { email, reason: !user ? 'user_not_found' : 'suspended' },
|
||||
},
|
||||
}).catch(() => {})
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (!user.passwordHash) {
|
||||
// Magic-link user tried credentials form — expected, not a security event
|
||||
return null
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValid = await verifyPassword(password, user.passwordHash)
|
||||
if (!isValid) {
|
||||
|
||||
Reference in New Issue
Block a user