fix: replace member.save() with atomic update in verify
This commit is contained in:
parent
707ff7b13a
commit
79c712a9e9
1 changed files with 86 additions and 0 deletions
86
server/api/auth/verify.post.js
Normal file
86
server/api/auth/verify.post.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// server/api/auth/verify.post.js
|
||||
import jwt from 'jsonwebtoken'
|
||||
import Member from '../../models/member.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await connectDB()
|
||||
|
||||
const body = await readBody(event)
|
||||
const token = body?.token
|
||||
|
||||
if (!token) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Token is required',
|
||||
})
|
||||
}
|
||||
|
||||
const config = useRuntimeConfig(event)
|
||||
|
||||
let decoded
|
||||
try {
|
||||
decoded = jwt.verify(token, config.jwtSecret)
|
||||
} catch {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Invalid or expired token',
|
||||
})
|
||||
}
|
||||
|
||||
const member = await Member.findById(decoded.memberId)
|
||||
|
||||
if (!member) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Invalid or expired token',
|
||||
})
|
||||
}
|
||||
|
||||
if (member.status === 'suspended') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Account is suspended',
|
||||
})
|
||||
}
|
||||
|
||||
if (member.status === 'cancelled') {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Account is cancelled',
|
||||
})
|
||||
}
|
||||
|
||||
// Single-use enforcement: jti must match and must not have been used
|
||||
if (!decoded.jti || decoded.jti !== member.magicLinkJti || member.magicLinkJtiUsed) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Invalid or expired token',
|
||||
})
|
||||
}
|
||||
|
||||
// Atomically burn the token before issuing session
|
||||
await Member.findByIdAndUpdate(
|
||||
member._id,
|
||||
{ $set: { magicLinkJtiUsed: true, lastLogin: new Date() } },
|
||||
{ runValidators: false }
|
||||
)
|
||||
|
||||
// Issue session token with tokenVersion claim for revocation support
|
||||
const sessionToken = jwt.sign(
|
||||
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
|
||||
config.jwtSecret,
|
||||
{ expiresIn: '7d' },
|
||||
)
|
||||
|
||||
setCookie(event, 'auth-token', sessionToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 7, // 7 days
|
||||
})
|
||||
|
||||
const redirectUrl = member.role === 'admin' ? '/admin' : '/member/dashboard'
|
||||
return { success: true, redirectUrl }
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue