Skip to content

Commit dc69622

Browse files
authored
Merge pull request #963 from jescalada/enable-multiple-auth-methods
feat: enable multiple auth methods
2 parents 4e77a0f + ddc20bf commit dc69622

File tree

17 files changed

+346
-146
lines changed

17 files changed

+346
-146
lines changed

cypress/e2e/login.cy.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ describe('Login page', () => {
3838

3939
// Validates that OIDC is configured correctly
4040
it('should redirect to /oidc', () => {
41+
// Set intercept first, since redirect on click can be quick
42+
cy.intercept('GET', '/api/auth/oidc').as('oidcRedirect');
4143
cy.get('[data-test="oidc-login"]').click();
42-
cy.url().should('include', '/oidc');
44+
cy.wait('@oidcRedirect');
4345
});
4446
});
4547
});

package-lock.json

Lines changed: 73 additions & 4 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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"mocha": "^10.8.2",
110110
"nyc": "^17.0.0",
111111
"prettier": "^3.0.0",
112+
"proxyquire": "^2.1.3",
112113
"sinon": "^19.0.2",
113114
"ts-mocha": "^11.1.0",
114115
"ts-node": "^10.9.2",

proxy.config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@
5353
"baseDN": "",
5454
"searchBase": ""
5555
}
56+
},
57+
{
58+
"type": "openidconnect",
59+
"enabled": false,
60+
"oidcConfig": {
61+
"issuer": "",
62+
"clientID": "",
63+
"clientSecret": "",
64+
"callbackURL": "",
65+
"scope": ""
66+
}
5667
}
5768
],
5869
"api": {

src/config/index.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,27 +89,29 @@ export const getDatabase = () => {
8989
throw Error('No database cofigured!');
9090
};
9191

92-
// Gets the configured authentication method, defaults to local
93-
export const getAuthentication = () => {
92+
/**
93+
* Get the list of enabled authentication methods
94+
* @return {Array} List of enabled authentication methods
95+
*/
96+
export const getAuthMethods = () => {
9497
if (_userSettings !== null && _userSettings.authentication) {
9598
_authentication = _userSettings.authentication;
9699
}
97-
for (const ix in _authentication) {
98-
if (!ix) continue;
99-
const auth = _authentication[ix];
100-
if (auth.enabled) {
101-
return auth;
102-
}
100+
101+
const enabledAuthMethods = _authentication.filter((auth) => auth.enabled);
102+
103+
if (enabledAuthMethods.length === 0) {
104+
throw new Error("No authentication method enabled");
103105
}
104106

105-
throw Error('No authentication cofigured!');
107+
return enabledAuthMethods;
106108
};
107109

108110
// Log configuration to console
109111
export const logConfiguration = () => {
110112
console.log(`authorisedList = ${JSON.stringify(getAuthorisedList())}`);
111113
console.log(`data sink = ${JSON.stringify(getDatabase())}`);
112-
console.log(`authentication = ${JSON.stringify(getAuthentication())}`);
114+
console.log(`authentication = ${JSON.stringify(getAuthMethods())}`);
113115
console.log(`rateLimit = ${JSON.stringify(getRateLimit())}`);
114116
};
115117

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
const configure = () => {
2-
const passport = require('passport');
3-
const ActiveDirectoryStrategy = require('passport-activedirectory');
4-
const config = require('../../config').getAuthentication();
5-
const adConfig = config.adConfig;
1+
const ActiveDirectoryStrategy = require('passport-activedirectory');
2+
const ldaphelper = require('./ldaphelper');
3+
4+
const configure = (passport) => {
65
const db = require('../../db');
7-
const userGroup = config.userGroup;
8-
const adminGroup = config.adminGroup;
9-
const domain = config.domain;
6+
7+
// We can refactor this by normalizing auth strategy config and pass it directly into the configure() function,
8+
// ideally when we convert this to TS.
9+
const authMethods = require('../../config').getAuthMethods();
10+
const config = authMethods.find((method) => method.type.toLowerCase() === "activeDirectory");
11+
const adConfig = config.adConfig;
12+
13+
const { userGroup, adminGroup, domain } = config;
1014

1115
console.log(`AD User Group: ${userGroup}, AD Admin Group: ${adminGroup}`);
1216

13-
const ldaphelper = require('./ldaphelper');
1417
passport.use(
1518
new ActiveDirectoryStrategy(
1619
{
@@ -19,42 +22,47 @@ const configure = () => {
1922
ldap: adConfig,
2023
},
2124
async function (req, profile, ad, done) {
22-
profile.username = profile._json.sAMAccountName.toLowerCase();
23-
profile.email = profile._json.mail;
24-
profile.id = profile.username;
25-
req.user = profile;
26-
27-
console.log(
28-
`passport.activeDirectory: resolved login ${
29-
profile._json.userPrincipalName
30-
}, profile=${JSON.stringify(profile)}`,
31-
);
32-
// First check to see if the user is in the usergroups
33-
const isUser = await ldaphelper.isUserInAdGroup(profile.username, domain, userGroup);
34-
35-
if (!isUser) {
36-
const message = `User it not a member of ${userGroup}`;
37-
return done(message, null);
38-
}
25+
try {
26+
profile.username = profile._json.sAMAccountName?.toLowerCase();
27+
profile.email = profile._json.mail;
28+
profile.id = profile.username;
29+
req.user = profile;
3930

40-
// Now check if the user is an admin
41-
const isAdmin = await ldaphelper.isUserInAdGroup(profile.username, domain, adminGroup);
31+
console.log(
32+
`passport.activeDirectory: resolved login ${
33+
profile._json.userPrincipalName
34+
}, profile=${JSON.stringify(profile)}`,
35+
);
36+
// First check to see if the user is in the usergroups
37+
const isUser = await ldaphelper.isUserInAdGroup(profile.username, domain, userGroup);
4238

43-
profile.admin = isAdmin;
44-
console.log(`passport.activeDirectory: ${profile.username} admin=${isAdmin}`);
39+
if (!isUser) {
40+
const message = `User it not a member of ${userGroup}`;
41+
return done(message, null);
42+
}
4543

46-
const user = {
47-
username: profile.username,
48-
admin: isAdmin,
49-
email: profile._json.mail,
50-
displayName: profile.displayName,
51-
title: profile._json.title,
52-
};
44+
// Now check if the user is an admin
45+
const isAdmin = await ldaphelper.isUserInAdGroup(profile.username, domain, adminGroup);
5346

54-
await db.updateUser(user);
47+
profile.admin = isAdmin;
48+
console.log(`passport.activeDirectory: ${profile.username} admin=${isAdmin}`);
5549

56-
return done(null, user);
57-
},
50+
const user = {
51+
username: profile.username,
52+
admin: isAdmin,
53+
email: profile._json.mail,
54+
displayName: profile.displayName,
55+
title: profile._json.title,
56+
};
57+
58+
await db.updateUser(user);
59+
60+
return done(null, user);
61+
} catch (err) {
62+
console.log(`Error authenticating AD user: ${err.message}`);
63+
return done(err, null);
64+
}
65+
}
5866
),
5967
);
6068

@@ -69,4 +77,4 @@ const configure = () => {
6977
return passport;
7078
};
7179

72-
module.exports.configure = configure;
80+
module.exports = { configure };

src/service/passport/index.js

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
1+
const passport = require("passport");
12
const local = require('./local');
23
const activeDirectory = require('./activeDirectory');
34
const oidc = require('./oidc');
45
const config = require('../../config');
5-
const authenticationConfig = config.getAuthentication();
6-
let _passport;
6+
7+
// Allows obtaining strategy config function and type
8+
// Keep in mind to add AuthStrategy enum when refactoring this to TS
9+
const authStrategies = {
10+
local: local,
11+
activedirectory: activeDirectory,
12+
openidconnect: oidc,
13+
};
714

815
const configure = async () => {
9-
const type = authenticationConfig.type.toLowerCase();
10-
11-
switch (type) {
12-
case 'activedirectory':
13-
_passport = await activeDirectory.configure();
14-
break;
15-
case 'local':
16-
_passport = await local.configure();
17-
break;
18-
case 'openidconnect':
19-
_passport = await oidc.configure();
20-
break;
21-
default:
22-
throw Error(`uknown authentication type ${type}`);
16+
passport.initialize();
17+
18+
const authMethods = config.getAuthMethods();
19+
20+
for (const auth of authMethods) {
21+
const strategy = authStrategies[auth.type.toLowerCase()];
22+
if (strategy && typeof strategy.configure === "function") {
23+
await strategy.configure(passport);
24+
}
2325
}
24-
if (!_passport.type) {
25-
_passport.type = type;
26+
27+
if (authMethods.some(auth => auth.type.toLowerCase() === "local")) {
28+
await local.createDefaultAdmin();
2629
}
27-
return _passport;
28-
};
2930

30-
module.exports.configure = configure;
31-
module.exports.getPassport = () => {
32-
return _passport;
31+
return passport;
3332
};
33+
34+
const getPassport = () => passport;
35+
36+
module.exports = { authStrategies, configure, getPassport };

0 commit comments

Comments
 (0)