Skip to content
This repository has been archived by the owner on Jul 21, 2022. It is now read-only.

Commit

Permalink
Added ratelimits to the API
Browse files Browse the repository at this point in the history
  • Loading branch information
FireMario211 committed Oct 2, 2021
1 parent 9950dde commit d97be93
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 12 deletions.
10 changes: 7 additions & 3 deletions src/modules/api/announcements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ function decode_base64(str) {
}

export const prop = {
name: "announcements",
desc: "API for Announcements",
run: async (req: express.Request, res: express.Response): Promise<any> => {
name: "announcements",
desc: "API for Announcements",
rateLimit: {
max: 10,
time: 30 * 1000
},
run: async (req: express.Request, res: express.Response): Promise<any> => {
const allowedMethods = ["GET", "POST", "DELETE"];
res.set("Allow", allowedMethods.join(", ")); // To give the method of whats allowed
if (!allowedMethods.includes(req.method)) return res.sendStatus(405);
Expand Down
4 changes: 4 additions & 0 deletions src/modules/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ export const auth = {
export const prop = {
name: "auth",
desc: "Authenticates a user (Logging in)",
rateLimit: {
max: 10,
time: 60 * 1000
},
run: async (req: express.Request, res: express.Response): Promise<unknown> => {
res.set("Allow", "POST"); // To give the method of whats allowed
if (req.method != "POST") return res.sendStatus(405) // If the request isn't POST then respond with Method not Allowed.
Expand Down
4 changes: 4 additions & 0 deletions src/modules/api/mail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@
export const prop = {
name: "mail",
desc: "Sends a \"mail\" from /billing/mail to support@amethyst.host",
rateLimit: {
max: 3,
time: 60 * 1000
},
run: async (req: express.Request, res: express.Response): Promise<any> => {
res.set("Allow", "POST"); // To give the method of whats allowed
if (req.method != "POST") return res.sendStatus(405) // If the request isn't a POST request, then respond with Method not Allowed.
Expand Down
4 changes: 4 additions & 0 deletions src/modules/api/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const stripe = require('stripe')(process.env.STRIPE_SK_TEST);
export const prop = {
name: "order",
desc: "API for ordering a service",
rateLimit: {
max: 1,
time: 30 * 1000
},
run: async (req: express.Request, res: express.Response): Promise<any> => {
const allowedMethods = ["GET", "POST"];
res.set("Allow", allowedMethods.join(", ")); // To give the method of whats allowed
Expand Down
4 changes: 4 additions & 0 deletions src/modules/api/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import fetch from 'node-fetch';
export const prop = {
name: "register",
desc: "Creates an account (Only on Website)",
rateLimit: {
max: 3,
time: 2 * (60 * 1000) // 2 Minutes
},
run: async (req: express.Request, res: express.Response) => {
res.set("Allow", "POST"); // To give the method of whats allowed
if (req.method != "POST") return res.sendStatus(405) // If the request isn't POST then respond with Method not Allowed.
Expand Down
4 changes: 4 additions & 0 deletions src/modules/api/tickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ function editContent(content, timestamp, ticket_id) {
export const prop = {
name: "tickets",
desc: "Support Ticket System",
rateLimit: {
max: 20,
time: 10 * 1000
},
run: async (req: express.Request, res: express.Response): Promise<any> => {
const allowedMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"];
const params = req.params[0].split("/").slice(1); // Probably a better way to do this in website.ts via doing /api/:method/:optionalparam*? but it doesnt work for me.
Expand Down
4 changes: 4 additions & 0 deletions src/modules/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
export const prop = {
name: "user",
desc: "API for users.",
rateLimit: {
max: 2,
time: 10 * 1000
},
run: async (req: express.Request, res: express.Response) => {
// Copy & Paste from tickets.ts hA
const allowedMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"];
Expand Down
30 changes: 24 additions & 6 deletions src/modules/website.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import cookieParser from "cookie-parser"
import { auth } from "./api/auth"
import * as plans from "../plans.json"
import { sql } from './sql'
import { Ticket } from "../types/billing/ticket";
import { Ticket } from "../types/billing/ticket";
import { UserData } from "../types/billing/user";
import rateLimit from "express-rate-limit";
//import cors from "cors"
const app : express.Application = express();
const html: string = path.join(__dirname, "views", "html");
Expand All @@ -26,11 +27,27 @@ const files : Array<string> = fs.readdirSync(`./dist/modules/api`)
for (const f of files) {
const ep: Endpoint = require(`./api/${f.replace(".js", "")}`);
endpoints.set(ep.prop.name, ep);
if (ep.prop["rateLimit"]) {
app.use("/api/" + ep.prop.name + "*", rateLimit({
windowMs: ep.prop["rateLimit"].time,
max: ep.prop["rateLimit"].max,
message: "You are sending too many API requests! Please try again later.",
}));
// There could be another solution for doing this.
}

}
util.expressLog(`${endpoints.size} api endpoints loaded`);

app.use(cookieParser())

// 30 requests every 40 seconds
const apiLimiter = rateLimit({
windowMs: 40 * 1000, // 40 seconds
max: 30,
message: "You are sending too many API requests! Please try again later."
});

app.use(morgan("[express]\t:method :url :status :res[content-length] - :response-time ms"));

// serve static files
Expand Down Expand Up @@ -77,21 +94,22 @@ app.get("/", (r: express.Request, s: express.Response) => {
});

// Probably another method to do this, but this is the best I can think of right now.
const apiMethod = function (r: express.Request, s: express.Response) {
const apiMethod = async function (r: express.Request, s: express.Response, next: express.NextFunction) {
const ep: Endpoint = endpoints.get(r.params.method)
if (ep) { // Prevent site from sending errors when the :method is not defined.
ep.prop.run(r, s);

// Ratelimiter could be improved.
await ep.prop.run(r, s);
} else {
return s.status(404)
.send("if you were searching for a 404.. you found it!!");
}
}

/* amethyst.host/api/bill
amethyst.host/api/auth
and so on.. */
app.all("/api/:method*", (r: express.Request, s: express.Response) => {
apiMethod(r, s);
app.all("/api/:method*", apiLimiter, (r: express.Request, s: express.Response, next: express.NextFunction) => {
apiMethod(r, s, next);
});
// billing
app.get("/billing", (r: express.Request, s: express.Response) => {
Expand Down
9 changes: 6 additions & 3 deletions src/types/endpoint.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { Request, Response } from "express";

export interface Endpoint {
prop: {
readonly name : string;
readonly desc?: string;

readonly name : string;
readonly desc? : string;
readonly rateLimit?: {
readonly max : number,
readonly time : number
}
run: (req: Request,
res: Response)
=> void;
Expand Down

0 comments on commit d97be93

Please sign in to comment.