pull/7445/merge
David 3 weeks ago committed by GitHub
commit 6a865e42f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -8,7 +8,7 @@
"vue"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"i18n-ally.localesPaths": [
"server/locales"

@ -101,6 +101,7 @@
"js-yaml": "3.14.0",
"jsdom": "16.4.0",
"jsonwebtoken": "9.0.0",
"jwks-rsa": "3.1.0",
"katex": "0.12.0",
"klaw": "3.0.0",
"knex": "0.21.7",

@ -0,0 +1,47 @@
const jwt = require('jsonwebtoken')
const jwksClient = require('jwks-rsa')
/**
* Function to get the signing key for a specific token.
* @param {Object} header - JWT header containing the `kid`.
* @returns {Promise<string>} - Resolves with the signing key.
*/
function getSigningKey(header, jwksUri) {
return new Promise((resolve, reject) => {
const client = jwksClient({ jwksUri })
client.getSigningKey(header.kid, (err, key) => {
if (err) {
return reject(new Error('Error getting signing key: ' + err))
}
resolve(key.getPublicKey())
})
})
}
/**
* Verifies a JWT token using a public key from JWKS.
* @param {string} token - The JWT token to verify.
* @param {Object} conf - Configuration object containing `issuer` and `clientId`.
* @returns {Promise<Object>} - Resolves with the decoded token if verification is successful.
*/
async function verifyJwt(token, conf) {
try {
const decodedHeader = jwt.decode(token, { complete: true })
if (!decodedHeader || !decodedHeader.header) {
throw new Error('JWT verification failed: Invalid token header')
}
const signingKey = await getSigningKey(decodedHeader.header, conf.jwksUri)
const decoded = jwt.verify(token, signingKey, {
algorithms: conf.algorithms || ['RS256'],
issuer: conf.issuer,
audience: conf.clientId
})
return decoded
} catch (err) {
throw new Error('JWT verification failed: ' + err.message)
}
}
module.exports = {
verifyJwt
}

@ -1,4 +1,5 @@
const _ = require('lodash')
const { verifyJwt } = require('../../../helpers/jwt')
/* global WIKI */
@ -9,33 +10,60 @@ const _ = require('lodash')
const OpenIDConnectStrategy = require('passport-openidconnect').Strategy
module.exports = {
init (passport, conf) {
passport.use(conf.key,
new OpenIDConnectStrategy({
async init(passport, conf) {
try {
let oidcConfig = {
issuer: conf.issuer,
authorizationURL: conf.authorizationURL,
tokenURL: conf.tokenURL,
userInfoURL: conf.userInfoURL,
clientID: conf.clientId,
clientSecret: conf.clientSecret,
issuer: conf.issuer,
userInfoURL: conf.userInfoURL,
callbackURL: conf.callbackURL,
scope: 'profile email ' + conf.scope,
passReqToCallback: true,
skipUserProfile: conf.skipUserProfile,
acrValues: conf.acrValues
}, async (req, iss, uiProfile, idProfile, context, idToken, accessToken, refreshToken, params, cb) => {
const profile = Object.assign({}, idProfile, uiProfile)
}
if (conf.wellKnownURL) {
try {
const response = await fetch(conf.wellKnownURL)
if (!response.ok) throw new Error(response.statusText)
const wellKnown = await response.json()
if (!oidcConfig.issuer) oidcConfig.issuer = wellKnown.issuer
if (!oidcConfig.authorizationURL) oidcConfig.authorizationURL = wellKnown.authorization_endpoint
if (!oidcConfig.tokenURL) oidcConfig.tokenURL = wellKnown.token_endpoint
if (!oidcConfig.userInfoURL) oidcConfig.userInfoURL = wellKnown.userinfo_endpoint
oidcConfig.jwksUri = wellKnown.jwks_uri
oidcConfig.idTokenSigningAlgValuesSupported = wellKnown.id_token_signing_alg_values_supported
} catch (error) {
WIKI.logger.error('Error fetching OIDC well-known configuration:', error)
}
}
passport.use(conf.key, new OpenIDConnectStrategy(oidcConfig, async (req, iss, uiProfile, idProfile, context, idToken, accessToken, refreshToken, params, cb) => {
let idTokenClaims = {}
if (conf.mergeIdTokenClaims && idToken) {
idTokenClaims = await verifyJwt(idToken, {
issuer: oidcConfig.issuer,
clientId: oidcConfig.clientID,
jwksUri: oidcConfig.jwksUri,
algorithms: oidcConfig.idTokenSigningAlgValuesSupported
})
}
// Merge claims from ID token and profile, with idProfile taking precedence
const profile = { ...idTokenClaims, ...idProfile }
try {
const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy,
profile: {
...profile,
email: _.get(profile, '_json.' + conf.emailClaim),
displayName: _.get(profile, '_json.' + conf.displayNameClaim, '')
id: _.get(profile, conf.userIdClaim),
displayName: _.get(profile, conf.displayNameClaim, 'Unknown User'),
email: _.get(profile, conf.emailClaim)
}
})
if (conf.mapGroups) {
const groups = _.get(profile, '_json.' + conf.groupsClaim)
const groups = _.get(profile, conf.groupsClaim)
if (groups && _.isArray(groups)) {
const currentGroups = (await user.$relatedQuery('groups').select('groups.id')).map(g => g.id)
const expectedGroups = Object.values(WIKI.auth.groups).filter(g => groups.includes(g.name)).map(g => g.id)
@ -51,14 +79,12 @@ module.exports = {
} catch (err) {
cb(err, null)
}
})
)
},
logout (conf) {
if (!conf.logoutURL) {
return '/'
} else {
return conf.logoutURL
}))
} catch (err) {
WIKI.logger.error(`Error initializing OpenID Connect strategy: ${err}`)
}
},
logout(conf) {
return conf.logoutURL || '/'
}
}

@ -7,66 +7,62 @@ color: blue-grey darken-2
website: http://openid.net/connect/
isAvailable: true
useForm: false
scopes:
- openid
- profile
- email
props:
clientId:
wellKnownURL:
type: String
title: Client ID
hint: Application Client ID
title: Well-Known Configuration URL
hint: The Well-Known configuration Endpoint URL (e.g. https://provider/.well-known/openid-configuration)
order: 1
clientSecret:
type: String
title: Client Secret
hint: Application Client Secret
order: 2
authorizationURL:
type: String
title: Authorization Endpoint URL
hint: Application Authorization Endpoint URL
order: 3
hint: Application Authorization Endpoint URL (overrides value from well-known URL if set)
order: 2
tokenURL:
type: String
title: Token Endpoint URL
hint: Application Token Endpoint URL
order: 4
hint: Application Token Endpoint URL (overrides value from well-known URL if set)
order: 3
userInfoURL:
type: String
title: User Info Endpoint URL
hint: User Info Endpoint URL
hint: User Info Endpoint URL (overrides value from well-known URL if set)
order: 4
issuer:
type: String
title: Issuer URL
hint: Issuer URL (overrides value from well-known URL if set)
order: 5
skipUserProfile:
type: Boolean
default: false
title: Skip User Profile
hint: Skips call to the OIDC UserInfo endpoint
clientId:
type: String
title: Client ID
hint: Application Client ID
order: 6
issuer:
clientSecret:
type: String
title: Issuer
hint: Issuer URL
title: Client Secret
hint: Application Client Secret
order: 7
userIdClaim:
type: String
title: User Id Claim
hint: Field containing the unique user identifier
default: sub
maxWidth: 500
order: 8
emailClaim:
type: String
title: Email Claim
hint: Field containing the email address
default: email
maxWidth: 500
order: 8
order: 9
displayNameClaim:
type: String
title: Display Name Claim
hint: Field containing the user display name
default: displayName
default: name
maxWidth: 500
order: 9
mapGroups:
type: Boolean
title: Map Groups
hint: Map groups matching names from the groups claim value
default: false
order: 10
groupsClaim:
type: String
@ -75,13 +71,37 @@ props:
default: groups
maxWidth: 500
order: 11
mergeIdTokenClaims:
type: Boolean
title: Merge ID Token Claims
hint: If enabled, verifies the ID token and merges its claims into the user profile
default: false
order: 12
mapGroups:
type: Boolean
title: Map Groups
hint: Map groups matching names from the groups claim value
default: false
order: 13
skipUserProfile:
type: Boolean
default: false
title: Skip User Profile
hint: Skips call to the OIDC UserInfo endpoint
order: 14
logoutURL:
type: String
title: Logout URL
hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process.
order: 12
order: 15
scope:
type: String
title: Additional Scopes
hint: (optional) Additional space-separated OIDC scopes (e.g. 'offline_access groups') - openid, profile and email are always included
maxWidth: 500
order: 16
acrValues:
type: String
title: ACR Values
hint: (optional) Authentication Context Class Reference
order: 13
order: 17

Loading…
Cancel
Save