Skip to content

Commit 26d3e84

Browse files
committed
feat: add support for auth providers
1 parent 04d9416 commit 26d3e84

File tree

7 files changed

+155
-72
lines changed

7 files changed

+155
-72
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"check:all": "yarn lint && yarn build"
2020
},
2121
"peerDependencies": {
22-
"adminjs": "^7.0.0"
22+
"adminjs": "^7.4.0"
2323
},
2424
"devDependencies": {
2525
"@semantic-release/git": "^10.0.1",
@@ -29,7 +29,7 @@
2929
"@types/node": "^18.15.3",
3030
"@typescript-eslint/eslint-plugin": "^5.56.0",
3131
"@typescript-eslint/parser": "^5.56.0",
32-
"adminjs": "^7.0.0",
32+
"adminjs": "^7.4.0",
3333
"eslint": "^8.36.0",
3434
"eslint-config-prettier": "^8.8.0",
3535
"eslint-plugin-import": "^2.27.5",

src/authentication/login.handler.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import AdminJS from 'adminjs';
22
import { FastifyInstance } from 'fastify';
33

4-
import { AuthenticationOptions } from '../types.js';
4+
import { AuthenticationContext, AuthenticationOptions } from '../types.js';
55

66
const getLoginPath = (admin: AdminJS): string => {
77
const { loginPath } = admin.options;
@@ -17,21 +17,45 @@ export const withLogin = (
1717
const { rootPath } = admin.options;
1818
const loginPath = getLoginPath(admin);
1919

20+
const { provider } = auth;
21+
const providerProps = provider?.getUiProps?.() ?? {};
22+
2023
fastifyInstance.get(loginPath, async (req, reply) => {
21-
const login = await admin.renderLogin({
24+
const baseProps = {
2225
action: admin.options.loginPath,
2326
errorMessage: null,
27+
};
28+
const login = await admin.renderLogin({
29+
...baseProps,
30+
...providerProps,
2431
});
2532
reply.type('text/html');
2633
reply.send(login);
2734
});
2835

2936
fastifyInstance.post(loginPath, async (req, reply) => {
30-
const { email, password } = req.body as {
31-
email: string;
32-
password: string;
33-
};
34-
const adminUser = await auth.authenticate(email, password);
37+
const context: AuthenticationContext = { request: req, reply };
38+
39+
let adminUser;
40+
if (provider) {
41+
adminUser = await provider.handleLogin(
42+
{
43+
headers: req.headers,
44+
query: req.query ?? {},
45+
params: req.params ?? {},
46+
data: req.body ?? {},
47+
},
48+
context
49+
);
50+
} else {
51+
const { email, password } = req.body as {
52+
email: string;
53+
password: string;
54+
};
55+
// "auth.authenticate" must always be defined if "auth.provider" isn't
56+
adminUser = await auth.authenticate!(email, password, context);
57+
}
58+
3559
if (adminUser) {
3660
req.session.set('adminUser', adminUser);
3761

@@ -44,6 +68,7 @@ export const withLogin = (
4468
const login = await admin.renderLogin({
4569
action: admin.options.loginPath,
4670
errorMessage: 'invalidCredentials',
71+
...providerProps,
4772
});
4873
reply.type('text/html');
4974
reply.send(login);

src/authentication/logout.handler.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import AdminJS from 'adminjs';
22
import { FastifyInstance } from 'fastify';
3+
import { AuthenticationOptions } from '../types.js';
34

45
const getLogoutPath = (admin: AdminJS) => {
56
const { logoutPath } = admin.options;
@@ -9,11 +10,17 @@ const getLogoutPath = (admin: AdminJS) => {
910

1011
export const withLogout = (
1112
fastifyApp: FastifyInstance,
12-
admin: AdminJS
13+
admin: AdminJS,
14+
auth: AuthenticationOptions,
1315
): void => {
1416
const logoutPath = getLogoutPath(admin);
17+
const { provider } = auth;
1518

1619
fastifyApp.get(logoutPath, async (request, reply) => {
20+
if (provider) {
21+
await provider.handleLogout({ request, reply });
22+
}
23+
1724
if (request.session) {
1825
request.session.destroy(() => {
1926
reply.redirect(admin.options.loginPath);

src/authentication/refresh.handler.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import AdminJS, { CurrentAdmin } from "adminjs";
2+
import { FastifyInstance } from "fastify";
3+
import { AuthenticationOptions } from "../types.js";
4+
import { WrongArgumentError } from "../errors.js";
5+
6+
const getRefreshTokenPath = (admin: AdminJS) => {
7+
const { refreshTokenPath, rootPath } = admin.options;
8+
const normalizedRefreshTokenPath = refreshTokenPath.replace(rootPath, "");
9+
10+
return normalizedRefreshTokenPath.startsWith("/")
11+
? normalizedRefreshTokenPath
12+
: `/${normalizedRefreshTokenPath}`;
13+
};
14+
15+
const MISSING_PROVIDER_ERROR =
16+
'"provider" has to be configured to use refresh token mechanism';
17+
18+
export const withRefresh = (
19+
fastifyApp: FastifyInstance,
20+
admin: AdminJS,
21+
auth: AuthenticationOptions
22+
): void => {
23+
const refreshTokenPath = getRefreshTokenPath(admin);
24+
25+
const { provider } = auth;
26+
27+
fastifyApp.post(refreshTokenPath, async (request, reply) => {
28+
if (!provider) {
29+
throw new WrongArgumentError(MISSING_PROVIDER_ERROR);
30+
}
31+
32+
const updatedAuthInfo = await provider.handleRefreshToken(
33+
{
34+
data: request.body ?? {},
35+
query: request.query ?? {},
36+
params: request.params ?? {},
37+
headers: request.headers,
38+
},
39+
{ request, reply }
40+
);
41+
42+
let admin = request.session.adminUser as Partial<CurrentAdmin> | null;
43+
if (!admin) {
44+
admin = {};
45+
}
46+
47+
if (!admin._auth) {
48+
admin._auth = {};
49+
}
50+
51+
admin._auth = {
52+
...admin._auth,
53+
...updatedAuthInfo,
54+
};
55+
56+
request.session.set('adminUser', admin);
57+
request.session.save(() => {
58+
reply.send(admin);
59+
});
60+
});
61+
};

src/buildAuthenticatedRouter.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ import { FastifyInstance } from 'fastify';
66

77
import { withLogin } from './authentication/login.handler.js';
88
import { withLogout } from './authentication/logout.handler.js';
9+
import { withRefresh } from './authentication/refresh.handler.js';
910
import { withProtectedRoutesHandler } from './authentication/protected-routes.handler.js';
1011
import { buildRouter } from './buildRouter.js';
1112
import { AuthenticationOptions } from './types.js';
13+
import { WrongArgumentError } from './errors.js';
14+
15+
const MISSING_AUTH_CONFIG_ERROR =
16+
'You must configure either "authenticate" method or assign an auth "provider"';
17+
const INVALID_AUTH_CONFIG_ERROR =
18+
'You cannot configure both "authenticate" and "provider". "authenticate" will be removed in next major release.';
1219

1320
/**
1421
* @typedef {Function} Authenticate
@@ -51,6 +58,21 @@ export const buildAuthenticatedRouter = async (
5158
fastifyApp: FastifyInstance,
5259
sessionOptions?: FastifySessionPlugin.FastifySessionOptions
5360
): Promise<void> => {
61+
if (!auth.authenticate && !auth.provider) {
62+
throw new WrongArgumentError(MISSING_AUTH_CONFIG_ERROR);
63+
}
64+
65+
if (auth.authenticate && auth.provider) {
66+
throw new WrongArgumentError(INVALID_AUTH_CONFIG_ERROR);
67+
}
68+
69+
if (auth.provider) {
70+
admin.options.env = {
71+
...admin.options.env,
72+
...auth.provider.getUiProps(),
73+
};
74+
}
75+
5476
await fastifyApp.register(fastifyCookie, {
5577
secret: auth.cookiePassword,
5678
});
@@ -65,5 +87,6 @@ export const buildAuthenticatedRouter = async (
6587
await buildRouter(admin, fastifyApp);
6688
withProtectedRoutesHandler(fastifyApp, admin);
6789
withLogin(fastifyApp, admin, auth);
68-
withLogout(fastifyApp, admin);
90+
withLogout(fastifyApp, admin, auth);
91+
withRefresh(fastifyApp, admin, auth);
6992
};

src/types.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
1+
import { BaseAuthProvider } from "adminjs";
2+
import { FastifyReply, FastifyRequest } from "fastify";
3+
14
export type AuthenticationOptions = {
25
cookiePassword: string;
36
cookieName?: string;
4-
authenticate: (email: string, password: string) => unknown | null;
7+
authenticate?: (email: string, password: string, context?: AuthenticationContext) => unknown | null;
8+
provider?: BaseAuthProvider;
9+
};
10+
11+
export type AuthenticationContext = {
12+
/**
13+
* @description Authentication request object
14+
*/
15+
request: FastifyRequest;
16+
/**
17+
* @description Authentication response object
18+
*/
19+
reply: FastifyReply;
520
};

yarn.lock

Lines changed: 12 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2817,44 +2817,6 @@
28172817
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
28182818
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
28192819

2820-
"@types/babel-core@^6.25.7":
2821-
version "6.25.7"
2822-
resolved "https://registry.yarnpkg.com/@types/babel-core/-/babel-core-6.25.7.tgz#f9c22d5c085686da2f6ffbdae778edb3e6017671"
2823-
integrity sha512-WPnyzNFVRo6bxpr7bcL27qXtNKNQ3iToziNBpibaXHyKGWQA0+tTLt73QQxC/5zzbM544ih6Ni5L5xrck6rGwg==
2824-
dependencies:
2825-
"@types/babel-generator" "*"
2826-
"@types/babel-template" "*"
2827-
"@types/babel-traverse" "*"
2828-
"@types/babel-types" "*"
2829-
"@types/babylon" "*"
2830-
2831-
"@types/babel-generator@*":
2832-
version "6.25.5"
2833-
resolved "https://registry.yarnpkg.com/@types/babel-generator/-/babel-generator-6.25.5.tgz#b02723fd589349b05524376e5530228d3675d878"
2834-
integrity sha512-lhbwMlAy5rfWG+R6l8aPtJdEFX/kcv6LMFIuvUb0i89ehqgD24je9YcB+0fRspQhgJGlEsUImxpw4pQeKS/+8Q==
2835-
dependencies:
2836-
"@types/babel-types" "*"
2837-
2838-
"@types/babel-template@*":
2839-
version "6.25.2"
2840-
resolved "https://registry.yarnpkg.com/@types/babel-template/-/babel-template-6.25.2.tgz#3c4cde02dbcbbf461a58d095a9f69f35eabd5f06"
2841-
integrity sha512-QKtDQRJmAz3Y1HSxfMl0syIHebMc/NnOeH/8qeD0zjgU2juD0uyC922biMxCy5xjTNvHinigML2l8kxE8eEBmw==
2842-
dependencies:
2843-
"@types/babel-types" "*"
2844-
"@types/babylon" "*"
2845-
2846-
"@types/babel-traverse@*":
2847-
version "6.25.7"
2848-
resolved "https://registry.yarnpkg.com/@types/babel-traverse/-/babel-traverse-6.25.7.tgz#bc75fce23d8394534562a36a32dec94a54d11835"
2849-
integrity sha512-BeQiEGLnVzypzBdsexEpZAHUx+WucOMXW6srEWDkl4SegBlaCy+iBvRO+4vz6EZ+BNQg22G4MCdDdvZxf+jW5A==
2850-
dependencies:
2851-
"@types/babel-types" "*"
2852-
2853-
"@types/babel-types@*":
2854-
version "7.0.11"
2855-
resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.11.tgz#263b113fa396fac4373188d73225297fb86f19a9"
2856-
integrity sha512-pkPtJUUY+Vwv6B1inAz55rQvivClHJxc9aVEPPmaq2cbyeMLCiDpbKpcKyX4LAwpNGi+SHBv0tHv6+0gXv0P2A==
2857-
28582820
"@types/babel__core@^7.1.14":
28592821
version "7.20.0"
28602822
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891"
@@ -2888,13 +2850,6 @@
28882850
dependencies:
28892851
"@babel/types" "^7.3.0"
28902852

2891-
"@types/babylon@*":
2892-
version "6.16.6"
2893-
resolved "https://registry.yarnpkg.com/@types/babylon/-/babylon-6.16.6.tgz#a1e7e01567b26a5ebad321a74d10299189d8d932"
2894-
integrity sha512-G4yqdVlhr6YhzLXFKy5F7HtRBU8Y23+iWy7UKthMq/OSQnL1hbsoeXESQ2LY8zEDlknipDG3nRGhUC9tkwvy/w==
2895-
dependencies:
2896-
"@types/babel-types" "*"
2897-
28982853
"@types/busboy@^1.5.0":
28992854
version "1.5.0"
29002855
resolved "https://registry.yarnpkg.com/@types/busboy/-/busboy-1.5.0.tgz#62681556cbbd2afc8d2efa6bafaa15602f0838b9"
@@ -3032,15 +2987,6 @@
30322987
"@types/scheduler" "*"
30332988
csstype "^3.0.2"
30342989

3035-
"@types/react@^18.0.28":
3036-
version "18.0.28"
3037-
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
3038-
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
3039-
dependencies:
3040-
"@types/prop-types" "*"
3041-
"@types/scheduler" "*"
3042-
csstype "^3.0.2"
3043-
30442990
"@types/resolve@1.20.2":
30452991
version "1.20.2"
30462992
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
@@ -3229,10 +3175,10 @@ acorn@^8.5.0, acorn@^8.8.0:
32293175
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
32303176
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
32313177

3232-
adminjs@^7.0.0:
3233-
version "7.0.0"
3234-
resolved "https://registry.yarnpkg.com/adminjs/-/adminjs-7.0.0.tgz#5dad16fcdd91dfe9fd84402b3e109f9fdbb74534"
3235-
integrity sha512-6cvr04yhPpoqpK9lfy5ohxHMUI+J9lDZbRScyqzmpPTZ4P8E68unZekixx7nAGXFBmhixP5+CumLNpCNzcUeGA==
3178+
adminjs@^7.4.0:
3179+
version "7.4.0"
3180+
resolved "https://registry.yarnpkg.com/adminjs/-/adminjs-7.4.0.tgz#9551c79ac1b6047f1cc86ac1525e01660fea954a"
3181+
integrity sha512-GKot4WNEe5aQN2MLkSR216N0oE9KrpJ+COwPrYhRlF42wUMiQucwQbq36VfMb/ZsiEpF3SfBdSa9Qi6EApR0WQ==
32363182
dependencies:
32373183
"@adminjs/design-system" "^4.0.0"
32383184
"@babel/core" "^7.21.0"
@@ -3251,8 +3197,6 @@ adminjs@^7.0.0:
32513197
"@rollup/plugin-node-resolve" "^15.0.1"
32523198
"@rollup/plugin-replace" "^5.0.2"
32533199
"@rollup/plugin-terser" "^0.4.0"
3254-
"@types/babel-core" "^6.25.7"
3255-
"@types/react" "^18.0.28"
32563200
axios "^1.3.4"
32573201
commander "^10.0.0"
32583202
flat "^5.0.2"
@@ -3263,6 +3207,7 @@ adminjs@^7.0.0:
32633207
ora "^6.2.0"
32643208
prop-types "^15.8.1"
32653209
punycode "^2.3.0"
3210+
qs "^6.11.1"
32663211
react "^18.2.0"
32673212
react-dom "^18.2.0"
32683213
react-feather "^2.0.10"
@@ -8462,6 +8407,13 @@ qrcode-terminal@^0.12.0:
84628407
resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
84638408
integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==
84648409

8410+
qs@^6.11.1:
8411+
version "6.11.2"
8412+
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
8413+
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
8414+
dependencies:
8415+
side-channel "^1.0.4"
8416+
84658417
queue-microtask@^1.2.2:
84668418
version "1.2.3"
84678419
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"

0 commit comments

Comments
 (0)