Skip to content

Commit 52e33d9

Browse files
committed
feat(auth): refactor to use openid-client instead of passport-openidconnect
1 parent 49a09b3 commit 52e33d9

File tree

5 files changed

+120
-142
lines changed

5 files changed

+120
-142
lines changed

package-lock.json

Lines changed: 33 additions & 78 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@
5959
"moment": "^2.29.4",
6060
"mongodb": "^5.0.0",
6161
"nodemailer": "^6.6.1",
62+
"openid-client": "^6.2.0",
6263
"parse-diff": "^0.11.1",
6364
"passport": "^0.7.0",
6465
"passport-activedirectory": "^1.0.4",
6566
"passport-local": "^1.0.0",
66-
"passport-openidconnect": "^0.1.2",
6767
"perfect-scrollbar": "^1.5.5",
6868
"prop-types": "15.8.1",
6969
"react": "^16.13.1",

src/service/passport/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ const configure = async () => {
2121
default:
2222
throw Error(`uknown authentication type ${type}`);
2323
}
24-
_passport.type = authenticationConfig.type;
24+
if (!_passport.type) {
25+
_passport.type = type;
26+
}
2527
return _passport;
2628
};
2729

src/service/passport/oidc.js

Lines changed: 83 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,103 @@
1-
const configure = async () => {
2-
const passport = require('passport');
3-
const { Strategy: OIDCStrategy } = require('passport-openidconnect');
4-
const db = require('../../db');
1+
const openIdClient = require('openid-client');
2+
const { Strategy } = require('openid-client/passport');
3+
const passport = require('passport');
4+
const db = require('../../db');
55

6+
const configure = async () => {
67
const config = require('../../config').getAuthentication();
7-
const oidcConfig = config.oidcConfig;
8+
const { oidcConfig } = config;
9+
const { issuer, clientID, clientSecret, callbackURL, scope } = oidcConfig;
10+
11+
if (!oidcConfig || !oidcConfig.issuer) {
12+
throw new Error('Missing OIDC issuer in configuration')
13+
}
14+
15+
const server = new URL(issuer);
16+
17+
try {
18+
const config = await openIdClient.discovery(server, clientID, clientSecret);
19+
20+
const strategy = new Strategy({ callbackURL, config, scope }, async (tokenSet, done) => {
21+
// Validate token sub for added security
22+
const idTokenClaims = tokenSet.claims();
23+
const expectedSub = idTokenClaims.sub;
24+
const userInfo = await openIdClient.fetchUserInfo(config, tokenSet.access_token, expectedSub);
25+
handleUserAuthentication(userInfo, done);
26+
});
27+
28+
// currentUrl must be overridden to match the callback URL
29+
strategy.currentUrl = (request) => {
30+
const callbackUrl = new URL(callbackURL);
31+
const currentUrl = Strategy.prototype.currentUrl.call(this, request);
32+
currentUrl.host = callbackUrl.host;
33+
currentUrl.protocol = callbackUrl.protocol;
34+
return currentUrl;
35+
};
836

9-
passport.use(
10-
new OIDCStrategy(oidcConfig, async function verify(issuer, profile, cb) {
37+
passport.use(strategy);
38+
39+
passport.serializeUser((user, done) => {
40+
done(null, user.oidcId || user.username);
41+
})
42+
43+
passport.deserializeUser(async (id, done) => {
1144
try {
12-
const user = await db.findUserByOIDC(profile.id);
13-
14-
if (!user) {
15-
const email = safelyExtractEmail(profile);
16-
if (!email) {
17-
return cb(new Error('No email found in OIDC profile'));
18-
}
19-
20-
const username = getUsername(email);
21-
const newUser = {
22-
username: username,
23-
email: email,
24-
oidcId: profile.id,
25-
};
26-
27-
await db.createUser(
28-
newUser.username,
29-
null,
30-
newUser.email,
31-
'Edit me',
32-
false,
33-
newUser.oidcId,
34-
);
35-
36-
return cb(null, newUser);
37-
}
38-
return cb(null, user);
45+
const user = await db.findUserByOIDC(id);
46+
done(null, user);
3947
} catch (err) {
40-
return cb(err);
48+
done(err);
4149
}
42-
}),
43-
);
44-
45-
passport.serializeUser((user, cb) => {
46-
cb(null, user.oidcId || user.username);
47-
});
48-
49-
passport.deserializeUser(async (id, cb) => {
50-
try {
51-
const user = (await db.findUserByOIDC(id)) || (await db.findUser(id));
52-
cb(null, user);
53-
} catch (err) {
54-
cb(err);
55-
}
56-
});
50+
})
51+
passport.type = server.host;
52+
53+
return passport;
54+
} catch (error) {
55+
console.error('OIDC configuration failed:', error);
56+
throw error;
57+
}
58+
}
5759

58-
passport.type = 'openidconnect';
59-
return passport;
60-
};
6160

6261
module.exports.configure = configure;
6362

63+
/**
64+
* Handles user authentication with OIDC.
65+
* @param userInfo the OIDC user info object
66+
* @param done the callback function
67+
* @returns a promise with the authenticated user or an error
68+
*/
69+
const handleUserAuthentication = async (userInfo, done) => {
70+
try {
71+
let user = await db.findUserByOIDC(userInfo.sub);
72+
73+
if (!user) {
74+
const email = safelyExtractEmail(userInfo);
75+
if (!email) return done(new Error('No email found in OIDC profile'));
76+
77+
const newUser = {
78+
username: getUsername(email),
79+
email,
80+
oidcId: userInfo.sub,
81+
};
82+
83+
await db.createUser(newUser.username, null, newUser.email, 'Edit me', false, newUser.oidcId);
84+
return done(null, newUser);
85+
}
86+
87+
return done(null, user);
88+
} catch (err) {
89+
return done(err);
90+
}
91+
};
92+
6493
/**
6594
* Extracts email from OIDC profile.
6695
* This function is necessary because OIDC providers have different ways of storing emails.
6796
* @param {object} profile the profile object from OIDC provider
6897
* @return {string | null} the email address
6998
*/
7099
const safelyExtractEmail = (profile) => {
71-
if (profile.emails && profile.emails.length > 0) {
72-
return profile.emails[0].value;
73-
}
74-
75-
if (profile.email) {
76-
return profile.email;
77-
}
78-
return null;
100+
return profile.email || (profile.emails && profile.emails.length > 0 ? profile.emails[0].value : null);
79101
};
80102

81103
/**

src/service/routes/auth.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ router.get('/oidc', passport.authenticate(passportType));
4646

4747
router.get('/oidc/callback', (req, res, next) => {
4848
passport.authenticate(passportType, (err, user, info) => {
49-
console.log('authenticate callback executed');
5049
if (err) {
5150
console.error('Authentication error:', err);
5251
return res.status(401).end();

0 commit comments

Comments
 (0)