From bc0b466a8e79911022ce38937e0dc832154d5b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Fri, 24 Feb 2023 16:30:45 +0000 Subject: [PATCH] refactor: improve code structure --- backend/routes/api/oidc.js | 171 ++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 69 deletions(-) diff --git a/backend/routes/api/oidc.js b/backend/routes/api/oidc.js index e60949b3..e3da2caa 100644 --- a/backend/routes/api/oidc.js +++ b/backend/routes/api/oidc.js @@ -29,36 +29,13 @@ router * Retrieve all users */ .get(jwtdecode(), async (req, res, next) => { - console.log("oidc init >>>", res.locals.access, oidc); - + console.log("oidc: init flow"); settingModel .query() .where({id: 'oidc-config'}) .first() - .then( async row => { - console.log('oidc init > config > ', row); - - let issuer = await oidc.Issuer.discover(row.meta.issuerURL); - let client = new issuer.Client({ - client_id: row.meta.clientID, - client_secret: row.meta.clientSecret, - redirect_uris: [row.meta.redirectURL], - response_types: ['code'], - }) - let state = crypto.randomUUID(); - let nonce = crypto.randomUUID(); - let url = client.authorizationUrl({ - scope: 'openid email profile', - resource: 'http://rye.local:2081/api/oidc/callback', - state, - nonce, - }) - - console.log('oidc init > url > ', state, nonce, url); - - res.cookie("npm_oidc", state + '--' + nonce); - res.redirect(url); - }); + .then( row => getInitParams(req, row)) + .then( params => redirectToAuthorizationURL(res, params)); }); @@ -80,53 +57,109 @@ router * Retrieve a specific user */ .get(jwtdecode(), async (req, res, next) => { - console.log("oidc callback >>>"); - + console.log("oidc: callback"); settingModel .query() .where({id: 'oidc-config'}) .first() - .then( async row => { - console.log('oidc callback > config > ', row); - - let issuer = await oidc.Issuer.discover(row.meta.issuerURL); - let client = new issuer.Client({ - client_id: row.meta.clientID, - client_secret: row.meta.clientSecret, - redirect_uris: [row.meta.redirectURL], - response_types: ['code'], - }); - - let state, nonce; - let cookies = req.headers.cookie.split(';'); - for (cookie of cookies) { - if (cookie.split('=')[0].trim() === 'npm_oidc') { - let raw = cookie.split('=')[1]; - let val = raw.split('--'); - state = val[0].trim(); - nonce = val[1].trim(); - break; - } - } - - const params = client.callbackParams(req); - const tokenSet = await client.callback(row.meta.redirectURL, params, { /*code_verifier: verifier,*/ state, nonce }); - let claims = tokenSet.claims(); - console.log('validated ID Token claims %j', claims); - - return internalToken.getTokenFromOAuthClaim({ identity: claims.email }) - - }) - .then( response => { - console.log('oidc callback > signed token > >', response); - res.cookie('npm_oidc', response.token + '---' + response.expires); - res.redirect('/login'); - }) - .catch( err => { - console.log('oidc callback ERR > ', err); - res.cookie('npm_oidc_error', err.message); - res.redirect('/login'); - }); + .then( settings => validateCallback(req, settings)) + .then( token => redirectWithJwtToken(res, token)) + .catch( err => redirectWithError(res, err)); }); +/** + * Executed discovery and returns the configured `openid-client` client + * + * @param {Setting} row + * */ +let getClient = async row => { + let issuer = await oidc.Issuer.discover(row.meta.issuerURL); + + return new issuer.Client({ + client_id: row.meta.clientID, + client_secret: row.meta.clientSecret, + redirect_uris: [row.meta.redirectURL], + response_types: ['code'], + }); +} + +/** + * Generates state, nonce and authorization url. + * + * @param {Request} req + * @param {Setting} row + * @return { {String}, {String}, {String} } state, nonce and url + * */ +let getInitParams = async (req, row) => { + let client = await getClient(row); + let state = crypto.randomUUID(); + let nonce = crypto.randomUUID(); + let url = client.authorizationUrl({ + scope: 'openid email profile', + resource: `${req.protocol}://${req.get('host')}${req.originalUrl}`, + state, + nonce, + }) + + return { state, nonce, url }; +} + +/** + * Parses state and nonce from cookie during the callback phase. + * + * @param {Request} req + * @return { {String}, {String} } state and nonce + * */ +let parseStateFromCookie = req => { + let state, nonce; + let cookies = req.headers.cookie.split(';'); + for (cookie of cookies) { + if (cookie.split('=')[0].trim() === 'npm_oidc') { + let raw = cookie.split('=')[1]; + let val = raw.split('--'); + state = val[0].trim(); + nonce = val[1].trim(); + break; + } + } + + return { state, nonce }; +} + +/** + * Executes validation of callback parameters. + * + * @param {Request} req + * @param {Setting} settings + * @return {Promise} a promise resolving to a jwt token + * */ +let validateCallback = async (req, settings) => { + let client = await getClient(settings); + let { state, nonce } = parseStateFromCookie(req); + + const params = client.callbackParams(req); + const tokenSet = await client.callback(settings.meta.redirectURL, params, { state, nonce }); + let claims = tokenSet.claims(); + console.log('oidc: authentication successful for email', claims.email); + + return internalToken.getTokenFromOAuthClaim({ identity: claims.email }) +} + +let redirectToAuthorizationURL = (res, params) => { + console.log('oidc: init flow > url > ', params.url); + res.cookie("npm_oidc", params.state + '--' + params.nonce); + res.redirect(params.url); +} + +let redirectWithJwtToken = (res, token) => { + res.cookie('npm_oidc', token.token + '---' + token.expires); + res.redirect('/login'); +} + +let redirectWithError = (res, error) => { + console.log('oidc: callback error: ', error); + res.cookie('npm_oidc_error', error.message); + res.redirect('/login'); +} + module.exports = router;