fix(security): rate-limit public invite and password-reset endpoints

- requestPasswordReset: rate-limited by email (authRateLimiter, 5/15 min)
  to prevent email bombing
- resetPassword: rate-limited by token to add explicit brute-force defence
- getInvite + acceptInvite: rate-limited by invite token (authRateLimiter)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 21:38:08 +02:00
parent 9e31c6d972
commit df191d1e03
+17
View File
@@ -5,6 +5,7 @@ import { SystemRole } from "@capakraken/db";
import { createTRPCRouter, adminProcedure, publicProcedure } from "../trpc.js";
import { getAppBaseUrl } from "../lib/app-base-url.js";
import { sendEmail } from "../lib/email.js";
import { authRateLimiter } from "../middleware/rate-limit.js";
const INVITE_TTL_MS = 72 * 60 * 60 * 1000; // 72 hours
@@ -85,6 +86,14 @@ export const inviteRouter = createTRPCRouter({
getInvite: publicProcedure
.input(z.object({ token: z.string() }))
.query(async ({ ctx, input }) => {
const rl = await authRateLimiter(input.token);
if (!rl.allowed) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: "Too many attempts. Please wait before trying again.",
});
}
const invite = await ctx.db.inviteToken.findUnique({
where: { token: input.token },
select: { email: true, role: true, expiresAt: true, usedAt: true },
@@ -104,6 +113,14 @@ export const inviteRouter = createTRPCRouter({
}),
)
.mutation(async ({ ctx, input }) => {
const rl = await authRateLimiter(input.token);
if (!rl.allowed) {
throw new TRPCError({
code: "TOO_MANY_REQUESTS",
message: "Too many attempts. Please wait before trying again.",
});
}
const invite = await ctx.db.inviteToken.findUnique({
where: { token: input.token },
});