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,74 +1,18 @@
|
|||
// server/api/auth/login.post.js
|
||||
import jwt from "jsonwebtoken";
|
||||
import { randomUUID } from "crypto";
|
||||
import { Resend } from "resend";
|
||||
import Member from "../../models/member.js";
|
||||
import { connectDB } from "../../utils/mongoose.js";
|
||||
import { validateBody } from "../../utils/validateBody.js";
|
||||
import { emailSchema } from "../../utils/schemas.js";
|
||||
|
||||
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||
import { sendMagicLink } from "../../utils/magicLink.js";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await connectDB();
|
||||
|
||||
const baseUrl = process.env.BASE_URL;
|
||||
if (!baseUrl) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "BASE_URL environment variable is not set",
|
||||
});
|
||||
}
|
||||
|
||||
const { email } = await validateBody(event, emailSchema);
|
||||
|
||||
const GENERIC_MESSAGE = "If this email is registered, we've sent a login link.";
|
||||
|
||||
const member = await Member.findOne({ email });
|
||||
|
||||
if (!member) {
|
||||
return {
|
||||
success: true,
|
||||
message: GENERIC_MESSAGE,
|
||||
};
|
||||
}
|
||||
|
||||
const config = useRuntimeConfig(event);
|
||||
const jti = randomUUID();
|
||||
|
||||
const token = jwt.sign(
|
||||
{ memberId: member._id, jti },
|
||||
config.jwtSecret,
|
||||
{ expiresIn: "15m" },
|
||||
);
|
||||
|
||||
// Store jti so we can burn it on first use
|
||||
await Member.findByIdAndUpdate(
|
||||
member._id,
|
||||
{ $set: { magicLinkJti: jti, magicLinkJtiUsed: false } },
|
||||
{ runValidators: false }
|
||||
);
|
||||
|
||||
// Token goes in the fragment — never sent to server, never logged
|
||||
const magicLink = `${baseUrl}/verify#${token}`;
|
||||
|
||||
const emailSubject = "Your Ghost Guild login link";
|
||||
const emailBody = `Hi,\n\nSign in to Ghost Guild:\n${magicLink}\n\nThis link expires in 15 minutes. If you didn't request it, ignore this email.`;
|
||||
|
||||
try {
|
||||
await resend.emails.send({
|
||||
from: "Ghost Guild <ghostguild@babyghosts.org>",
|
||||
to: email,
|
||||
subject: emailSubject,
|
||||
text: emailBody,
|
||||
});
|
||||
|
||||
logActivity(member._id, 'email_sent', {
|
||||
emailType: 'magic_link',
|
||||
subject: emailSubject,
|
||||
body: emailBody
|
||||
})
|
||||
|
||||
await sendMagicLink(email);
|
||||
return {
|
||||
success: true,
|
||||
message: GENERIC_MESSAGE,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue