refactor(launch): simplify launch-readiness fixes
Follow-up to 208638e. Code review surfaced a few real issues; this
commit addresses them.
- login.post.js now uses the new sendMagicLink util instead of
duplicating the jti/jwt/Resend/logActivity logic. Reduces 60 lines.
- sendMagicLink accepts an optional pre-loaded Member doc, skipping
the redundant findOne when the caller already has one. customer.post.js
passes the just-created/upgraded member, dropping signup from 3
Mongo round-trips to 1 (lookup is gone; jti burn remains).
- sendMagicLink now lowercases the email defensively so callers don't
have to remember.
- rateLimit.js: replaced an effectively-dead eviction line with a
probabilistic sweep (~1% of calls scan and evict keys whose newest
entry has aged out). Caps unbounded Map growth under random-key
spraying.
- reconcile-payments.post.js: 401/403/404 from Helcim now bails out
immediately instead of burning all 3 retry attempts; dry-run
summary filters via the same RECONCILABLE_STATUSES set as apply
mode so counts match.
- Deleted WHAT-comments and section banners per CLAUDE.md no-comment
rule. Kept genuine WHY-comments (validateBeforeSave rationale,
amount-IGNORED-for-tickets, sendConfirmation deliberately-omitted).
Tests: 758/760 passing (unchanged).
This commit is contained in:
parent
208638e374
commit
51230e5151
7 changed files with 33 additions and 98 deletions
|
|
@ -1,8 +1,3 @@
|
|||
// Public signup endpoint. Creates a Helcim customer + Member record and
|
||||
// dispatches a magic link for email verification. The full session cookie
|
||||
// is set when the user clicks the magic link (see /api/auth/verify); paid-tier
|
||||
// signups also receive a short-lived payment-bridge cookie so they can complete
|
||||
// Helcim checkout in the same tab without verifying email first.
|
||||
import { getRequestHeader, getRequestIP } from 'h3'
|
||||
import Member from '../../models/member.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
|
@ -13,14 +8,12 @@ import { rateLimit } from '../../utils/rateLimit.js'
|
|||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// --- Origin check (CSRF defense in depth on top of SameSite=Lax) ---
|
||||
const origin = getRequestHeader(event, 'origin')
|
||||
const allowed = process.env.BASE_URL
|
||||
if (!origin || (allowed && origin !== allowed)) {
|
||||
throw createError({ statusCode: 403, statusMessage: 'Invalid origin' })
|
||||
}
|
||||
|
||||
// --- Per-IP rate limit (5 / hour) ---
|
||||
const ip = getRequestIP(event, { xForwardedFor: true }) || 'unknown'
|
||||
if (!rateLimit(`signup:ip:${ip}`, { max: 5, windowMs: 3600_000 })) {
|
||||
throw createError({ statusCode: 429, statusMessage: 'Too many signup attempts' })
|
||||
|
|
@ -29,7 +22,6 @@ export default defineEventHandler(async (event) => {
|
|||
await connectDB()
|
||||
const body = await validateBody(event, helcimCustomerSchema)
|
||||
|
||||
// --- Per-email rate limit (3 / hour) ---
|
||||
if (!rateLimit(`signup:email:${body.email}`, { max: 3, windowMs: 3600_000 })) {
|
||||
throw createError({
|
||||
statusCode: 429,
|
||||
|
|
@ -88,13 +80,10 @@ export default defineEventHandler(async (event) => {
|
|||
})
|
||||
}
|
||||
|
||||
// Issue a magic link instead of an immediate session — the auth-token
|
||||
// cookie is set when the user clicks through, proving email ownership.
|
||||
// Use the normalized email so guest upgrades (which may not project
|
||||
// the email field back) still get a magic link.
|
||||
await sendMagicLink(normalizedEmail, {
|
||||
subject: 'Verify your Ghost Guild signup',
|
||||
intro: 'Verify your email to finish your Ghost Guild signup:'
|
||||
intro: 'Verify your email to finish your Ghost Guild signup:',
|
||||
member
|
||||
})
|
||||
|
||||
// Paid-tier signups need to complete Helcim checkout in the same tab
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue