Skip to content

Commit

Permalink
[Task] #43, add slowDown and RateLimit for failed login attempts
Browse files Browse the repository at this point in the history
  • Loading branch information
Type-Style committed Mar 5, 2024
1 parent 5087ae2 commit 63c8ef7
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 23 deletions.
41 changes: 27 additions & 14 deletions src/controller/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { create as createError } from '@src/middleware/error';
import { validationResult, query } from 'express-validator';
import jwt from 'jsonwebtoken';
import logger from '@src/scripts/logger';
import { create } from 'domain';
import { crypt } from '@src/scripts/crypt';
import { loginSlowDown, loginLimiter } from '@src/middleware/limit';

const router = express.Router();

Expand Down Expand Up @@ -43,29 +44,41 @@ router.get("/login/", async function login(req: Request, res: Response) {
res.render("login-form");
});

router.post("/login/", async function postLogin(req: Request, res: Response) {
router.post("/login/", loginSlowDown, async function postLogin(req: Request, res: Response, next: NextFunction) {
logger.log("post login was called");
logger.log(req.body);
res.locals.text = "post recieved";

// TODO login authentication here
const validLogin = true;
if (!validLogin) {
return res.redirect("/read/login");
} else {
createToken(req, res);
res.render("login-form"); // TODO Send Token only
}
loginLimiter(req, res, () => {
let validLogin = false;
const password = crypt(req.body.password);
// Loop through all environment variables
for (const key in process.env) {
if (!key.startsWith('USER')) { continue; }
if (key.substring(5) == req.body.user &&
process.env[key] == password) {
validLogin = true;
break;
}
}
if (validLogin) {
const token = createToken(req, res);
res.json({ "token": token });
} else {
res.redirect("/read/login");
}
});
});

function isLoggedIn(req: Request, res: Response) {
console.log("login check");
const result = validateToken(req, res);
if (!result) {
return res.redirect("/read/login");
loginLimiter(req, res, () => {
res.redirect("/read/login");
});
}
}


function validateToken(req: Request, res: Response) {
const key = process.env.KEYB;
const header = req.header('Authorization');
Expand All @@ -85,7 +98,6 @@ function validateToken(req: Request, res: Response) {
}
}


function createToken(req: Request, res: Response) {
const key = process.env.KEYB;
if (!key) { throw new Error('KEYA is not defined in the environment variables'); }
Expand All @@ -95,6 +107,7 @@ function createToken(req: Request, res: Response) {
};
const token = jwt.sign(payload, key, { expiresIn: 60 * 1 });
res.locals.token = token;
return token;
}

export default router;
2 changes: 1 addition & 1 deletion src/controller/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ async function writeData(req: Request, res: Response, next: NextFunction) {


const router = express.Router();
router.get('/', baseSlowDown, entry.validate, errorChecking, writeData);
router.get('/', baseSlowDown, entry.validate, errorChecking, writeData);
router.head('/', baseSlowDown, entry.validate, errorChecking);

export default router;
31 changes: 23 additions & 8 deletions src/middleware/limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import logger from '@src/scripts/logger';
*/
const baseOptions: Partial<rateLimiterOptions & slowDownOptions> = {
windowMs: 30 * 60 * 1000,
skip: (req, res) => (res.locals.ip == "127.0.0.1" || res.locals.ip == "::1")
//skip: (req, res) => (res.locals.ip == "127.0.0.1" || res.locals.ip == "::1")
}

const baseSlowDownOptions: Partial<slowDownOptions> = {
Expand All @@ -22,6 +22,14 @@ const baseRateLimitOptions: Partial<rateLimiterOptions> = {
limit: 10, // Limit each IP per window
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
handler: function rateHandler(req: Request, res: Response, next: NextFunction, options: rateLimiterOptions) {
if (!Object.prototype.hasOwnProperty.call(ipsThatReachedLimit, res.locals.ip)) {
logger.error(`[RateLimit] reached ${req.originalUrl}, ${res.locals.ip}, ${req.get('User-Agent')}`);
ipsThatReachedLimit[res.locals.ip] = { limitReachedOnError: true, time: Date.now() };
}
res.status(options.statusCode).send(options.message);
}

}


Expand All @@ -44,14 +52,21 @@ setInterval(() => {
*/
export const baseSlowDown = slowDown(baseSlowDownOptions);

export const loginSlowDown = slowDown({
...baseSlowDownOptions,
delayAfter: 1, // no delay for amount of attempts
delayMs: (used: number) => (used - 1) * 250, // Add delay after delayAfter is reached
});


export const errorRateLimiter = rateLimit({
...baseRateLimitOptions,
message: 'Too many requests with errors',
handler: (req: Request, res: Response, next: NextFunction, options: rateLimiterOptions) => {
if (!Object.prototype.hasOwnProperty.call(ipsThatReachedLimit, res.locals.ip)) {
logger.error(`[RateLimit] for invalid requests reached ${res.locals.ip}, ${req.get('User-Agent')}`);
ipsThatReachedLimit[res.locals.ip] = { limitReachedOnError: true, time: Date.now() };
}
res.status(options.statusCode).send(options.message);
}
});

export const loginLimiter = rateLimit({
...baseRateLimitOptions,
windowMs: 3 * 60 * 1000,
limit: 3,
message: 'Too many failed login attempts',
});

0 comments on commit 63c8ef7

Please sign in to comment.