Migrate wekan-oidc from HTTP to fetch

This commit is contained in:
Harry Adel 2026-01-29 20:45:00 +02:00
parent 0fcdf47545
commit 49a272d6c0
2 changed files with 118 additions and 92 deletions

View file

@ -1,4 +1,6 @@
import {addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler'; import {addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler';
import { fetch, Headers } from 'meteor/fetch';
import https from 'https';
Oidc = {}; Oidc = {};
httpCa = false; httpCa = false;
@ -18,10 +20,10 @@ var profile = {};
var serviceData = {}; var serviceData = {};
var userinfo = {}; var userinfo = {};
OAuth.registerService('oidc', 2, null, function (query) { OAuth.registerService('oidc', 2, null, async function (query) {
var debug = process.env.DEBUG === 'true'; var debug = process.env.DEBUG === 'true';
var token = getToken(query); var token = await getToken(query);
if (debug) console.log('XXX: register token:', token); if (debug) console.log('XXX: register token:', token);
var accessToken = token.access_token || token.id_token; var accessToken = token.access_token || token.id_token;
@ -40,7 +42,7 @@ OAuth.registerService('oidc', 2, null, function (query) {
else else
{ {
// normal behaviour, getting the claims from UserInfo endpoint. // normal behaviour, getting the claims from UserInfo endpoint.
userinfo = getUserInfo(accessToken); userinfo = await getUserInfo(accessToken);
} }
if (userinfo.ocs) userinfo = userinfo.ocs.data; // Nextcloud hack if (userinfo.ocs) userinfo = userinfo.ocs.data; // Nextcloud hack
@ -73,7 +75,8 @@ OAuth.registerService('oidc', 2, null, function (query) {
if (accessToken) { if (accessToken) {
var tokenContent = getTokenContent(accessToken); var tokenContent = getTokenContent(accessToken);
var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields); var config = await getConfiguration();
var fields = _.pick(tokenContent, config.idTokenWhitelistFields);
_.extend(serviceData, fields); _.extend(serviceData, fields);
} }
@ -100,7 +103,7 @@ OAuth.registerService('oidc', 2, null, function (query) {
// therefore: keep admin privileges for wekan as before // therefore: keep admin privileges for wekan as before
if(Array.isArray(serviceData.groups) && serviceData.groups.length && typeof serviceData.groups[0] === "string" ) 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) 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. // Fix OIDC login loop for integer user ID. Thanks to danielkaiser.
// https://github.com/wekan/wekan/issues/4795 // https://github.com/wekan/wekan/issues/4795
Meteor.call('groupRoutineOnLogin',serviceData, ""+serviceData.id); await Meteor.callAsync('groupRoutineOnLogin',serviceData, ""+serviceData.id);
Meteor.call('boardRoutineOnLogin',serviceData, ""+serviceData.id); await Meteor.callAsync('boardRoutineOnLogin',serviceData, ""+serviceData.id);
return { return {
serviceData: serviceData, serviceData: serviceData,
@ -134,143 +137,166 @@ if (Meteor.release) {
} }
if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { 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 debug = process.env.DEBUG === 'true';
var config = getConfiguration(); var config = await getConfiguration();
var serverTokenEndpoint;
if(config.tokenEndpoint.includes('https://')){ if(config.tokenEndpoint.includes('https://')){
var serverTokenEndpoint = config.tokenEndpoint; serverTokenEndpoint = config.tokenEndpoint;
}else{ }else{
var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; serverTokenEndpoint = config.serverUrl + config.tokenEndpoint;
} }
var requestPermissions = config.requestPermissions;
var response;
try { try {
var postOptions = { var body = new URLSearchParams({
headers: { code: query.code,
Accept: 'application/json', client_id: config.clientId,
"User-Agent": userAgent client_secret: OAuth.openSecret(config.secret),
}, redirect_uri: OAuth._redirectUri('oidc', config),
params: { grant_type: 'authorization_code',
code: query.code, state: query.state
client_id: config.clientId, });
client_secret: OAuth.openSecret(config.secret),
redirect_uri: OAuth._redirectUri('oidc', config), var fetchOptions = {
grant_type: 'authorization_code', method: 'POST',
state: query.state headers: new Headers({
} 'Accept': 'application/json',
}; 'User-Agent': userAgent,
'Content-Type': 'application/x-www-form-urlencoded'
}),
body: body.toString()
};
if (httpCa) { 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) { } catch (err) {
throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message),
{ response: err.response }); { 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) { 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 debug = process.env.DEBUG === 'true';
var config = getConfiguration(); var config = await getConfiguration();
var serverTokenEndpoint;
if(config.tokenEndpoint.includes('https://')){ if(config.tokenEndpoint.includes('https://')){
var serverTokenEndpoint = config.tokenEndpoint; serverTokenEndpoint = config.tokenEndpoint;
}else{ }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 // OIM needs basic Authentication token in the header - ClientID + SECRET in base64
var dataToken=null; var dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET;
var strBasicToken=null; var strBasicToken64 = Buffer.from(dataToken).toString('base64');
var strBasicToken64=null;
dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET;
strBasicToken = new Buffer(dataToken);
strBasicToken64 = strBasicToken.toString('base64');
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
if (debug) console.log('Basic Token: ', strBasicToken64); if (debug) console.log('Basic Token: ', strBasicToken64);
try { try {
var postOptions = { var body = new URLSearchParams({
headers: { code: query.code,
Accept: 'application/json', client_id: config.clientId,
"User-Agent": userAgent, client_secret: OAuth.openSecret(config.secret),
"Authorization": "Basic " + strBasicToken64 redirect_uri: OAuth._redirectUri('oidc', config),
}, grant_type: 'authorization_code',
params: { state: query.state
code: query.code, });
client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret), var fetchOptions = {
redirect_uri: OAuth._redirectUri('oidc', config), method: 'POST',
grant_type: 'authorization_code', headers: new Headers({
state: query.state 'Accept': 'application/json',
} 'User-Agent': userAgent,
}; 'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + strBasicToken64
}),
body: body.toString()
};
if (httpCa) { 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) { } catch (err) {
throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message),
{ response: err.response }); { 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 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. // 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. // 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://")) { if (config.userinfoEndpoint.includes("https://")) {
var serverUserinfoEndpoint = config.userinfoEndpoint; serverUserinfoEndpoint = config.userinfoEndpoint;
} else { } else {
var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint; serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint;
} }
var response;
try { try {
var getOptions = { var fetchOptions = {
headers: { method: 'GET',
"User-Agent": userAgent, headers: new Headers({
"Authorization": "Bearer " + accessToken 'User-Agent': userAgent,
} 'Authorization': 'Bearer ' + accessToken
}; })
};
if (httpCa) { 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) { } catch (err) {
throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message), throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message),
{response: err.response}); {response: err.response});
} }
if (debug) console.log('XXX: getUserInfo response: ', response.data);
return response.data;
}; };
var getConfiguration = function () { var getConfiguration = async function () {
var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' }); var config = await ServiceConfiguration.configurations.findOneAsync({ service: 'oidc' });
if (!config) { if (!config) {
throw new ServiceConfiguration.ConfigError('Service oidc not configured.'); throw new ServiceConfiguration.ConfigError('Service oidc not configured.');
} }

View file

@ -8,7 +8,7 @@ Package.describe({
Package.onUse(function(api) { Package.onUse(function(api) {
api.use('oauth2', ['client', 'server']); api.use('oauth2', ['client', 'server']);
api.use('oauth', ['client', 'server']); api.use('oauth', ['client', 'server']);
api.use('http', ['server']); api.use('fetch', ['server']);
api.use('underscore', 'client'); api.use('underscore', 'client');
api.use('ecmascript'); api.use('ecmascript');
api.use('templating', 'client'); api.use('templating', 'client');