Skip to content

Commit 5797162

Browse files
authored
feat: added support for limiting the number of attempts (#59)(@sviat9440)
* feat: added support for limiting the number of attempts * fixes
1 parent 519f0c2 commit 5797162

File tree

5 files changed

+96
-21
lines changed

5 files changed

+96
-21
lines changed

examples/auth.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import AdminJS from "adminjs";
2-
import express from "express";
3-
import mongoose from "mongoose";
4-
import MongooseAdapter from "@adminjs/mongoose";
5-
import AdminJSExpress from "../src";
1+
import MongooseAdapter from '@adminjs/mongoose';
2+
import AdminJS from 'adminjs';
3+
import express from 'express';
4+
import mongoose from 'mongoose';
5+
import AdminJSExpress from '../src';
6+
import './mongoose/admin-model';
67

7-
import "./mongoose/article-model";
8-
import "./mongoose/admin-model";
8+
import './mongoose/article-model';
99

1010
AdminJS.registerAdapter(MongooseAdapter);
1111

@@ -33,6 +33,10 @@ const start = async () => {
3333
return null;
3434
},
3535
cookiePassword: "somasd1nda0asssjsdhb21uy3g",
36+
maxRetries: {
37+
count: 3,
38+
duration: 120,
39+
},
3640
});
3741

3842
app.use(adminJs.options.rootPath, router);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adminjs/express",
3-
"version": "4.0.3",
3+
"version": "4.1.0",
44
"description": "This is an official AdminJS plugin which integrates it with Express.js framework",
55
"main": "lib/index.js",
66
"scripts": {

src/authentication/login.handler.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import AdminJS from "adminjs";
2-
import { Router } from "express";
3-
import { AuthenticationOptions } from "../types";
1+
import AdminJS from 'adminjs';
2+
import { Router } from 'express';
3+
import {
4+
AuthenticationMaxRetriesOptions,
5+
AuthenticationOptions,
6+
} from '../types';
47

58
const getLoginPath = (admin: AdminJS): string => {
69
const { loginPath, rootPath } = admin.options;
@@ -15,6 +18,48 @@ const getLoginPath = (admin: AdminJS): string => {
1518
: `/${normalizedLoginPath}`;
1619
};
1720

21+
class Retry {
22+
private static retriesContainer: Map<string, Retry> = new Map();
23+
private lastRetry: Date | undefined;
24+
private retriesCount = 0;
25+
26+
constructor(ip: string) {
27+
const existing = Retry.retriesContainer.get(ip);
28+
if (existing) {
29+
return existing;
30+
}
31+
Retry.retriesContainer.set(ip, this);
32+
}
33+
34+
public canLogin(
35+
maxRetries: number | AuthenticationMaxRetriesOptions | undefined
36+
): boolean {
37+
if (maxRetries === undefined) {
38+
return true;
39+
} else if (typeof maxRetries === "number") {
40+
maxRetries = {
41+
count: maxRetries,
42+
duration: 60,
43+
};
44+
} else if (maxRetries.count <= 0) {
45+
return true;
46+
}
47+
if (
48+
!this.lastRetry ||
49+
new Date().getTime() - this.lastRetry.getTime() >
50+
maxRetries.duration * 1000
51+
) {
52+
this.lastRetry = new Date();
53+
this.retriesCount = 1;
54+
return true;
55+
} else {
56+
this.lastRetry = new Date();
57+
this.retriesCount++;
58+
return this.retriesCount <= maxRetries.count;
59+
}
60+
}
61+
}
62+
1863
export const withLogin = (
1964
router: Router,
2065
admin: AdminJS,
@@ -32,6 +77,14 @@ export const withLogin = (
3277
});
3378

3479
router.post(loginPath, async (req, res, next) => {
80+
if (!new Retry(req.ip).canLogin(auth.maxRetries)) {
81+
const login = await admin.renderLogin({
82+
action: admin.options.loginPath,
83+
errorMessage: "tooManyRequests",
84+
});
85+
res.send(login);
86+
return;
87+
}
3588
const { email, password } = req.fields as {
3689
email: string;
3790
password: string;

src/buildAuthenticatedRouter.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import AdminJS from "adminjs";
2-
import express, { Router } from "express";
3-
import session from "express-session";
4-
import { withLogout } from "./authentication/logout.handler";
5-
import { buildRouter } from "./buildRouter";
6-
import { OldBodyParserUsedError } from "./errors";
7-
import { AuthenticationOptions, FormidableOptions } from "./types";
8-
import { withLogin } from "./authentication/login.handler";
9-
import { withProtectedRoutesHandler } from "./authentication/protected-routes.handler";
10-
import formidableMiddleware from "express-formidable";
1+
import AdminJS from 'adminjs';
2+
import express, { Router } from 'express';
3+
import formidableMiddleware from 'express-formidable';
4+
import session from 'express-session';
5+
import { withLogin } from './authentication/login.handler';
6+
import { withLogout } from './authentication/logout.handler';
7+
import { withProtectedRoutesHandler } from './authentication/protected-routes.handler';
8+
import { buildRouter } from './buildRouter';
9+
import { OldBodyParserUsedError } from './errors';
10+
import {
11+
AuthenticationOptions,
12+
FormidableOptions,
13+
} from './types';
1114

1215
/**
1316
* @typedef {Function} Authenticate

src/types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,23 @@ export type FormidableOptions = {
1010
multiples?: boolean;
1111
};
1212

13+
export type AuthenticationMaxRetriesOptions = {
14+
/**
15+
* @description Count of retries
16+
*/
17+
count: number;
18+
/**
19+
* @description Time to reset (in seconds)
20+
*/
21+
duration: number;
22+
};
23+
1324
export type AuthenticationOptions = {
1425
cookiePassword: string;
1526
cookieName?: string;
1627
authenticate: (email: string, password: string) => unknown | null;
28+
/**
29+
* @description Maximum number of authorization attempts (if number - per minute)
30+
*/
31+
maxRetries?: number | AuthenticationMaxRetriesOptions;
1732
};

0 commit comments

Comments
 (0)