@react-foundry/fastify-auth 0.1.9 → 0.2.0

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 CHANGED
@@ -1,11 +1,8 @@
1
- "use strict";
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 = (0, error_1.createError)('FST_BAD_HEADER', 'Malformed Authorization header', 400);
7
- const AuthFailed = (0, error_1.createError)('FST_BASIC_AUTH_FAILED', 'Incorrect or missing authentication details', 401);
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
- exports.basic = basic;
40
- exports.default = exports.basic;
36
+ export default basic;
package/dist/common.js CHANGED
@@ -1,9 +1,4 @@
1
- "use strict";
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
- exports.fromExtractor = fromExtractor;
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
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dummy = void 0;
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
- exports.dummy = dummy;
14
- exports.default = exports.dummy;
10
+ export default dummy;
package/dist/headers.js CHANGED
@@ -1,12 +1,9 @@
1
- "use strict";
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: (0, common_1.fromExtractor)((req) => {
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
- exports.headers = headers;
24
- exports.default = exports.headers;
20
+ export default headers;
package/dist/index.js CHANGED
@@ -1,57 +1,18 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
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 || (exports.AuthMethod = 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 !== fastify_session_1.SessionStore.Cookie);
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) ? (0, dummy_1.dummy)(methodOptions, fullSessions)
90
- : isHeaders(methodOptions) ? (0, headers_1.headers)(methodOptions, fullSessions)
91
- : isBasic(methodOptions) ? (0, basic_1.basic)(methodOptions, fullSessions)
92
- : isOIDC(methodOptions) ? (0, oidc_1.oidc)(methodOptions, fullSessions)
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(rate_limit_1.default, rateLimit);
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(fastify_session_1.default, session);
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
- exports.fastifyAuth = (0, fastify_plugin_1.default)(fastifyAuthPlugin, {
143
+ export const fastifyAuth = fp(fastifyAuthPlugin, {
183
144
  fastify: '5.x',
184
145
  name: 'auth',
185
146
  });
186
- exports.default = exports.fastifyAuth;
187
- var fastify_session_2 = require("@react-foundry/fastify-session");
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
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
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 = (0, error_1.createError)('FST_BAD_SESSION', 'Unable to verify session', 409);
16
- const oidc = async ({ clientId, clientSecret, issuer, redirectUri: _redirectUri }, fullSessions) => {
17
- openid_client_1.custom.setHttpOptionsDefaults({
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 openid_client_1.Issuer.discover(issuer);
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(base64url_1.default.decode(token.split('.')[1])) || {};
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(common_1.id),
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 = openid_client_1.generators.codeVerifier();
148
- const codeChallenge = openid_client_1.generators.codeChallenge(codeVerifier);
149
- const state = openid_client_1.generators.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
- exports.oidc = oidc;
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.9",
3
+ "version": "0.2.0",
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.mjs",
10
- "require": "./dist/index.js",
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.9"
28
+ "@react-foundry/fastify-session": "^0.2.0"
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": "npm run build:esm && npm run build:cjs",
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.mjs",
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
@@ -1,4 +0,0 @@
1
- export const fromExtractor = (extractor) => (req, _reply) => {
2
- req.user = extractor(req);
3
- };
4
- export const id = (x) => x;
package/dist/dummy.mjs DELETED
@@ -1,10 +0,0 @@
1
- import { fromExtractor } from './common';
2
- export const dummy = ({ username, groups = [], roles = [], }, _fullSessions) => ({
3
- authenticate: fromExtractor((_) => ({
4
- username,
5
- groups,
6
- roles
7
- })),
8
- wantSession: false
9
- });
10
- export default dummy;
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;