Files
Leon Serfaty 51c541ad22 Security & robustness hardening pass
Cross-cutting input-validation, isolation, and DoS-resistance fixes across
the app, API, billing, queue, and infra layers.

- Runtime validation (zod) for client-supplied admin actions (role/plan/
  limits), series generation index, and all pg-boss queue payloads
- Auth: require email verification before sign-in; reject weak/placeholder/
  short BETTER_AUTH_SECRET in production
- Billing: sanitize Stripe/PayPal errors (log server-side, generic to client);
  race-safe subscription upsert; only count "processed" webhook events as
  handled; verify org membership in getEffectivePlan to block plan escalation
- Series generation: reserve usage up front and refund on failure; bill the
  owning org, not the caller's active org
- Injection defenses: HTML-escape user fields in emails, strip CR/LF from
  subject/recipient, validate ElevenLabs voiceId before URL interpolation
- Media routes: stream off disk instead of buffering whole files; rate-limit
  anonymous public audio/cover endpoints by client IP
2026-06-20 20:59:03 -04:00

119 lines
4.4 KiB
TypeScript

import { prisma } from "@/lib/db";
import { getPlan, type Plan, type PlanKey, type FeatureKey } from "./plans";
export interface UpsertSubscriptionInput {
provider: "stripe" | "paypal";
referenceId: string;
plan: PlanKey;
status: string;
billingInterval?: "month" | "year" | null;
seats?: number | null;
stripeCustomerId?: string | null;
stripeSubscriptionId?: string | null;
paypalSubscriptionId?: string | null;
paypalPlanId?: string | null;
periodStart?: Date | null;
periodEnd?: Date | null;
cancelAtPeriodEnd?: boolean | null;
}
/**
* The single writer both billing providers funnel into. Idempotent on the
* provider subscription id, so duplicate/replayed webhooks converge on one row.
*/
export async function upsertSubscription(input: UpsertSubscriptionInput) {
const data = {
provider: input.provider,
plan: input.plan,
status: input.status,
billingInterval: input.billingInterval ?? undefined,
seats: input.seats ?? undefined,
stripeCustomerId: input.stripeCustomerId ?? undefined,
stripeSubscriptionId: input.stripeSubscriptionId ?? undefined,
paypalSubscriptionId: input.paypalSubscriptionId ?? undefined,
paypalPlanId: input.paypalPlanId ?? undefined,
periodStart: input.periodStart ?? undefined,
periodEnd: input.periodEnd ?? undefined,
cancelAtPeriodEnd: input.cancelAtPeriodEnd ?? undefined,
};
// Atomic upsert keyed on whichever provider subscription id is present on the
// incoming record. The DB-level @@unique on these columns lets concurrent
// webhook retries converge on a single row instead of racing into duplicates.
if (input.stripeSubscriptionId) {
return prisma.subscription.upsert({
where: { stripeSubscriptionId: input.stripeSubscriptionId },
create: { referenceId: input.referenceId, ...data },
update: data,
});
}
if (input.paypalSubscriptionId) {
return prisma.subscription.upsert({
where: { paypalSubscriptionId: input.paypalSubscriptionId },
create: { referenceId: input.referenceId, ...data },
update: data,
});
}
// Safe fallback: neither provider id is present (no unique key to upsert on),
// so create a fresh row.
return prisma.subscription.create({ data: { referenceId: input.referenceId, ...data } });
}
/**
* Resolve the active plan for a billing subject (a user id, or an organization id
* for Agency). Returns "free" when there is no active subscription.
*
* Both Stripe and PayPal write into the same `subscription` table keyed by
* `referenceId`, so this is provider-agnostic by construction.
*/
export async function getSubjectPlanKey(referenceId: string): Promise<PlanKey> {
const sub = await prisma.subscription.findFirst({
where: { referenceId, status: { in: ["active", "trialing"] } },
orderBy: { createdAt: "desc" },
});
return (sub?.plan as PlanKey) ?? "free";
}
/** The active subscription row for a subject, if any. */
export function getActiveSubscription(referenceId: string) {
return prisma.subscription.findFirst({
where: { referenceId, status: { in: ["active", "trialing", "past_due"] } },
orderBy: { createdAt: "desc" },
});
}
/**
* Resolve the effective plan for the current request. When an organization is
* active (Agency workspace), the org's plan governs; otherwise the user's own.
*/
export async function getEffectivePlan(
userId: string,
activeOrgId?: string | null
): Promise<{ plan: Plan; key: PlanKey; subjectId: string; subjectType: "user" | "organization" }> {
if (activeOrgId) {
// Only grant the org's plan if the user is an actual member of that org.
// A stale/forged activeOrganizationId must not elevate a non-member.
const membership = await prisma.member.findUnique({
where: { organizationId_userId: { organizationId: activeOrgId, userId } },
select: { id: true },
});
if (membership) {
const key = await getSubjectPlanKey(activeOrgId);
if (key !== "free") {
return { plan: getPlan(key), key, subjectId: activeOrgId, subjectType: "organization" };
}
}
}
const key = await getSubjectPlanKey(userId);
return { plan: getPlan(key), key, subjectId: userId, subjectType: "user" };
}
export async function subjectHasFeature(
userId: string,
feature: FeatureKey,
activeOrgId?: string | null
): Promise<boolean> {
const { plan } = await getEffectivePlan(userId, activeOrgId);
return plan.features.includes(feature);
}