Superadmin impersonation, hardened auth/upload paths, dependency updates

- Add superadmin impersonation: sessions.impersonated_by (migration 0003) is
  stamped onto audit rows so impersonated actions are attributable, with a
  persistent ImpersonationBanner in the app shell.
- Harden auth and upload handling across routes (safe redirect targets,
  filename sanitization, checkout grant handling).
- Update dependencies: Sentry 8 -> 10, @fastify/static 8 -> 10,
  react-router-dom 6.30.6; add find-my-way / fast-uri overrides.
- Add tests for safe-next, checkout-grant, and upload-filename.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leon Serfaty
2026-08-26 10:25:39 -04:00
co-authored by Claude Opus 5
parent 4a1122a7c9
commit eb36b81dc9
28 changed files with 7936 additions and 5763 deletions
+6 -2
View File
@@ -8,6 +8,10 @@ import { getStripe, getPlanConfig, stripeIsConfigured } from '../lib/stripe';
export async function billingRoutes(app: FastifyInstance) {
app.addHook('preHandler', app.requireFirm);
// Reading plan state is fine for anyone in the firm; spending money or opening the Stripe
// portal (which exposes payment methods and invoice history) is the owner's call alone.
const ownerOnly = { preHandler: app.requireRole('owner') };
// Status — what does the UI need to show? Configured at all? Current plan? Has subscription?
app.get('/api/billing/status', async (req) => {
const firmId = req.user!.firmId!;
@@ -21,7 +25,7 @@ export async function billingRoutes(app: FastifyInstance) {
});
// Create a Checkout Session — returns the URL to redirect the user to.
app.post('/api/billing/checkout', async (req, reply) => {
app.post('/api/billing/checkout', ownerOnly, async (req, reply) => {
const parsed = z
.object({ plan: z.enum(['pro', 'lifetime']) })
.safeParse(req.body);
@@ -69,7 +73,7 @@ export async function billingRoutes(app: FastifyInstance) {
});
// Customer Portal — for managing the subscription, updating payment method, viewing invoices.
app.post('/api/billing/portal', async (req, reply) => {
app.post('/api/billing/portal', ownerOnly, async (req, reply) => {
if (!stripeIsConfigured()) return reply.code(503).send({ error: 'stripe_not_configured' });
const firmId = req.user!.firmId!;