Skip to content

Commit 4fd369f

Browse files
committed
refactor: Extract more functions
1 parent 3bf7765 commit 4fd369f

14 files changed

+191
-149
lines changed

.eslintrc.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
module.exports = {
2-
parser: '@typescript-eslint/parser',
3-
plugins: ['@typescript-eslint/eslint-plugin'],
2+
parser: "@typescript-eslint/parser",
3+
plugins: ["@typescript-eslint/eslint-plugin"],
44
extends: [
5-
'plugin:@typescript-eslint/recommended',
6-
'plugin:prettier/recommended',
5+
"plugin:@typescript-eslint/recommended",
6+
"plugin:prettier/recommended",
77
],
88
parserOptions: {
99
ecmaVersion: 2018,
10-
sourceType: 'module',
10+
sourceType: "module",
1111
ecmaFeatures: {
1212
jsx: true,
1313
},
1414
},
1515
rules: {
16-
'prettier/prettier': 'error'
16+
"prettier/prettier": "error",
1717
},
18-
ignorePatterns: ['node_modules', 'lib'],
18+
ignorePatterns: ["node_modules", "lib", "types"],
1919
settings: {
2020
react: {
21-
version: 'detect',
21+
version: "detect",
2222
},
2323
},
24-
};
24+
};

examples/auth.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import AdminBro from "admin-bro";
22
import express from "express";
33
import mongoose from "mongoose";
44
import MongooseAdapter from "@admin-bro/mongoose";
5-
6-
AdminBro.registerAdapter(MongooseAdapter);
7-
85
import AdminBroExpress from "../index";
96

107
import "./mongoose/article-model";
118
import "./mongoose/admin-model";
129

