Skip to content

Commit 03c1460

Browse files
sidshas03coopernetes
authored andcommitted
fix: resolve remaining Cypress CI issues
- Fix CSRF protection by setting NODE_ENV=test in CI - Fix OIDC strategy by checking enabled flag before configuration - Fix port configuration by using correct server port (8080) - Add start:ci script to run server only (not dev client) - Set CYPRESS_BASE_URL environment variable for consistency This should resolve: - CSRF token missing errors in CI - Unknown authentication strategy errors - Port mismatch issues (3000 vs 8080) - Shell script syntax errors with & character
1 parent a151d37 commit 03c1460

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

src/service/passport/oidc.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
const db = require('../../db');
2+
3+
const type = 'openidconnect';
4+
5+
const configure = async (passport) => {
6+
// Temp fix for ERR_REQUIRE_ESM, will be changed when we refactor to ESM
7+
const { discovery, fetchUserInfo } = await import('openid-client');
8+
const { Strategy } = await import('openid-client/passport');
9+
const authMethods = require('../../config').getAuthMethods();
10+
const oidcMethod = authMethods.find((method) => method.type.toLowerCase() === 'openidconnect');
11+
12+
if (!oidcMethod || !oidcMethod.enabled) {
13+
console.log('OIDC authentication is not enabled, skipping configuration');
14+
return passport;
15+
}
16+
17+
const oidcConfig = oidcMethod.oidcConfig;
18+
const { issuer, clientID, clientSecret, callbackURL, scope } = oidcConfig;
19+
20+
if (!oidcConfig || !oidcConfig.issuer) {
21+
throw new Error('Missing OIDC issuer in configuration');
22+
}
23+
24+
const server = new URL(issuer);
25+
let config;
26+
27+
try {
28+
config = await discovery(server, clientID, clientSecret);
29+
} catch (error) {
30+
console.error('Error during OIDC discovery:', error);
31+
throw new Error('OIDC setup error (discovery): ' + error.message);
32+
}
33+
34+
try {
35+
const strategy = new Strategy({ callbackURL, config, scope }, async (tokenSet, done) => {
36+
// Validate token sub for added security
37+
const idTokenClaims = tokenSet.claims();
38+
const expectedSub = idTokenClaims.sub;
39+
const userInfo = await fetchUserInfo(config, tokenSet.access_token, expectedSub);
40+
handleUserAuthentication(userInfo, done);
41+
});
42+
43+
// currentUrl must be overridden to match the callback URL
44+
strategy.currentUrl = function (request) {
45+
const callbackUrl = new URL(callbackURL);
46+
const currentUrl = Strategy.prototype.currentUrl.call(this, request);
47+
currentUrl.host = callbackUrl.host;
48+
currentUrl.protocol = callbackUrl.protocol;
49+
return currentUrl;
50+
};
51+
52+
// Prevent default strategy name from being overridden with the server host
53+
passport.use(type, strategy);
54+
55+
passport.serializeUser((user, done) => {
56+
done(null, user.oidcId || user.username);
57+
});
58+
59+
passport.deserializeUser(async (id, done) => {
60+
try {
61+
const user = await db.findUserByOIDC(id);
62+
done(null, user);
63+
} catch (err) {
64+
done(err);
65+
}
66+
});
67+
68+
return passport;
69+
} catch (error) {
70+
console.error('Error during OIDC passport setup:', error);
71+
throw new Error('OIDC setup error (strategy): ' + error.message);
72+
}
73+
};
74+
75+
/**
76+
* Handles user authentication with OIDC.
77+
* @param {Object} userInfo the OIDC user info object
78+
* @param {Function} done the callback function
79+
* @return {Promise} a promise with the authenticated user or an error
80+
*/
81+
const handleUserAuthentication = async (userInfo, done) => {
82+
console.log('handleUserAuthentication called');
83+
try {
84+
const user = await db.findUserByOIDC(userInfo.sub);
85+
86+
if (!user) {
87+
const email = safelyExtractEmail(userInfo);
88+
if (!email) return done(new Error('No email found in OIDC profile'));
89+
90+
const newUser = {
91+
username: getUsername(email),
92+
email,
93+
oidcId: userInfo.sub,
94+
};
95+
96+
await db.createUser(newUser.username, null, newUser.email, 'Edit me', false, newUser.oidcId);
97+
return done(null, newUser);
98+
}
99+
100+
return done(null, user);
101+
} catch (err) {
102+
return done(err);
103+
}
104+
};
105+
106+
/**
107+
* Extracts email from OIDC profile.
108+
* This function is necessary because OIDC providers have different ways of storing emails.
109+
* @param {object} profile the profile object from OIDC provider
110+
* @return {string | null} the email address
111+
*/
112+
const safelyExtractEmail = (profile) => {
113+
return (
114+
profile.email || (profile.emails && profile.emails.length > 0 ? profile.emails[0].value : null)
115+
);
116+
};
117+
118+
/**
119+
* Generates a username from email address.
120+
* This helps differentiate users within the specific OIDC provider.
121+
* Note: This is incompatible with multiple providers. Ideally, users are identified by
122+
* OIDC ID (requires refactoring the database).
123+
* @param {string} email the email address
124+
* @return {string} the username
125+
*/
126+
const getUsername = (email) => {
127+
return email ? email.split('@')[0] : '';
128+
};
129+
130+
module.exports = { configure, type };

0 commit comments

Comments
 (0)