@react-foundry/fastify-auth 0.1.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/basic.js +5 -9
- package/dist/common.js +2 -7
- package/dist/dummy.js +4 -8
- package/dist/headers.js +4 -8
- package/dist/index.js +19 -59
- package/dist/oidc.js +14 -21
- package/package.json +7 -9
- package/dist/basic.mjs +0 -36
- package/dist/common.mjs +0 -4
- package/dist/dummy.mjs +0 -10
- package/dist/headers.mjs +0 -20
- package/dist/index.mjs +0 -148
- package/dist/oidc.mjs +0 -191
package/dist/basic.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.basic = void 0;
|
|
4
|
-
const error_1 = require("@fastify/error");
|
|
1
|
+
import { createError } from '@fastify/error';
|
|
5
2
|
const authPrefix = 'Basic ';
|
|
6
|
-
const BadHeader =
|
|
7
|
-
const AuthFailed =
|
|
8
|
-
const basic = ({ password, roles = [], username, realm = 'members', charset = 'utf-8' }, _fullSessions) => {
|
|
3
|
+
const BadHeader = createError('FST_BAD_HEADER', 'Malformed Authorization header', 400);
|
|
4
|
+
const AuthFailed = createError('FST_BASIC_AUTH_FAILED', 'Incorrect or missing authentication details', 401);
|
|
5
|
+
export const basic = ({ password, roles = [], username, realm = 'members', charset = 'utf-8' }, _fullSessions) => {
|
|
9
6
|
const base64Decode = (s) => Buffer.from(s, 'base64').toString(charset);
|
|
10
7
|
const decodeHeader = (s) => {
|
|
11
8
|
try {
|
|
@@ -36,5 +33,4 @@ const basic = ({ password, roles = [], username, realm = 'members', charset = 'u
|
|
|
36
33
|
wantSession: false
|
|
37
34
|
};
|
|
38
35
|
};
|
|
39
|
-
|
|
40
|
-
exports.default = exports.basic;
|
|
36
|
+
export default basic;
|
package/dist/common.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.id = exports.fromExtractor = void 0;
|
|
4
|
-
const fromExtractor = (extractor) => (req, _reply) => {
|
|
1
|
+
export const fromExtractor = (extractor) => (req, _reply) => {
|
|
5
2
|
req.user = extractor(req);
|
|
6
3
|
};
|
|
7
|
-
|
|
8
|
-
const id = (x) => x;
|
|
9
|
-
exports.id = id;
|
|
4
|
+
export const id = (x) => x;
|
package/dist/dummy.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const common_1 = require("./common");
|
|
5
|
-
const dummy = ({ username, groups = [], roles = [], }, _fullSessions) => ({
|
|
6
|
-
authenticate: (0, common_1.fromExtractor)((_) => ({
|
|
1
|
+
import { fromExtractor } from './common';
|
|
2
|
+
export const dummy = ({ username, groups = [], roles = [], }, _fullSessions) => ({
|
|
3
|
+
authenticate: fromExtractor((_) => ({
|
|
7
4
|
username,
|
|
8
5
|
groups,
|
|
9
6
|
roles
|
|
10
7
|
})),
|
|
11
8
|
wantSession: false
|
|
12
9
|
});
|
|
13
|
-
|
|
14
|
-
exports.default = exports.dummy;
|
|
10
|
+
export default dummy;
|
package/dist/headers.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.headers = void 0;
|
|
4
|
-
const common_1 = require("./common");
|
|
1
|
+
import { fromExtractor } from './common';
|
|
5
2
|
const valueFromHeader = (header) => (Array.isArray(header)
|
|
6
3
|
? header[0]
|
|
7
4
|
: header);
|
|
8
|
-
const headers = ({ groupsHeader = 'x-auth-groups', rolesHeader = 'x-auth-roles', usernameHeader = 'x-auth-username' }, _fullSessions) => ({
|
|
9
|
-
authenticate:
|
|
5
|
+
export const headers = ({ groupsHeader = 'x-auth-groups', rolesHeader = 'x-auth-roles', usernameHeader = 'x-auth-username' }, _fullSessions) => ({
|
|
6
|
+
authenticate: fromExtractor((req) => {
|
|
10
7
|
const username = valueFromHeader(req.headers[usernameHeader]);
|
|
11
8
|
const groups = valueFromHeader(req.headers[groupsHeader]);
|
|
12
9
|
const roles = valueFromHeader(req.headers[rolesHeader]);
|
|
@@ -20,5 +17,4 @@ const headers = ({ groupsHeader = 'x-auth-groups', rolesHeader = 'x-auth-roles',
|
|
|
20
17
|
}),
|
|
21
18
|
wantSession: false
|
|
22
19
|
});
|
|
23
|
-
|
|
24
|
-
exports.default = exports.headers;
|
|
20
|
+
export default headers;
|
package/dist/index.js
CHANGED
|
@@ -1,57 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.SessionStore = exports.fastifyAuth = exports.AuthMethod = void 0;
|
|
40
|
-
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
41
|
-
const rate_limit_1 = __importDefault(require("@fastify/rate-limit"));
|
|
42
|
-
const fastify_session_1 = __importStar(require("@react-foundry/fastify-session"));
|
|
43
|
-
const basic_1 = require("./basic");
|
|
44
|
-
const dummy_1 = require("./dummy");
|
|
45
|
-
const headers_1 = require("./headers");
|
|
46
|
-
const oidc_1 = require("./oidc");
|
|
47
|
-
var AuthMethod;
|
|
1
|
+
import fp from 'fastify-plugin';
|
|
2
|
+
import fastifyRateLimit from '@fastify/rate-limit';
|
|
3
|
+
import fastifySession, { SessionStore } from '@react-foundry/fastify-session';
|
|
4
|
+
import { basic } from './basic';
|
|
5
|
+
import { dummy } from './dummy';
|
|
6
|
+
import { headers } from './headers';
|
|
7
|
+
import { oidc } from './oidc';
|
|
8
|
+
export var AuthMethod;
|
|
48
9
|
(function (AuthMethod) {
|
|
49
10
|
AuthMethod["None"] = "none";
|
|
50
11
|
AuthMethod["Dummy"] = "dummy";
|
|
51
12
|
AuthMethod["Headers"] = "headers";
|
|
52
13
|
AuthMethod["Basic"] = "basic";
|
|
53
14
|
AuthMethod["OIDC"] = "oidc";
|
|
54
|
-
})(AuthMethod || (
|
|
15
|
+
})(AuthMethod || (AuthMethod = {}));
|
|
55
16
|
;
|
|
56
17
|
const isNone = (v) => v.method === AuthMethod.None || v.method === undefined;
|
|
57
18
|
const isDummy = (v) => v.method === AuthMethod.Dummy;
|
|
@@ -65,7 +26,7 @@ const fastifyAuthPlugin = async (fastify, { privacy = true, pathPrefix = '/auth/
|
|
|
65
26
|
max: 100,
|
|
66
27
|
timeWindow: 15 * 60000
|
|
67
28
|
}, ...methodOptions }) => {
|
|
68
|
-
const fullSessions = !!(session.store && session.store !==
|
|
29
|
+
const fullSessions = !!(session.store && session.store !== SessionStore.Cookie);
|
|
69
30
|
const serDes = (user, _req) => user;
|
|
70
31
|
const redact = (user, _req) => ({
|
|
71
32
|
username: user.username,
|
|
@@ -86,10 +47,10 @@ const fastifyAuthPlugin = async (fastify, { privacy = true, pathPrefix = '/auth/
|
|
|
86
47
|
rateLimit: authRateLimit
|
|
87
48
|
}
|
|
88
49
|
};
|
|
89
|
-
const { authenticate, callback, deserialize = serDes, serialize = _serialize, terminate, wantSession } = await (isDummy(methodOptions) ?
|
|
90
|
-
: isHeaders(methodOptions) ?
|
|
91
|
-
: isBasic(methodOptions) ?
|
|
92
|
-
: isOIDC(methodOptions) ?
|
|
50
|
+
const { authenticate, callback, deserialize = serDes, serialize = _serialize, terminate, wantSession } = await (isDummy(methodOptions) ? dummy(methodOptions, fullSessions)
|
|
51
|
+
: isHeaders(methodOptions) ? headers(methodOptions, fullSessions)
|
|
52
|
+
: isBasic(methodOptions) ? basic(methodOptions, fullSessions)
|
|
53
|
+
: isOIDC(methodOptions) ? oidc(methodOptions, fullSessions)
|
|
93
54
|
: {});
|
|
94
55
|
const useSession = wantSession || !privacy;
|
|
95
56
|
const whitelist = (callback
|
|
@@ -97,7 +58,7 @@ const fastifyAuthPlugin = async (fastify, { privacy = true, pathPrefix = '/auth/
|
|
|
97
58
|
: [pathPrefix + signOutPath]);
|
|
98
59
|
fastify.decorateRequest('user', null);
|
|
99
60
|
if (!fastify.hasDecorator('rateLimit') && (authenticate || callback)) {
|
|
100
|
-
fastify.register(
|
|
61
|
+
fastify.register(fastifyRateLimit, rateLimit);
|
|
101
62
|
}
|
|
102
63
|
if (authenticate) {
|
|
103
64
|
if (useSession) {
|
|
@@ -117,7 +78,7 @@ const fastifyAuthPlugin = async (fastify, { privacy = true, pathPrefix = '/auth/
|
|
|
117
78
|
if (!session.store) {
|
|
118
79
|
fastify.log.info('Session required for authentication; registering plugin...');
|
|
119
80
|
}
|
|
120
|
-
fastify.register(
|
|
81
|
+
fastify.register(fastifySession, session);
|
|
121
82
|
}
|
|
122
83
|
if (authenticate) {
|
|
123
84
|
if (useSession) {
|
|
@@ -179,10 +140,9 @@ const fastifyAuthPlugin = async (fastify, { privacy = true, pathPrefix = '/auth/
|
|
|
179
140
|
});
|
|
180
141
|
}
|
|
181
142
|
};
|
|
182
|
-
|
|
143
|
+
export const fastifyAuth = fp(fastifyAuthPlugin, {
|
|
183
144
|
fastify: '5.x',
|
|
184
145
|
name: 'auth',
|
|
185
146
|
});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Object.defineProperty(exports, "SessionStore", { enumerable: true, get: function () { return fastify_session_2.SessionStore; } });
|
|
147
|
+
export default fastifyAuth;
|
|
148
|
+
export { SessionStore } from '@react-foundry/fastify-session';
|
package/dist/oidc.js
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.oidc = void 0;
|
|
7
|
-
const base64url_1 = __importDefault(require("base64url"));
|
|
8
|
-
const openid_client_1 = require("openid-client");
|
|
9
|
-
const error_1 = require("@fastify/error");
|
|
10
|
-
const common_1 = require("./common");
|
|
1
|
+
import base64url from 'base64url';
|
|
2
|
+
import { Issuer, custom, generators } from 'openid-client';
|
|
3
|
+
import { createError } from '@fastify/error';
|
|
4
|
+
import { id } from './common';
|
|
11
5
|
const resourceToRoles = (acc, [x, y]) => ([
|
|
12
6
|
...acc,
|
|
13
7
|
...(y.roles?.map((e) => `${x}:${e}`) || [])
|
|
14
8
|
]);
|
|
15
|
-
const BadSession =
|
|
16
|
-
const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri }, fullSessions) => {
|
|
17
|
-
|
|
9
|
+
const BadSession = createError('FST_BAD_SESSION', 'Unable to verify session', 409);
|
|
10
|
+
export const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri }, fullSessions) => {
|
|
11
|
+
custom.setHttpOptionsDefaults({
|
|
18
12
|
timeout: 5000,
|
|
19
13
|
});
|
|
20
14
|
const redirectUri = _redirectUri + '/auth/callback';
|
|
21
|
-
const iss = await
|
|
15
|
+
const iss = await Issuer.discover(issuer);
|
|
22
16
|
const client = new iss.Client({
|
|
23
17
|
client_id: clientId,
|
|
24
18
|
client_secret: clientSecret,
|
|
@@ -28,7 +22,7 @@ const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri
|
|
|
28
22
|
: 'none')
|
|
29
23
|
});
|
|
30
24
|
const authInfo = (accessToken, refreshToken, idToken, userinfo = {}) => {
|
|
31
|
-
const extractJWTClaims = (token) => token && JSON.parse(
|
|
25
|
+
const extractJWTClaims = (token) => token && JSON.parse(base64url.decode(token.split('.')[1])) || {};
|
|
32
26
|
const accessClaims = extractJWTClaims(accessToken);
|
|
33
27
|
const idClaims = extractJWTClaims(idToken);
|
|
34
28
|
const refreshClaims = extractJWTClaims(refreshToken);
|
|
@@ -68,7 +62,7 @@ const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri
|
|
|
68
62
|
...(data.roles || []),
|
|
69
63
|
...(data.realm_access?.roles || []),
|
|
70
64
|
...(Object.entries(data.resource_access || {}).reduce(resourceToRoles, []))
|
|
71
|
-
].filter(
|
|
65
|
+
].filter(id),
|
|
72
66
|
accessToken: data.accessToken,
|
|
73
67
|
accessTokenValid,
|
|
74
68
|
refreshToken: data.refreshToken,
|
|
@@ -144,9 +138,9 @@ const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri
|
|
|
144
138
|
}
|
|
145
139
|
};
|
|
146
140
|
const authenticate = async (req, reply) => {
|
|
147
|
-
const codeVerifier =
|
|
148
|
-
const codeChallenge =
|
|
149
|
-
const state =
|
|
141
|
+
const codeVerifier = generators.codeVerifier();
|
|
142
|
+
const codeChallenge = generators.codeChallenge(codeVerifier);
|
|
143
|
+
const state = generators.state();
|
|
150
144
|
const redirectTo = client.authorizationUrl({
|
|
151
145
|
scope: 'openid',
|
|
152
146
|
state,
|
|
@@ -194,5 +188,4 @@ const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri
|
|
|
194
188
|
wantSession: true
|
|
195
189
|
};
|
|
196
190
|
};
|
|
197
|
-
|
|
198
|
-
exports.default = exports.oidc;
|
|
191
|
+
export default oidc;
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-foundry/fastify-auth",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Authentication plugin for Fastify.",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
8
9
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"import": "./dist/index.
|
|
10
|
-
"
|
|
11
|
-
"default": "./dist/index.mjs"
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"base64url": "^3.0.1",
|
|
26
26
|
"fastify-plugin": "^5.1.0",
|
|
27
27
|
"openid-client": "^5.7.1",
|
|
28
|
-
"@react-foundry/fastify-session": "^0.1
|
|
28
|
+
"@react-foundry/fastify-session": "^0.2.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"fastify": "5.8.4",
|
|
@@ -36,11 +36,9 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
39
|
-
"build": "
|
|
40
|
-
"build:esm": "tsc -m es2022 && find dist -name '*.js' -exec sh -c 'mv \"$0\" \"${0%.js}.mjs\"' {} \\;",
|
|
41
|
-
"build:cjs": "tsc",
|
|
39
|
+
"build": "tsc",
|
|
42
40
|
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
43
41
|
},
|
|
44
|
-
"module": "dist/index.
|
|
42
|
+
"module": "dist/index.js",
|
|
45
43
|
"typings": "dist/index.d.ts"
|
|
46
44
|
}
|
package/dist/basic.mjs
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createError } from '@fastify/error';
|
|
2
|
-
const authPrefix = 'Basic ';
|
|
3
|
-
const BadHeader = createError('FST_BAD_HEADER', 'Malformed Authorization header', 400);
|
|
4
|
-
const AuthFailed = createError('FST_BASIC_AUTH_FAILED', 'Incorrect or missing authentication details', 401);
|
|
5
|
-
export const basic = ({ password, roles = [], username, realm = 'members', charset = 'utf-8' }, _fullSessions) => {
|
|
6
|
-
const base64Decode = (s) => Buffer.from(s, 'base64').toString(charset);
|
|
7
|
-
const decodeHeader = (s) => {
|
|
8
|
-
try {
|
|
9
|
-
return base64Decode(s.substring(authPrefix.length)).split(':');
|
|
10
|
-
}
|
|
11
|
-
catch (_err) {
|
|
12
|
-
throw new BadHeader();
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
return {
|
|
16
|
-
authenticate: (req, reply) => {
|
|
17
|
-
const authHeader = req.headers['authorization'] || '';
|
|
18
|
-
const [suppliedUsername, suppliedPassword] = (authHeader.startsWith(authPrefix)
|
|
19
|
-
? decodeHeader(authHeader)
|
|
20
|
-
: []);
|
|
21
|
-
if (suppliedUsername === username && suppliedPassword === password) {
|
|
22
|
-
const user = {
|
|
23
|
-
username,
|
|
24
|
-
roles
|
|
25
|
-
};
|
|
26
|
-
req.user = user;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
reply.header('WWW-Authenticate', `Basic realm="${realm}", charset="${charset}"`);
|
|
30
|
-
throw new AuthFailed();
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
wantSession: false
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
export default basic;
|
package/dist/common.mjs
DELETED
package/dist/dummy.mjs
DELETED
package/dist/headers.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { fromExtractor } from './common';
|
|
2
|
-
const valueFromHeader = (header) => (Array.isArray(header)
|
|
3
|
-
? header[0]
|
|
4
|
-
: header);
|
|
5
|
-
export const headers = ({ groupsHeader = 'x-auth-groups', rolesHeader = 'x-auth-roles', usernameHeader = 'x-auth-username' }, _fullSessions) => ({
|
|
6
|
-
authenticate: fromExtractor((req) => {
|
|
7
|
-
const username = valueFromHeader(req.headers[usernameHeader]);
|
|
8
|
-
const groups = valueFromHeader(req.headers[groupsHeader]);
|
|
9
|
-
const roles = valueFromHeader(req.headers[rolesHeader]);
|
|
10
|
-
return (username && roles
|
|
11
|
-
? {
|
|
12
|
-
username: username,
|
|
13
|
-
groups: groups?.split(',') || [],
|
|
14
|
-
roles: roles?.split(',') || []
|
|
15
|
-
}
|
|
16
|
-
: undefined);
|
|
17
|
-
}),
|
|
18
|
-
wantSession: false
|
|
19
|
-
});
|
|
20
|
-
export default headers;
|
package/dist/index.mjs
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import fp from 'fastify-plugin';
|
|
2
|
-
import fastifyRateLimit from '@fastify/rate-limit';
|
|
3
|
-
import fastifySession, { SessionStore } from '@react-foundry/fastify-session';
|
|
4
|
-
import { basic } from './basic';
|
|
5
|
-
import { dummy } from './dummy';
|
|
6
|
-
import { headers } from './headers';
|
|
7
|
-
import { oidc } from './oidc';
|
|
8
|
-
export var AuthMethod;
|
|
9
|
-
(function (AuthMethod) {
|
|
10
|
-
AuthMethod["None"] = "none";
|
|
11
|
-
AuthMethod["Dummy"] = "dummy";
|
|
12
|
-
AuthMethod["Headers"] = "headers";
|
|
13
|
-
AuthMethod["Basic"] = "basic";
|
|
14
|
-
AuthMethod["OIDC"] = "oidc";
|
|
15
|
-
})(AuthMethod || (AuthMethod = {}));
|
|
16
|
-
;
|
|
17
|
-
const isNone = (v) => v.method === AuthMethod.None || v.method === undefined;
|
|
18
|
-
const isDummy = (v) => v.method === AuthMethod.Dummy;
|
|
19
|
-
const isHeaders = (v) => v.method === AuthMethod.Headers;
|
|
20
|
-
const isBasic = (v) => v.method === AuthMethod.Basic;
|
|
21
|
-
const isOIDC = (v) => v.method === AuthMethod.OIDC;
|
|
22
|
-
const fastifyAuthPlugin = async (fastify, { privacy = true, pathPrefix = '/auth/', session = {}, signInPath = 'sign-in', signOutPath = 'sign-out', callbackPath = 'callback', redirectPath = '/', rateLimit: _rateLimit = {
|
|
23
|
-
max: 60,
|
|
24
|
-
timeWindow: 60000,
|
|
25
|
-
}, authRateLimit = {
|
|
26
|
-
max: 100,
|
|
27
|
-
timeWindow: 15 * 60000
|
|
28
|
-
}, ...methodOptions }) => {
|
|
29
|
-
const fullSessions = !!(session.store && session.store !== SessionStore.Cookie);
|
|
30
|
-
const serDes = (user, _req) => user;
|
|
31
|
-
const redact = (user, _req) => ({
|
|
32
|
-
username: user.username,
|
|
33
|
-
roles: user.roles
|
|
34
|
-
});
|
|
35
|
-
const _serialize = (fullSessions
|
|
36
|
-
? serDes
|
|
37
|
-
: redact);
|
|
38
|
-
const redirect = async (_req, reply) => {
|
|
39
|
-
return reply.redirect(redirectPath, 302);
|
|
40
|
-
};
|
|
41
|
-
const rateLimit = _rateLimit && {
|
|
42
|
-
..._rateLimit,
|
|
43
|
-
global: privacy
|
|
44
|
-
};
|
|
45
|
-
const authConfig = {
|
|
46
|
-
config: {
|
|
47
|
-
rateLimit: authRateLimit
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const { authenticate, callback, deserialize = serDes, serialize = _serialize, terminate, wantSession } = await (isDummy(methodOptions) ? dummy(methodOptions, fullSessions)
|
|
51
|
-
: isHeaders(methodOptions) ? headers(methodOptions, fullSessions)
|
|
52
|
-
: isBasic(methodOptions) ? basic(methodOptions, fullSessions)
|
|
53
|
-
: isOIDC(methodOptions) ? oidc(methodOptions, fullSessions)
|
|
54
|
-
: {});
|
|
55
|
-
const useSession = wantSession || !privacy;
|
|
56
|
-
const whitelist = (callback
|
|
57
|
-
? [pathPrefix + callbackPath, pathPrefix + signOutPath]
|
|
58
|
-
: [pathPrefix + signOutPath]);
|
|
59
|
-
fastify.decorateRequest('user', null);
|
|
60
|
-
if (!fastify.hasDecorator('rateLimit') && (authenticate || callback)) {
|
|
61
|
-
fastify.register(fastifyRateLimit, rateLimit);
|
|
62
|
-
}
|
|
63
|
-
if (authenticate) {
|
|
64
|
-
if (useSession) {
|
|
65
|
-
fastify.addHook('onSend', async (req, _reply, _payload) => {
|
|
66
|
-
if (req.user) {
|
|
67
|
-
if (req.session) {
|
|
68
|
-
req.session.user = await serialize(req.user, req);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
req.log.error('Unable to store session');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (useSession || session.store) {
|
|
78
|
-
if (!session.store) {
|
|
79
|
-
fastify.log.info('Session required for authentication; registering plugin...');
|
|
80
|
-
}
|
|
81
|
-
fastify.register(fastifySession, session);
|
|
82
|
-
}
|
|
83
|
-
if (authenticate) {
|
|
84
|
-
if (useSession) {
|
|
85
|
-
fastify.addHook('preHandler', async (req, _reply) => {
|
|
86
|
-
if (req.session?.user) {
|
|
87
|
-
req.user = await deserialize(req.session.user, req);
|
|
88
|
-
if (req.user) {
|
|
89
|
-
req.log.debug(`User, '${req.user.username}', authenticated from session`);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
req.log.info('Failed to authenticate from session; ending session...');
|
|
93
|
-
delete req.session.user;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (privacy) {
|
|
99
|
-
fastify.addHook('preHandler', async (req, reply) => {
|
|
100
|
-
if (!req.user && !whitelist.includes(req.url)) {
|
|
101
|
-
const r = await authenticate(req, reply);
|
|
102
|
-
if (!callback) {
|
|
103
|
-
const username = req.user?.username;
|
|
104
|
-
req.log.debug(`User, '${username}', authenticated`);
|
|
105
|
-
}
|
|
106
|
-
return r;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
fastify.get(pathPrefix + signInPath, authConfig, async (req, reply) => {
|
|
112
|
-
const r = await authenticate(req, reply);
|
|
113
|
-
if (!callback) {
|
|
114
|
-
req.log.debug(`User, '${req.user?.username}', authenticated`);
|
|
115
|
-
}
|
|
116
|
-
return (reply.sent
|
|
117
|
-
? r
|
|
118
|
-
: redirect(req, reply));
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
if (callback) {
|
|
122
|
-
fastify.get(pathPrefix + callbackPath, authConfig, async (req, reply) => {
|
|
123
|
-
const r = await callback(req, reply);
|
|
124
|
-
req.log.debug(`User, '${req.user?.username}', authenticated`);
|
|
125
|
-
return (reply.sent
|
|
126
|
-
? r
|
|
127
|
-
: redirect(req, reply));
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
fastify.get(pathPrefix + signOutPath, async (req, reply) => {
|
|
131
|
-
if (useSession && req.session?.user) {
|
|
132
|
-
delete req.user;
|
|
133
|
-
delete req.session.user;
|
|
134
|
-
}
|
|
135
|
-
const r = await terminate?.(req, reply);
|
|
136
|
-
req.log.debug('User logged out');
|
|
137
|
-
return (reply.sent
|
|
138
|
-
? r
|
|
139
|
-
: redirect(req, reply));
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
export const fastifyAuth = fp(fastifyAuthPlugin, {
|
|
144
|
-
fastify: '5.x',
|
|
145
|
-
name: 'auth',
|
|
146
|
-
});
|
|
147
|
-
export default fastifyAuth;
|
|
148
|
-
export { SessionStore } from '@react-foundry/fastify-session';
|
package/dist/oidc.mjs
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import base64url from 'base64url';
|
|
2
|
-
import { Issuer, custom, generators } from 'openid-client';
|
|
3
|
-
import { createError } from '@fastify/error';
|
|
4
|
-
import { id } from './common';
|
|
5
|
-
const resourceToRoles = (acc, [x, y]) => ([
|
|
6
|
-
...acc,
|
|
7
|
-
...(y.roles?.map((e) => `${x}:${e}`) || [])
|
|
8
|
-
]);
|
|
9
|
-
const BadSession = createError('FST_BAD_SESSION', 'Unable to verify session', 409);
|
|
10
|
-
export const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri }, fullSessions) => {
|
|
11
|
-
custom.setHttpOptionsDefaults({
|
|
12
|
-
timeout: 5000,
|
|
13
|
-
});
|
|
14
|
-
const redirectUri = _redirectUri + '/auth/callback';
|
|
15
|
-
const iss = await Issuer.discover(issuer);
|
|
16
|
-
const client = new iss.Client({
|
|
17
|
-
client_id: clientId,
|
|
18
|
-
client_secret: clientSecret,
|
|
19
|
-
redirect_uris: [redirectUri],
|
|
20
|
-
token_endpoint_auth_method: (clientSecret
|
|
21
|
-
? 'client_secret_basic'
|
|
22
|
-
: 'none')
|
|
23
|
-
});
|
|
24
|
-
const authInfo = (accessToken, refreshToken, idToken, userinfo = {}) => {
|
|
25
|
-
const extractJWTClaims = (token) => token && JSON.parse(base64url.decode(token.split('.')[1])) || {};
|
|
26
|
-
const accessClaims = extractJWTClaims(accessToken);
|
|
27
|
-
const idClaims = extractJWTClaims(idToken);
|
|
28
|
-
const refreshClaims = extractJWTClaims(refreshToken);
|
|
29
|
-
const data = {
|
|
30
|
-
...accessClaims,
|
|
31
|
-
...idClaims,
|
|
32
|
-
...userinfo,
|
|
33
|
-
accessToken,
|
|
34
|
-
refreshToken,
|
|
35
|
-
idToken,
|
|
36
|
-
userinfo
|
|
37
|
-
};
|
|
38
|
-
const expiry = new Date((refreshClaims.exp || accessClaims.exp) * 1000);
|
|
39
|
-
const now = Math.floor(Date.now() / 1000);
|
|
40
|
-
const isValid = ({ nbf = 0, exp = 0 }) => ((nbf <= now) && (now < exp));
|
|
41
|
-
const accessTokenValid = isValid(accessClaims);
|
|
42
|
-
const refreshTokenValid = isValid(refreshClaims);
|
|
43
|
-
const idTokenValid = isValid(idClaims);
|
|
44
|
-
return {
|
|
45
|
-
provider: 'oidc',
|
|
46
|
-
id: data.sub,
|
|
47
|
-
displayName: data.displayName || data.name,
|
|
48
|
-
name: {
|
|
49
|
-
familyName: data.familyName || data.family_name,
|
|
50
|
-
givenName: data.givenName || data.given_name,
|
|
51
|
-
middleName: data.middleName || data.middle_name
|
|
52
|
-
},
|
|
53
|
-
emails: (data.email
|
|
54
|
-
? [{ value: data.email }]
|
|
55
|
-
: undefined),
|
|
56
|
-
photos: (data.photo
|
|
57
|
-
? [{ value: data.photo }]
|
|
58
|
-
: undefined),
|
|
59
|
-
username: data.username || data.preferred_username,
|
|
60
|
-
groups: data.groups,
|
|
61
|
-
roles: [
|
|
62
|
-
...(data.roles || []),
|
|
63
|
-
...(data.realm_access?.roles || []),
|
|
64
|
-
...(Object.entries(data.resource_access || {}).reduce(resourceToRoles, []))
|
|
65
|
-
].filter(id),
|
|
66
|
-
accessToken: data.accessToken,
|
|
67
|
-
accessTokenValid,
|
|
68
|
-
refreshToken: data.refreshToken,
|
|
69
|
-
refreshTokenValid,
|
|
70
|
-
idToken: data.idToken,
|
|
71
|
-
idTokenValid,
|
|
72
|
-
userinfo: data.userinfo,
|
|
73
|
-
expiry
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
const serialize = (user, req) => {
|
|
77
|
-
if (fullSessions) {
|
|
78
|
-
return user;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
const cookieLimit = 4096;
|
|
82
|
-
const encryptionCost = 1.5;
|
|
83
|
-
const smallEnough = (v) => (JSON.stringify(v).length * encryptionCost <= cookieLimit);
|
|
84
|
-
const payload = {
|
|
85
|
-
accessToken: user.accessToken,
|
|
86
|
-
refreshToken: user.refreshToken,
|
|
87
|
-
idToken: user.idToken,
|
|
88
|
-
userinfo: user.userinfo
|
|
89
|
-
};
|
|
90
|
-
if (smallEnough(payload)) {
|
|
91
|
-
return payload;
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
delete payload.userinfo;
|
|
95
|
-
if (smallEnough(payload)) {
|
|
96
|
-
return payload;
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
delete payload.idToken;
|
|
100
|
-
if (smallEnough(payload)) {
|
|
101
|
-
return payload;
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
delete payload.refreshToken;
|
|
105
|
-
req.log.warn('Cannot fit refresh token in session; session will expire early');
|
|
106
|
-
return payload;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
const deserialize = async ({ accessToken, refreshToken, idToken, userinfo }, req) => {
|
|
113
|
-
const user = authInfo(accessToken, refreshToken, idToken, userinfo || {});
|
|
114
|
-
if (user.username && user.accessTokenValid) {
|
|
115
|
-
return user;
|
|
116
|
-
}
|
|
117
|
-
else if (!user.accessTokenValid && refreshToken && user.refreshTokenValid) {
|
|
118
|
-
try {
|
|
119
|
-
const tokenSet = await client.refresh(refreshToken);
|
|
120
|
-
req.log.info('Obtained new access token');
|
|
121
|
-
const newUser = authInfo(tokenSet.access_token, tokenSet.refresh_token, tokenSet.id_token, userinfo || {});
|
|
122
|
-
if (newUser.username && newUser.accessTokenValid) {
|
|
123
|
-
return newUser;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
req.log.error('Access token was invalid');
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch (_err) {
|
|
131
|
-
req.log.error('Failed to obtain new access token');
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
req.log.info('Access token has expired and cannot renew');
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
const authenticate = async (req, reply) => {
|
|
141
|
-
const codeVerifier = generators.codeVerifier();
|
|
142
|
-
const codeChallenge = generators.codeChallenge(codeVerifier);
|
|
143
|
-
const state = generators.state();
|
|
144
|
-
const redirectTo = client.authorizationUrl({
|
|
145
|
-
scope: 'openid',
|
|
146
|
-
state,
|
|
147
|
-
code_challenge: codeChallenge,
|
|
148
|
-
code_challenge_method: 'S256'
|
|
149
|
-
});
|
|
150
|
-
const sessionObj = {
|
|
151
|
-
codeVerifier,
|
|
152
|
-
state
|
|
153
|
-
};
|
|
154
|
-
req.session.oidc = sessionObj;
|
|
155
|
-
return reply.redirect(redirectTo);
|
|
156
|
-
};
|
|
157
|
-
const callback = async (req, reply) => {
|
|
158
|
-
const sessionObj = (req.session.oidc || {});
|
|
159
|
-
delete req.session.oidc;
|
|
160
|
-
const { codeVerifier, state } = sessionObj;
|
|
161
|
-
if (!(codeVerifier && state)) {
|
|
162
|
-
throw new BadSession();
|
|
163
|
-
}
|
|
164
|
-
const checks = {
|
|
165
|
-
code_verifier: codeVerifier,
|
|
166
|
-
state
|
|
167
|
-
};
|
|
168
|
-
const params = client.callbackParams(req.raw);
|
|
169
|
-
const tokenSet = await client.callback(redirectUri, params, checks);
|
|
170
|
-
const userinfo = await client.userinfo(tokenSet);
|
|
171
|
-
const user = authInfo(tokenSet.access_token, tokenSet.refresh_token, tokenSet.id_token, userinfo);
|
|
172
|
-
if (user.username) {
|
|
173
|
-
req.user = user;
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
const terminate = async (req, reply) => {
|
|
177
|
-
const redirectTo = client.endSessionUrl({
|
|
178
|
-
post_logout_redirect_uri: _redirectUri
|
|
179
|
-
});
|
|
180
|
-
return reply.redirect(redirectTo);
|
|
181
|
-
};
|
|
182
|
-
return {
|
|
183
|
-
authenticate,
|
|
184
|
-
callback,
|
|
185
|
-
deserialize,
|
|
186
|
-
serialize,
|
|
187
|
-
terminate,
|
|
188
|
-
wantSession: true
|
|
189
|
-
};
|
|
190
|
-
};
|
|
191
|
-
export default oidc;
|