10+
AdminBro.registerAdapter(MongooseAdapter);
11+
1312
const ADMIN = {
1413
1514
password: "password",

examples/simple.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import AdminBro from "admin-bro";
22
import express from "express";
33
import mongoose from "mongoose";
4-
54
import MongooseAdapter from "@admin-bro/mongoose";
65

7-
AdminBro.registerAdapter(MongooseAdapter);
8-
96
import AdminBroExpress from "../index";
10-
117
import "./mongoose/article-model";
128
import "./mongoose/admin-model";
139

10+
AdminBro.registerAdapter(MongooseAdapter);
11+
1412
const start = async () => {
1513
const connection = await mongoose.connect(
16-
process.env.MONGO_URL || "mongodb://localhost:27017/example"
14+
process.env.MONGO_URL || "mongodb://localhost:27017/example",
15+
{ useNewUrlParser: true, useUnifiedTopology: true }
1716
);
1817
const app = express();
1918

index.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
/* eslint-disable max-len */
2+
import { buildAuthenticatedRouter } from "./src/buildAuthenticatedRouter";
3+
import { buildRouter } from "./src/buildRouter";
4+
25
/**
36
* @module @admin-bro/express
47
* @subcategory Plugins
@@ -88,7 +91,13 @@
8891
* meaning that it should have at least an email property.
8992
*/
9093

91-
import * as Plugin from "./src/plugin";
94+
/**
95+
* Plugin name
96+
* @static
97+
* @memberof module:@admin-bro/express
98+
*/
99+
export const name = "AdminBroExpressjs";
100+
101+
module.exports = { name, buildAuthenticatedRouter, buildRouter };
92102

93-
module.exports = Plugin;
94-
export default Plugin;
103+
export default { name, buildAuthenticatedRouter, buildRouter };

nodemon.auth.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"verbose": false,
3+
"ignore": ["*.test.*", "*.spec.*"],
4+
"exec": "ts-node --files examples/auth.ts",
5+
"watch": [
6+
"src",
7+
"examples"
8+
],
9+
"ext": "ts"
10+
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"scripts": {
77
"dev": "rm -rf lib && tsc --watch",
88
"build": "rm -rf lib && tsc",
9-
"simple": "nodemon",
9+
"example:simple": "nodemon",
10+
"example:auth": "nodemon --config nodemon.auth.json",
1011
"test": "jest --config ./test/jest.json",
1112
"lint": "eslint './**/*.ts'",
1213
"check:all": "yarn lint && yarn build && yarn test",

src/authentication/login.handler.ts

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import AdminBro from "admin-bro";
2+
import { Router } from "express";
3+
import { AuthenticationOptions } from "../types";
4+
5+
const getLoginPath = (admin: AdminBro): string => {
6+
const { loginPath, rootPath } = admin.options;
7+
// since we are inside already namespaced router we have to replace login and logout routes that
8+
// they don't have rootUrl inside. So changing /admin/login to just /login.
9+
// but there is a case where user gives / as a root url and /login becomes `login`. We have to
10+
// fix it by adding / in front of the route
11+
const normalizedLoginPath = loginPath.replace(rootPath, "");
12+
13+
return normalizedLoginPath.startsWith("/")
14+
? normalizedLoginPath
15+
: `/${normalizedLoginPath}`;
16+
};
17+
18+
export const withLogin = (
19+
router: Router,
20+
admin: AdminBro,
21+
auth: AuthenticationOptions
22+
): void => {
23+
const { rootPath } = admin.options;
24+
const loginPath = getLoginPath(admin);
25+
26+
router.get(loginPath, async (req, res) => {
27+
const login = await admin.renderLogin({
28+
action: admin.options.loginPath,
29+
errorMessage: null,
30+
});
31+
res.send(login);
32+
});
33+
34+
router.post(loginPath, async (req, res, next) => {
35+
const { email, password } = req.fields as {
36+
email: string;
37+
password: string;
38+
};
39+
const adminUser = await auth.authenticate(email, password);
40+
if (adminUser) {
41+
req.session.adminUser = adminUser;
42+
req.session.save((err) => {
43+
if (err) {
44+
next(err);
45+
}
46+
if (req.session.redirectTo) {
47+
res.redirect(req.session.redirectTo);
48+
} else {
49+
res.redirect(rootPath);
50+
}
51+
});
52+
} else {
53+
const login = await admin.renderLogin({
54+
action: admin.options.loginPath,
55+
errorMessage: "invalidCredentials",
56+
});
57+
res.send(login);
58+
}
59+
});
60+
};

src/authentication/logout.handler.ts

+18-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import AdminBro from "admin-bro";
2-
import { RequestHandler } from "express-serve-static-core";
3-
4-
export const createLogoutHandler = (admin: AdminBro): RequestHandler => async (
5-
request,
6-
response
7-
) => {
8-
request.session.destroy(() => {
9-
response.redirect(admin.options.loginPath);
2+
import { Router } from "express";
3+
4+
const getLogoutPath = (admin: AdminBro) => {
5+
const { logoutPath, rootPath } = admin.options;
6+
const normalizedLogoutPath = logoutPath.replace(rootPath, "");
7+
8+
return normalizedLogoutPath.startsWith("/")
9+
? normalizedLogoutPath
10+
: `/${normalizedLogoutPath}`;
11+
};
12+
13+
export const withLogout = (router: Router, admin: AdminBro): void => {
14+
const logoutPath = getLogoutPath(admin);
15+
16+
router.get(logoutPath, async (request, response) => {
17+
request.session.destroy(() => {
18+
response.redirect(admin.options.loginPath);
19+
});
1020
});
1121
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import AdminBro, { Router as AdminRouter } from "admin-bro";
2+
import { Router } from "express";
3+
4+
export const withProtectedRoutesHandler = (
5+
router: Router,
6+
admin: AdminBro
7+
): void => {
8+
const { rootPath } = admin.options;
9+
10+
router.use((req, res, next) => {
11+
if (AdminRouter.assets.find((asset) => req.originalUrl.match(asset.path))) {
12+
next();
13+
} else if (
14+
req.session.adminUser ||
15+
// these routes doesn't need authentication
16+
req.originalUrl.startsWith(admin.options.loginPath) ||
17+
req.originalUrl.startsWith(admin.options.logoutPath)
18+
) {
19+
next();
20+
} else {
21+
// If the redirection is caused by API call to some action just redirect to resource
22+
const [redirectTo] = req.originalUrl.split("/actions");
23+
req.session.redirectTo = redirectTo.includes(`${rootPath}/api`)
24+
? rootPath
25+
: redirectTo;
26+
req.session.save((err) => {
27+
if (err) {
28+
next(err);
29+
}
30+
res.redirect(admin.options.loginPath);
31+
});
32+
}
33+
});
34+
};

src/buildAuthenticatedRouter.ts

+10-97
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import AdminBro, { Router as AdminRouter } from "admin-bro";
1+
import AdminBro from "admin-bro";
22
import express, { Router } from "express";
3-
import formidableMiddleware from "express-formidable";
43
import session from "express-session";
5-
import { createLogoutHandler } from "./authentication/logout.handler";
4+
import { withLogout } from "./authentication/logout.handler";
65
import { buildRouter } from "./buildRouter";
76
import { OldBodyParserUsedError } from "./errors";
8-
import { FormidableOptions } from "./types";
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";
911

1012
/**
1113
* @typedef {Function} Authenticate
@@ -24,16 +26,6 @@ import { FormidableOptions } from "./types";
2426
* dependency. Normally express-session holds session in memory, which is
2527
* not optimized for production usage and, in development, it causes
2628
* logging out after every page refresh (if you use nodemon).
27-
*
28-
* @param {AdminBro} admin instance of AdminBro
29-
* @param {Object} auth authentication options
30-
* @param {module:@admin-bro/express.Authenticate} auth.authenticate authenticate function
31-
* @param {String} auth.cookiePassword secret used to encrypt cookies
32-
* @param {String} auth.cookieName=adminbro cookie name
33-
* @param {express.Router} [predefinedRouter] Express.js router
34-
* @param {SessionOptions} [sessionOptions] Options that are passed to [express-session](https://github.com/expressjs/session)
35-
* @param {ExpressFormidableOptions} [formidableOptions] Options that are passed to [express-session](https://github.com/expressjs/session)
36-
* @return {express.Router} Express.js router
3729
* @static
3830
* @memberof module:@admin-bro/express
3931
* @example
@@ -55,11 +47,7 @@ import { FormidableOptions } from "./types";
5547
*/
5648
export const buildAuthenticatedRouter = (
5749
admin: AdminBro,
58-
auth: {
59-
cookiePassword: string;
60-
cookieName?: string;
61-
authenticate: (email: string, password: string) => unknown | null;
62-
},
50+
auth: AuthenticationOptions,
6351
predefinedRouter?: express.Router,
6452
sessionOptions?: session.SessionOptions,
6553
formidableOptions?: FormidableOptions
@@ -80,86 +68,11 @@ export const buildAuthenticatedRouter = (
8068
name: auth.cookieName || "adminbro",
8169
})
8270
);
83-
8471
router.use(formidableMiddleware(formidableOptions));
8572

86-
const { rootPath } = admin.options;
87-
let { loginPath, logoutPath } = admin.options;
88-
// since we are inside already namespaced router we have to replace login and logout routes that
89-
// they don't have rootUrl inside. So changing /admin/login to just /login.
90-
// but there is a case where user gives / as a root url and /login becomes `login`. We have to
91-
// fix it by adding / in front of the route
92-
loginPath = loginPath.replace(rootPath, "");
93-
if (!loginPath.startsWith("/")) {
94-
loginPath = `/${loginPath}`;
95-
}
96-
97-
logoutPath = logoutPath.replace(rootPath, "");
98-
if (!logoutPath.startsWith("/")) {
99-
logoutPath = `/${logoutPath}`;
100-
}
101-
102-
router.get(loginPath, async (req, res) => {
103-
const login = await admin.renderLogin({
104-
action: admin.options.loginPath,
105-
errorMessage: null,
106-
});
107-
res.send(login);
108-
});
109-
110-
router.post(loginPath, async (req, res, next) => {
111-
const { email, password } = req.fields as {
112-
email: string;
113-
password: string;
114-
};
115-
const adminUser = await auth.authenticate(email, password);
116-
if (adminUser) {
117-
req.session.adminUser = adminUser;
118-
req.session.save((err) => {
119-
if (err) {
120-
next(err);
121-
}
122-
if (req.session.redirectTo) {
123-
res.redirect(req.session.redirectTo);
124-
} else {
125-
res.redirect(rootPath);
126-
}
127-
});
128-
} else {
129-
const login = await admin.renderLogin({
130-
action: admin.options.loginPath,
131-
errorMessage: "invalidCredentials",
132-
});
133-
res.send(login);
134-
}
135-
});
136-
137-
router.use((req, res, next) => {
138-
if (AdminRouter.assets.find((asset) => req.originalUrl.match(asset.path))) {
139-
next();
140-
} else if (
141-
req.session.adminUser ||
142-
// these routes doesn't need authentication
143-
req.originalUrl.startsWith(admin.options.loginPath) ||
144-
req.originalUrl.startsWith(admin.options.logoutPath)
145-
) {
146-
next();
147-
} else {
148-
// If the redirection is caused by API call to some action just redirect to resource
149-
const [redirectTo] = req.originalUrl.split("/actions");
150-
req.session.redirectTo = redirectTo.includes(`${rootPath}/api`)
151-
? rootPath
152-
: redirectTo;
153-
req.session.save((err) => {
154-
if (err) {
155-
next(err);
156-
}
157-
res.redirect(admin.options.loginPath);
158-
});
159-
}
160-
});
161-
162-
router.get(logoutPath, createLogoutHandler(admin));
73+
withProtectedRoutesHandler(router, admin);
74+
withLogin(router, admin, auth);
75+
withLogout(router, admin);
16376

16477
return buildRouter(admin, router, formidableOptions);
16578
};

src/plugin.ts

-12
This file was deleted.

0 commit comments

Comments
 (0)