diff --git a/apps/web/src/pages/api/auth/[...all].ts b/apps/web/src/pages/api/auth/[...all].ts index f81033aa..edde523c 100644 --- a/apps/web/src/pages/api/auth/[...all].ts +++ b/apps/web/src/pages/api/auth/[...all].ts @@ -2,9 +2,17 @@ import { toNodeHandler } from "better-auth/node"; import { initAuth } from "@kan/auth/server"; import { createDrizzleClient } from "@kan/db/client"; +import { withRateLimit } from "~/utils/rateLimit"; export const config = { api: { bodyParser: false } }; export const auth = initAuth(createDrizzleClient()); -export default toNodeHandler(auth.handler); +const authHandler = toNodeHandler(auth.handler); + +export default withRateLimit( + { points: 100, duration: 60 }, + async (req, res) => { + return await authHandler(req, res); + }, +); diff --git a/apps/web/src/pages/api/download/attatchment.ts b/apps/web/src/pages/api/download/attatchment.ts index d174565e..ed9a4df8 100644 --- a/apps/web/src/pages/api/download/attatchment.ts +++ b/apps/web/src/pages/api/download/attatchment.ts @@ -1,9 +1,10 @@ import type { NextApiRequest, NextApiResponse } from "next"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +import { withRateLimit } from "~/utils/rateLimit"; + +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { if (req.method !== "GET") { return res.status(405).json({ message: "Method not allowed" }); } @@ -44,4 +45,5 @@ export default async function handler( console.error("Error downloading attachment:", error); return res.status(500).json({ message: "Failed to download attachment" }); } -} + }, +); diff --git a/apps/web/src/pages/api/oss-friends.ts b/apps/web/src/pages/api/oss-friends.ts index 7ad34ea4..57a99390 100644 --- a/apps/web/src/pages/api/oss-friends.ts +++ b/apps/web/src/pages/api/oss-friends.ts @@ -1,9 +1,10 @@ import type { NextApiRequest, NextApiResponse } from "next"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +import { withRateLimit } from "~/utils/rateLimit"; + +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { if (req.method !== "GET") { return res.status(405).json({ message: "Method not allowed" }); } @@ -20,4 +21,5 @@ export default async function handler( console.error("Error fetching OSS friends:", error); return res.status(500).json({ message: "Failed to fetch OSS friends" }); } -} + }, +); diff --git a/apps/web/src/pages/api/stripe/create_billing_session.ts b/apps/web/src/pages/api/stripe/create_billing_session.ts index 3738703b..4c449ed6 100644 --- a/apps/web/src/pages/api/stripe/create_billing_session.ts +++ b/apps/web/src/pages/api/stripe/create_billing_session.ts @@ -3,11 +3,11 @@ import { env } from "next-runtime-env"; import { createNextApiContext } from "@kan/api/trpc"; import { createStripeClient } from "@kan/stripe"; +import { withRateLimit } from "~/utils/rateLimit"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { const stripe = createStripeClient(); if (req.method !== "POST") { @@ -31,4 +31,5 @@ export default async function handler( console.error("Error:", error); return res.status(500).json({ error: "Error creating portal session" }); } -} + }, +); diff --git a/apps/web/src/pages/api/stripe/create_checkout_session.ts b/apps/web/src/pages/api/stripe/create_checkout_session.ts index 79606154..a5fd69c3 100644 --- a/apps/web/src/pages/api/stripe/create_checkout_session.ts +++ b/apps/web/src/pages/api/stripe/create_checkout_session.ts @@ -6,6 +6,7 @@ import { createNextApiContext } from "@kan/api/trpc"; import * as subscriptionRepo from "@kan/db/repository/subscription.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { createStripeClient } from "@kan/stripe"; +import { withRateLimit } from "~/utils/rateLimit"; const workspaceSlugSchema = z .string() @@ -21,10 +22,9 @@ interface CheckoutSessionRequest { stripeCustomerId: string; } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { const stripe = createStripeClient(); if (req.method !== "POST") { @@ -115,4 +115,5 @@ export default async function handler( console.error("Error:", error); return res.status(500).json({ error: "Error creating checkout session" }); } -} + }, +); diff --git a/apps/web/src/pages/api/stripe/webhook.ts b/apps/web/src/pages/api/stripe/webhook.ts index b138fb26..259fdaa1 100644 --- a/apps/web/src/pages/api/stripe/webhook.ts +++ b/apps/web/src/pages/api/stripe/webhook.ts @@ -4,6 +4,7 @@ import type { Readable } from "node:stream"; import { createNextApiContext } from "@kan/api/trpc"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; import { createStripeClient } from "@kan/stripe"; +import { withRateLimit } from "~/utils/rateLimit"; async function buffer(readable: Readable) { const chunks = []; @@ -13,10 +14,9 @@ async function buffer(readable: Readable) { return Buffer.concat(chunks); } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { const stripe = createStripeClient(); if (req.method !== "POST") { @@ -64,7 +64,8 @@ export default async function handler( console.error("Webhook error:", err); return res.status(400).json({ message: "Webhook handler failed" }); } -} + }, +); export const config = { api: { diff --git a/apps/web/src/pages/api/trello/authenticate.ts b/apps/web/src/pages/api/trello/authenticate.ts index 50cbb9b9..7155c017 100644 --- a/apps/web/src/pages/api/trello/authenticate.ts +++ b/apps/web/src/pages/api/trello/authenticate.ts @@ -3,11 +3,11 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { createNextApiContext } from "@kan/api/trpc"; import { integrations } from "@kan/db/schema"; import { addYears } from "date-fns"; +import { withRateLimit } from "~/utils/rateLimit"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { if (req.method !== "POST") { return res.status(405).json({ message: "Method not allowed" }); } @@ -48,4 +48,5 @@ export default async function handler( console.error("Trello authentication error:", err); return res.status(400).json({ message: "Trello authentication failed" }); } -} \ No newline at end of file + }, +); \ No newline at end of file diff --git a/apps/web/src/pages/api/unsubscribe.ts b/apps/web/src/pages/api/unsubscribe.ts index 1631266c..44093ec2 100644 --- a/apps/web/src/pages/api/unsubscribe.ts +++ b/apps/web/src/pages/api/unsubscribe.ts @@ -4,6 +4,7 @@ import { jwtVerify } from "jose"; import { z } from "zod"; import { env } from "~/env"; +import { withRateLimit } from "~/utils/rateLimit"; const requestSchema = z.object({ token: z.string().min(1), @@ -19,10 +20,9 @@ type ResponseData = const textEncoder = new TextEncoder(); -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { if (process.env.NEXT_PUBLIC_KAN_ENV !== "cloud") { return res.status(404).json({ success: false, @@ -101,4 +101,5 @@ export default async function handler( } return res.status(200).json({ success: true }); -} + }, +); diff --git a/apps/web/src/pages/api/upload/image.ts b/apps/web/src/pages/api/upload/image.ts index e250bbd6..f9c60b39 100644 --- a/apps/web/src/pages/api/upload/image.ts +++ b/apps/web/src/pages/api/upload/image.ts @@ -6,13 +6,13 @@ import { env as nextRuntimeEnv } from "next-runtime-env"; import { createNextApiContext } from "@kan/api/trpc"; import { env } from "~/env"; +import { withRateLimit } from "~/utils/rateLimit"; const allowedContentTypes = ["image/jpeg", "image/png"]; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { if (req.method !== "POST") { return res.status(405).json({ error: "Method not allowed" }); } @@ -71,4 +71,5 @@ export default async function handler( } catch (error) { return res.status(500).json({ error: (error as Error).message }); } -} + }, +); diff --git a/apps/web/src/pages/api/v1/[...trpc].ts b/apps/web/src/pages/api/v1/[...trpc].ts index 4fe7ac6f..9a81abcc 100644 --- a/apps/web/src/pages/api/v1/[...trpc].ts +++ b/apps/web/src/pages/api/v1/[...trpc].ts @@ -6,25 +6,26 @@ import { appRouter } from "@kan/api"; import { createRESTContext } from "@kan/api/trpc"; import { env } from "~/env"; +import { withRateLimit } from "~/utils/rateLimit"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - await cors(req, res); +export default withRateLimit( + { points: 100, duration: 60 }, + async (req: NextApiRequest, res: NextApiResponse) => { + await cors(req, res); - const openApiHandler = createOpenApiNextHandler({ - router: appRouter, - createContext: createRESTContext, - onError: - env.NODE_ENV === "development" - ? ({ path, error }) => { - console.error( - `❌ REST failed on ${path ?? ""}: ${error.message}`, - ); - } - : undefined, - }); + const openApiHandler = createOpenApiNextHandler({ + router: appRouter, + createContext: createRESTContext, + onError: + env.NODE_ENV === "development" + ? ({ path, error }) => { + console.error( + `❌ REST failed on ${path ?? ""}: ${error.message}`, + ); + } + : undefined, + }); - return await openApiHandler(req, res); -} + return await openApiHandler(req, res); + }, +); diff --git a/apps/web/src/pages/api/v1/openapi.json.ts b/apps/web/src/pages/api/v1/openapi.json.ts index 36cd3a57..20dd4b5a 100644 --- a/apps/web/src/pages/api/v1/openapi.json.ts +++ b/apps/web/src/pages/api/v1/openapi.json.ts @@ -1,9 +1,11 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { openApiDocument } from "@kan/api/openapi"; +import { withRateLimit } from "~/utils/rateLimit"; -const handler = (req: NextApiRequest, res: NextApiResponse) => { - res.status(200).send(openApiDocument); -}; - -export default handler; +export default withRateLimit( + { points: 100, duration: 60 }, + (req: NextApiRequest, res: NextApiResponse) => { + res.status(200).send(openApiDocument); + }, +);