diff --git a/packages/wekan-oidc/oidc_server.js b/packages/wekan-oidc/oidc_server.js index 45c4f133e..af82b7c19 100644 --- a/packages/wekan-oidc/oidc_server.js +++ b/packages/wekan-oidc/oidc_server.js @@ -1,4 +1,6 @@ import {addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler'; +import { fetch, Headers } from 'meteor/fetch'; +import https from 'https'; Oidc = {}; httpCa = false; @@ -18,10 +20,10 @@ var profile = {}; var serviceData = {}; var userinfo = {}; -OAuth.registerService('oidc', 2, null, function (query) { +OAuth.registerService('oidc', 2, null, async function (query) { var debug = process.env.DEBUG === 'true'; - var token = getToken(query); + var token = await getToken(query); if (debug) console.log('XXX: register token:', token); var accessToken = token.access_token || token.id_token; @@ -40,7 +42,7 @@ OAuth.registerService('oidc', 2, null, function (query) { else { // normal behaviour, getting the claims from UserInfo endpoint. - userinfo = getUserInfo(accessToken); + userinfo = await getUserInfo(accessToken); } if (userinfo.ocs) userinfo = userinfo.ocs.data; // Nextcloud hack @@ -73,7 +75,8 @@ OAuth.registerService('oidc', 2, null, function (query) { if (accessToken) { var tokenContent = getTokenContent(accessToken); - var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields); + var config = await getConfiguration(); + var fields = _.pick(tokenContent, config.idTokenWhitelistFields); _.extend(serviceData, fields); } @@ -100,7 +103,7 @@ OAuth.registerService('oidc', 2, null, function (query) { // therefore: keep admin privileges for wekan as before if(Array.isArray(serviceData.groups) && serviceData.groups.length && typeof serviceData.groups[0] === "string" ) { - user = Meteor.users.findOne({'_id': serviceData.id}); + user = await Meteor.users.findOneAsync({'_id': serviceData.id}); serviceData.groups.forEach(function(groupName, i) { @@ -119,8 +122,8 @@ OAuth.registerService('oidc', 2, null, function (query) { // Fix OIDC login loop for integer user ID. Thanks to danielkaiser. // https://github.com/wekan/wekan/issues/4795 - Meteor.call('groupRoutineOnLogin',serviceData, ""+serviceData.id); - Meteor.call('boardRoutineOnLogin',serviceData, ""+serviceData.id); + await Meteor.callAsync('groupRoutineOnLogin',serviceData, ""+serviceData.id); + await Meteor.callAsync('boardRoutineOnLogin',serviceData, ""+serviceData.id); return { serviceData: serviceData, @@ -134,143 +137,166 @@ if (Meteor.release) { } if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { - var getToken = function (query) { + var getToken = async function (query) { var debug = process.env.DEBUG === 'true'; - var config = getConfiguration(); + var config = await getConfiguration(); + var serverTokenEndpoint; if(config.tokenEndpoint.includes('https://')){ - var serverTokenEndpoint = config.tokenEndpoint; + serverTokenEndpoint = config.tokenEndpoint; }else{ - var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; + serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; } - var requestPermissions = config.requestPermissions; - var response; try { - var postOptions = { - headers: { - Accept: 'application/json', - "User-Agent": userAgent - }, - params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - state: query.state - } - }; + var body = new URLSearchParams({ + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('oidc', config), + grant_type: 'authorization_code', + state: query.state + }); + + var fetchOptions = { + method: 'POST', + headers: new Headers({ + 'Accept': 'application/json', + 'User-Agent': userAgent, + 'Content-Type': 'application/x-www-form-urlencoded' + }), + body: body.toString() + }; + if (httpCa) { - postOptions['npmRequestOptions'] = { ca: httpCa }; + fetchOptions.agent = new https.Agent({ ca: httpCa }); } - response = HTTP.post(serverTokenEndpoint, postOptions); + + var response = await fetch(serverTokenEndpoint, fetchOptions); + var data = await response.json(); + + if (!response.ok) { + throw new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + response.statusText); + } + if (data.error) { + // if the http response was a json object with an error attribute + throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + data.error); + } + if (debug) console.log('XXX: getToken response: ', data); + return data; } catch (err) { throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), { response: err.response }); } - if (response.data.error) { - // if the http response was a json object with an error attribute - throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); - } else { - if (debug) console.log('XXX: getToken response: ', response.data); - return response.data; - } }; } if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { - var getToken = function (query) { + var getToken = async function (query) { var debug = process.env.DEBUG === 'true'; - var config = getConfiguration(); + var config = await getConfiguration(); + var serverTokenEndpoint; if(config.tokenEndpoint.includes('https://')){ - var serverTokenEndpoint = config.tokenEndpoint; + serverTokenEndpoint = config.tokenEndpoint; }else{ - var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; + serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; } - var requestPermissions = config.requestPermissions; - var response; // OIM needs basic Authentication token in the header - ClientID + SECRET in base64 - var dataToken=null; - var strBasicToken=null; - var strBasicToken64=null; - - dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET; - strBasicToken = new Buffer(dataToken); - strBasicToken64 = strBasicToken.toString('base64'); + var dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET; + var strBasicToken64 = Buffer.from(dataToken).toString('base64'); // eslint-disable-next-line no-console if (debug) console.log('Basic Token: ', strBasicToken64); try { - var postOptions = { - headers: { - Accept: 'application/json', - "User-Agent": userAgent, - "Authorization": "Basic " + strBasicToken64 - }, - params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - state: query.state - } - }; + var body = new URLSearchParams({ + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('oidc', config), + grant_type: 'authorization_code', + state: query.state + }); + + var fetchOptions = { + method: 'POST', + headers: new Headers({ + 'Accept': 'application/json', + 'User-Agent': userAgent, + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + strBasicToken64 + }), + body: body.toString() + }; + if (httpCa) { - postOptions['npmRequestOptions'] = { ca: httpCa }; + fetchOptions.agent = new https.Agent({ ca: httpCa }); } - response = HTTP.post(serverTokenEndpoint, postOptions); + + var response = await fetch(serverTokenEndpoint, fetchOptions); + var data = await response.json(); + + if (!response.ok) { + throw new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + response.statusText); + } + if (data.error) { + // if the http response was a json object with an error attribute + throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + data.error); + } + // eslint-disable-next-line no-console + if (debug) console.log('XXX: getToken response: ', data); + return data; } catch (err) { throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), { response: err.response }); } - if (response.data.error) { - // if the http response was a json object with an error attribute - throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); - } else { - // eslint-disable-next-line no-console - if (debug) console.log('XXX: getToken response: ', response.data); - return response.data; - } }; } -var getUserInfo = function (accessToken) { +var getUserInfo = async function (accessToken) { var debug = process.env.DEBUG === 'true'; - var config = getConfiguration(); + var config = await getConfiguration(); // Some userinfo endpoints use a different base URL than the authorization or token endpoints. // This logic allows the end user to override the setting by providing the full URL to userinfo in their config. + var serverUserinfoEndpoint; if (config.userinfoEndpoint.includes("https://")) { - var serverUserinfoEndpoint = config.userinfoEndpoint; + serverUserinfoEndpoint = config.userinfoEndpoint; } else { - var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint; + serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint; } - var response; + try { - var getOptions = { - headers: { - "User-Agent": userAgent, - "Authorization": "Bearer " + accessToken - } - }; + var fetchOptions = { + method: 'GET', + headers: new Headers({ + 'User-Agent': userAgent, + 'Authorization': 'Bearer ' + accessToken + }) + }; + if (httpCa) { - getOptions['npmRequestOptions'] = { ca: httpCa }; + fetchOptions.agent = new https.Agent({ ca: httpCa }); } - response = HTTP.get(serverUserinfoEndpoint, getOptions); + + var response = await fetch(serverUserinfoEndpoint, fetchOptions); + + if (!response.ok) { + throw new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + response.statusText); + } + + var data = await response.json(); + if (debug) console.log('XXX: getUserInfo response: ', data); + return data; } catch (err) { throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message), {response: err.response}); } - if (debug) console.log('XXX: getUserInfo response: ', response.data); - return response.data; }; -var getConfiguration = function () { - var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' }); +var getConfiguration = async function () { + var config = await ServiceConfiguration.configurations.findOneAsync({ service: 'oidc' }); if (!config) { throw new ServiceConfiguration.ConfigError('Service oidc not configured.'); } diff --git a/packages/wekan-oidc/package.js b/packages/wekan-oidc/package.js index d254e0dcc..83d68bde7 100644 --- a/packages/wekan-oidc/package.js +++ b/packages/wekan-oidc/package.js @@ -8,7 +8,7 @@ Package.describe({ Package.onUse(function(api) { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http', ['server']); + api.use('fetch', ['server']); api.use('underscore', 'client'); api.use('ecmascript'); api.use('templating', 'client');