-
-
Notifications
You must be signed in to change notification settings - Fork 683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] 🔨 Maintenance: Bringing openid-client lib to latest version #544
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,12 +1,20 @@ | ||||||||||||||||||||
import getAccountDb, { clearExpiredSessions } from '../account-db.js'; | ||||||||||||||||||||
import * as uuid from 'uuid'; | ||||||||||||||||||||
import { generators, Issuer } from 'openid-client'; | ||||||||||||||||||||
import * as openIdClient from 'openid-client'; | ||||||||||||||||||||
import finalConfig from '../load-config.js'; | ||||||||||||||||||||
import { TOKEN_EXPIRATION_NEVER } from '../util/validate-user.js'; | ||||||||||||||||||||
import { | ||||||||||||||||||||
getUserByUsername, | ||||||||||||||||||||
transferAllFilesFromUser, | ||||||||||||||||||||
} from '../services/user-service.js'; | ||||||||||||||||||||
import { webcrypto } from 'node:crypto'; | ||||||||||||||||||||
import fetch from 'node-fetch'; | ||||||||||||||||||||
|
||||||||||||||||||||
/* eslint-disable-next-line no-undef */ | ||||||||||||||||||||
if (!globalThis.crypto) { | ||||||||||||||||||||
/* eslint-disable-next-line no-undef */ | ||||||||||||||||||||
globalThis.crypto = webcrypto; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
export async function bootstrapOpenId(config) { | ||||||||||||||||||||
if (!('issuer' in config)) { | ||||||||||||||||||||
|
@@ -48,25 +56,87 @@ export async function bootstrapOpenId(config) { | |||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
async function setupOpenIdClient(config) { | ||||||||||||||||||||
let issuer = | ||||||||||||||||||||
typeof config.issuer === 'string' | ||||||||||||||||||||
? await Issuer.discover(config.issuer) | ||||||||||||||||||||
: new Issuer({ | ||||||||||||||||||||
issuer: config.issuer.name, | ||||||||||||||||||||
authorization_endpoint: config.issuer.authorization_endpoint, | ||||||||||||||||||||
token_endpoint: config.issuer.token_endpoint, | ||||||||||||||||||||
userinfo_endpoint: config.issuer.userinfo_endpoint, | ||||||||||||||||||||
}); | ||||||||||||||||||||
let discovered; | ||||||||||||||||||||
if (typeof config.issuer === 'string') { | ||||||||||||||||||||
discovered = await openIdClient.discovery( | ||||||||||||||||||||
new URL(config.issuer), | ||||||||||||||||||||
config.client_id, | ||||||||||||||||||||
config.client_secret, | ||||||||||||||||||||
); | ||||||||||||||||||||
} else { | ||||||||||||||||||||
const serverMetadata = { | ||||||||||||||||||||
issuer: config.issuer.name, | ||||||||||||||||||||
authorization_endpoint: config.issuer.authorization_endpoint, | ||||||||||||||||||||
token_endpoint: config.issuer.token_endpoint, | ||||||||||||||||||||
userinfo_endpoint: config.issuer.userinfo_endpoint, | ||||||||||||||||||||
}; | ||||||||||||||||||||
discovered = new openIdClient.Configuration( | ||||||||||||||||||||
serverMetadata, | ||||||||||||||||||||
config.client_id, | ||||||||||||||||||||
config.client_secret, | ||||||||||||||||||||
); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
const client = new issuer.Client({ | ||||||||||||||||||||
client_id: config.client_id, | ||||||||||||||||||||
client_secret: config.client_secret, | ||||||||||||||||||||
redirect_uri: new URL( | ||||||||||||||||||||
'/openid/callback', | ||||||||||||||||||||
config.server_hostname, | ||||||||||||||||||||
).toString(), | ||||||||||||||||||||
validate_id_token: true, | ||||||||||||||||||||
}); | ||||||||||||||||||||
const client = { | ||||||||||||||||||||
redirect_uris: [ | ||||||||||||||||||||
new URL('/openid/callback', config.server_hostname).toString(), | ||||||||||||||||||||
], | ||||||||||||||||||||
authorizationUrl(params) { | ||||||||||||||||||||
const urlObj = openIdClient.buildAuthorizationUrl(discovered, { | ||||||||||||||||||||
...params, | ||||||||||||||||||||
redirect_uri: this.redirect_uris[0], | ||||||||||||||||||||
}); | ||||||||||||||||||||
return urlObj.href; | ||||||||||||||||||||
}, | ||||||||||||||||||||
async callback(redirectUri, params, checks) { | ||||||||||||||||||||
const currentUrl = new URL(redirectUri); | ||||||||||||||||||||
currentUrl.searchParams.set('code', params.code); | ||||||||||||||||||||
const tokens = await openIdClient.authorizationCodeGrant( | ||||||||||||||||||||
discovered, | ||||||||||||||||||||
currentUrl, | ||||||||||||||||||||
{ | ||||||||||||||||||||
pkceCodeVerifier: checks.code_verifier, | ||||||||||||||||||||
idTokenExpected: true, | ||||||||||||||||||||
}, | ||||||||||||||||||||
); | ||||||||||||||||||||
return { | ||||||||||||||||||||
access_token: tokens.access_token, | ||||||||||||||||||||
expires_at: tokens.expires_at, | ||||||||||||||||||||
claims: () => tokens.claims?.(), | ||||||||||||||||||||
}; | ||||||||||||||||||||
}, | ||||||||||||||||||||
async grant(grantParams) { | ||||||||||||||||||||
const currentUrl = new URL(this.redirect_uris[0]); | ||||||||||||||||||||
currentUrl.searchParams.set('code', grantParams.code); | ||||||||||||||||||||
currentUrl.searchParams.set('state', grantParams.state); | ||||||||||||||||||||
const tokens = await openIdClient.authorizationCodeGrant( | ||||||||||||||||||||
discovered, | ||||||||||||||||||||
currentUrl, | ||||||||||||||||||||
{ | ||||||||||||||||||||
pkceCodeVerifier: grantParams.code_verifier, | ||||||||||||||||||||
expectedState: grantParams.state, | ||||||||||||||||||||
idTokenExpected: false, | ||||||||||||||||||||
}, | ||||||||||||||||||||
); | ||||||||||||||||||||
return { | ||||||||||||||||||||
access_token: tokens.access_token, | ||||||||||||||||||||
expires_at: tokens.expires_at, | ||||||||||||||||||||
claims: () => tokens.claims?.(), | ||||||||||||||||||||
}; | ||||||||||||||||||||
}, | ||||||||||||||||||||
async userinfo(accessToken, sub = '') { | ||||||||||||||||||||
if (!config.authMethod || config.authMethod === 'openid') { | ||||||||||||||||||||
return openIdClient.fetchUserInfo(discovered, accessToken, sub); | ||||||||||||||||||||
} else { | ||||||||||||||||||||
const response = await fetch('https://api.github.com/user', { | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ question: why are we connecting to GH api here? 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol, that was a test. I need to change to the userinfo endpoint |
||||||||||||||||||||
headers: { | ||||||||||||||||||||
Authorization: `Bearer ${accessToken}`, | ||||||||||||||||||||
}, | ||||||||||||||||||||
}); | ||||||||||||||||||||
return response.json(); | ||||||||||||||||||||
} | ||||||||||||||||||||
}, | ||||||||||||||||||||
}; | ||||||||||||||||||||
|
||||||||||||||||||||
return client; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
@@ -102,9 +172,11 @@ export async function loginWithOpenIdSetup(returnUrl) { | |||||||||||||||||||
return { error: 'openid-setup-failed' }; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
const state = generators.state(); | ||||||||||||||||||||
const code_verifier = generators.codeVerifier(); | ||||||||||||||||||||
const code_challenge = generators.codeChallenge(code_verifier); | ||||||||||||||||||||
const state = openIdClient.randomState(); | ||||||||||||||||||||
const code_verifier = openIdClient.randomPKCECodeVerifier(); | ||||||||||||||||||||
const code_challenge = await openIdClient.calculatePKCECodeChallenge( | ||||||||||||||||||||
code_verifier, | ||||||||||||||||||||
); | ||||||||||||||||||||
Comment on lines
+175
to
+179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incorrect usage of PKCE generation methods The methods Update the code to use the + const { generators } = openIdClient;
+ const code_verifier = generators.codeVerifier();
+ const code_challenge = generators.codeChallenge(code_verifier);
+ const state = generators.state();
- const state = openIdClient.randomState();
- const code_verifier = openIdClient.randomPKCECodeVerifier();
- const code_challenge = await openIdClient.calculatePKCECodeChallenge(
- code_verifier,
- ); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
|
||||||||||||||||||||
const now_time = Date.now(); | ||||||||||||||||||||
const expiry_time = now_time + 300 * 1000; | ||||||||||||||||||||
|
@@ -171,7 +243,6 @@ export async function loginWithOpenIdFinalize(body) { | |||||||||||||||||||
|
||||||||||||||||||||
try { | ||||||||||||||||||||
let tokenSet = null; | ||||||||||||||||||||
|
||||||||||||||||||||
if (!config.authMethod || config.authMethod === 'openid') { | ||||||||||||||||||||
const params = { code: body.code, state: body.state }; | ||||||||||||||||||||
tokenSet = await client.callback(client.redirect_uris[0], params, { | ||||||||||||||||||||
|
@@ -184,9 +255,12 @@ export async function loginWithOpenIdFinalize(body) { | |||||||||||||||||||
code: body.code, | ||||||||||||||||||||
redirect_uri: client.redirect_uris[0], | ||||||||||||||||||||
code_verifier, | ||||||||||||||||||||
state: body.state, | ||||||||||||||||||||
}); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
Comment on lines
+258
to
+261
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incorrect parameters in token exchange When using the Adjust the parameters as follows: tokenSet = await client.grant({
grant_type: 'authorization_code',
code: body.code,
+ redirect_uri: client.metadata.redirect_uris[0],
code_verifier,
- state: body.state,
}); Ensure that the
|
||||||||||||||||||||
const userInfo = await client.userinfo(tokenSet.access_token); | ||||||||||||||||||||
|
||||||||||||||||||||
const identity = | ||||||||||||||||||||
userInfo.preferred_username ?? | ||||||||||||||||||||
userInfo.login ?? | ||||||||||||||||||||
|
@@ -207,7 +281,6 @@ export async function loginWithOpenIdFinalize(body) { | |||||||||||||||||||
); | ||||||||||||||||||||
if (countUsersWithUserName === 0) { | ||||||||||||||||||||
userId = uuid.v4(); | ||||||||||||||||||||
// Check if user was created by another transaction | ||||||||||||||||||||
const existingUser = accountDb.first( | ||||||||||||||||||||
'SELECT id FROM users WHERE user_name = ?', | ||||||||||||||||||||
[identity], | ||||||||||||||||||||
|
@@ -256,12 +329,11 @@ export async function loginWithOpenIdFinalize(body) { | |||||||||||||||||||
} else if (error.message === 'openid-grant-failed') { | ||||||||||||||||||||
return { error: 'openid-grant-failed' }; | ||||||||||||||||||||
} else { | ||||||||||||||||||||
throw error; // Re-throw other unexpected errors | ||||||||||||||||||||
throw error; | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unhandled exception may cause server crash In the catch block, an error is re-thrown without proper handling. This could cause the server to crash if the exception is not caught elsewhere. Consider handling the error gracefully and returning an appropriate response to the client. } else {
- throw error;
+ console.error('An unexpected error occurred:', error);
+ return { error: 'unknown-error' };
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
const token = uuid.v4(); | ||||||||||||||||||||
|
||||||||||||||||||||
let expiration; | ||||||||||||||||||||
if (finalConfig.token_expiration === 'openid-provider') { | ||||||||||||||||||||
expiration = tokenSet.expires_at ?? TOKEN_EXPIRATION_NEVER; | ||||||||||||||||||||
|
@@ -314,7 +386,6 @@ export function isValidRedirectUrl(url) { | |||||||||||||||||||
try { | ||||||||||||||||||||
const redirectUrl = new URL(url); | ||||||||||||||||||||
const serverUrl = new URL(serverHostname); | ||||||||||||||||||||
|
||||||||||||||||||||
if ( | ||||||||||||||||||||
redirectUrl.hostname === serverUrl.hostname || | ||||||||||||||||||||
redirectUrl.hostname === 'localhost' | ||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
category: Maintenance | ||
authors: [lelemm] | ||
--- | ||
|
||
Bring openid-client lib to latest version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential misuse of
openid-client
methodsThe methods
openIdClient.discovery
andnew openIdClient.Configuration(...)
do not align with theopenid-client
library's documented usage in version 6.x. Typically, you should useopenIdClient.Issuer.discover()
to discover the OpenID Provider's configuration and create aClient
instance accordingly.Consider revising the code to correctly utilize the
openid-client
library methods. Here's how you might adjust the implementation: