Browse Source

add strict rate limit to endpoints that send email

Milo Schwartz 5 months ago
parent
commit
3c7025a327
2 changed files with 46 additions and 5 deletions
  1. 3 3
      server/routers/auth/requestPasswordReset.ts
  2. 43 2
      server/routers/external.ts

+ 3 - 3
server/routers/auth/requestPasswordReset.ts

@@ -8,10 +8,8 @@ import { db } from "@server/db";
 import { passwordResetTokens, users } from "@server/db/schema";
 import { eq } from "drizzle-orm";
 import { alphabet, generateRandomString, sha256 } from "oslo/crypto";
-import { encodeHex } from "oslo/encoding";
 import { createDate } from "oslo";
 import logger from "@server/logger";
-import { generateIdFromEntropySize } from "@server/auth/sessions/app";
 import { TimeSpan } from "oslo";
 import config from "@server/lib/config";
 import { sendEmail } from "@server/emails";
@@ -85,7 +83,9 @@ export async function requestPasswordReset(
         const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${email}&token=${token}`;
 
         if (!config.getRawConfig().email) {
-            logger.info(`Password reset requested for ${email}. Token: ${token}.`);
+            logger.info(
+                `Password reset requested for ${email}. Token: ${token}.`
+            );
         }
 
         await sendEmail(

+ 43 - 2
server/routers/external.ts

@@ -27,6 +27,8 @@ import { verifyUserHasAction } from "../middlewares/verifyUserHasAction";
 import { ActionsEnum } from "@server/auth/actions";
 import { verifyUserIsOrgOwner } from "../middlewares/verifyUserIsOrgOwner";
 import { createNewt, getToken } from "./newt";
+import rateLimit from "express-rate-limit";
+import createHttpError from "http-errors";
 
 // Root routes
 export const unauthenticated = Router();
@@ -452,22 +454,61 @@ authRouter.post(
 );
 authRouter.post("/2fa/disable", verifySessionUserMiddleware, auth.disable2fa);
 authRouter.post("/verify-email", verifySessionMiddleware, auth.verifyEmail);
+
 authRouter.post(
     "/verify-email/request",
     verifySessionMiddleware,
+    rateLimit({
+        windowMs: 15 * 60 * 1000,
+        max: 3,
+        keyGenerator: (req) => `requestEmailVerificationCode:${req.body.email}`,
+        handler: (req, res, next) => {
+            const message = `You can only request an email verification code ${3} times every ${15} minutes. Please try again later.`;
+            return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
+        }
+    }),
     auth.requestEmailVerificationCode
 );
+
 // authRouter.post(
 //     "/change-password",
 //     verifySessionUserMiddleware,
 //     auth.changePassword
 // );
-authRouter.post("/reset-password/request", auth.requestPasswordReset);
+
+authRouter.post(
+    "/reset-password/request",
+    rateLimit({
+        windowMs: 15 * 60 * 1000,
+        max: 3,
+        keyGenerator: (req) => `requestPasswordReset:${req.body.email}`,
+        handler: (req, res, next) => {
+            const message = `You can only request a password reset ${3} times every ${15} minutes. Please try again later.`;
+            return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
+        }
+    }),
+    auth.requestPasswordReset
+);
+
 authRouter.post("/reset-password/", auth.resetPassword);
 
 authRouter.post("/resource/:resourceId/password", resource.authWithPassword);
 authRouter.post("/resource/:resourceId/pincode", resource.authWithPincode);
-authRouter.post("/resource/:resourceId/whitelist", resource.authWithWhitelist);
+
+authRouter.post(
+    "/resource/:resourceId/whitelist",
+    rateLimit({
+        windowMs: 15 * 60 * 1000,
+        max: 10,
+        keyGenerator: (req) => `authWithWhitelist:${req.body.email}`,
+        handler: (req, res, next) => {
+            const message = `You can only request an email OTP ${10} times every ${15} minutes. Please try again later.`;
+            return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
+        }
+    }),
+    resource.authWithWhitelist
+);
+
 authRouter.post(
     "/resource/:resourceId/access-token",
     resource.authWithAccessToken