Follow-up to51230e5. /simplify review surfaced residual duplication and a timer leak. - useHelcimPay: extract _initializeTicket(metadata, errorPrefix) to collapse initializeTicketPayment + initializeSeriesTicketPayment (95% identical bodies). Drop the dead `amount` arg from initialize- TicketPayment — server re-derives ticket amounts in initialize- payment.post.js and never reads body.amount for ticket types. Capture timer ids and clearTimeout on resolve/reject so the 10-min payment timer and 5-second observer timer stop leaking after every payment. - EventTicketPurchase: caller updated for the dropped arg. - verify.post.js: replace inline jwt.sign + setCookie block with the setAuthCookie(event, member) helper. verify was the last hand-rolled caller after the helper was extracted in208638e. - LAUNCH_READINESS: add simplify-pass-followups bullet pointing to the six deferred items in docs/TODO.md. Tests: 758 passing, 2 skipped, 0 failing.
65 lines
1.7 KiB
JavaScript
65 lines
1.7 KiB
JavaScript
// server/api/auth/verify.post.js
|
|
import jwt from 'jsonwebtoken'
|
|
import Member from '../../models/member.js'
|
|
import { validateBody } from '../../utils/validateBody.js'
|
|
import { verifyMagicLinkSchema } from '../../utils/schemas.js'
|
|
import { setAuthCookie } from '../../utils/auth.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const { token } = await validateBody(event, verifyMagicLinkSchema)
|
|
|
|
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 }
|
|
)
|
|
|
|
setAuthCookie(event, member)
|
|
|
|
const redirectUrl = member.role === 'admin' ? '/admin' : '/member/dashboard'
|
|
return { success: true, redirectUrl }
|
|
})
|