Merge pull request #4588 from Viehlieb/feature/direct_login_from_oidc

Feature/direct login from OIDC
pull/4599/head
Lauri Ojansivu 3 years ago committed by GitHub
commit 24598eaf00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      CHANGELOG.md
  2. 63
      client/components/main/layouts.js
  3. 40
      config/accounts.js
  4. 18
      models/settings.js
  5. 4
      packages/wekan-accounts-oidc/oidc.js
  6. 24
      packages/wekan-oidc/README.md
  7. 93
      packages/wekan-oidc/oidc_client.js
  8. 1
      packages/wekan-oidc/oidc_server.js
  9. 2
      server/authentication.js

@ -935,7 +935,7 @@ This release removes the following new features:
- [Revert change from WeKan v5.81: At Sandstorm, every WeKan user is now WeKan Admin and has Admin Panel](https://github.com/wekan/wekan/commit/ebc7741fcb9ad854234921ed0546255411adeec9).
Thanks to ocdtrekkie and xet7.
and adds the following new features:
- [List header contains now a button to add the card to the bottom of the list](https://github.com/wekan/wekan/pull/4195).
@ -1187,7 +1187,7 @@ and fixes the following bugs:
Thanks to niklasdahlheimer.
- [Popup fixes: Archive cards, upload attachements etc](https://github.com/wekan/wekan/pull/4101).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.68 2021-10-27 WeKan ® release
@ -2158,7 +2158,7 @@ This release adds the following new features:
- [Custom Field "String Template"](https://github.com/wekan/wekan/pull/3701).
Thanks to tod31.
- [1) Admin reports. An option added to the admin panel that has reports an admin can run.
- [1) Admin reports. An option added to the admin panel that has reports an admin can run.
Right now it has two reports for attachments and broken cards.
2) Add the creator avatar to `cardDetails` and `minicard`. Avatar is only shown if it is selected in card settings.
3) Added a new search operator `creator`.
@ -2891,7 +2891,7 @@ and fixes the following bugs:
Thanks to xet7.
- [Rules not copied during board copy](https://github.com/wekan/wekan/pull/3458).
Thanks to jrsupplee.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.83 2021-01-20 Wekan release
@ -2912,7 +2912,7 @@ and fixes the following bugs:
Thanks to jrsupplee.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.82 2021-01-20 Wekan release
This release adds the following new features:

@ -4,7 +4,7 @@ BlazeLayout.setRoot('body');
let alreadyCheck = 1;
let isCheckDone = false;
let counter = 0;
const validator = {
set(obj, prop, value) {
if (prop === 'state' && value !== 'signIn') {
@ -21,7 +21,7 @@ const validator = {
// let isSettingDatabaseFctCallDone = false;
Template.userFormsLayout.onCreated(function() {
Template.userFormsLayout.onCreated(function () {
const templateInstance = this;
templateInstance.currentSetting = new ReactiveVar();
templateInstance.isLoading = new ReactiveVar(false);
@ -37,7 +37,7 @@ Template.userFormsLayout.onCreated(function() {
}
// isSettingDatabaseFctCallDone = true;
if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined)
if (currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined)
document.getElementById("isSettingDatabaseCallDone").style.display = 'none';
else
document.getElementById("isSettingDatabaseCallDone").style.display = 'block';
@ -50,6 +50,18 @@ Template.userFormsLayout.onCreated(function() {
}
});
if (!Meteor.user()?.profile) {
Meteor.call('isOidcRedirectionEnabled', (_, result) => {
if (result) {
AccountsTemplates.options.socialLoginStyle = 'redirect';
options = {
loginStyle: AccountsTemplates.options.socialLoginStyle,
};
Meteor.loginWithOidc(options);
}
else console.log("oidc redirect not set");
});
}
Meteor.call('isDisableRegistration', (_, result) => {
if (result) {
$('.at-signup-link').hide();
@ -81,22 +93,22 @@ Template.userFormsLayout.helpers({
// return isSettingDatabaseFctCallDone;
// },
isLegalNoticeLinkExist(){
isLegalNoticeLinkExist() {
const currSet = Template.instance().currentSetting.get();
if(currSet && currSet !== undefined && currSet != null){
if (currSet && currSet !== undefined && currSet != null) {
return currSet.legalNotice !== undefined && currSet.legalNotice.trim() != "";
}
else
return false;
},
getLegalNoticeWithWritTraduction(){
getLegalNoticeWithWritTraduction() {
let spanLegalNoticeElt = $("#legalNoticeSpan");
if(spanLegalNoticeElt != null && spanLegalNoticeElt != undefined){
if (spanLegalNoticeElt != null && spanLegalNoticeElt != undefined) {
spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {}));
}
let atLinkLegalNoticeElt = $("#legalNoticeAtLink");
if(atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined){
if (atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined) {
atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {}));
}
return true;
@ -147,41 +159,41 @@ Template.userFormsLayout.events({
}
isCheckDone = false;
},
'click #at-signUp'(event, templateInstance){
'click #at-signUp'(event, templateInstance) {
isCheckDone = false;
},
'DOMSubtreeModified #at-oidc'(event){
if(alreadyCheck <= 2){
'DOMSubtreeModified #at-oidc'(event) {
if (alreadyCheck <= 2) {
let currSetting = Settings.findOne();
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) {
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
if(alreadyCheck == 1){
if (alreadyCheck == 1) {
alreadyCheck++;
oidcBtnElt.html("");
}
else{
else {
alreadyCheck++;
oidcBtnElt.html(htmlvalue);
}
}
}
else{
else {
alreadyCheck = 1;
}
},
'DOMSubtreeModified .at-form'(event){
if(alreadyCheck <= 2 && !isCheckDone){
if(document.getElementById("at-oidc") != null){
'DOMSubtreeModified .at-form'(event) {
if (alreadyCheck <= 2 && !isCheckDone) {
if (document.getElementById("at-oidc") != null) {
let currSetting = Settings.findOne();
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) {
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
if(alreadyCheck == 1){
if (alreadyCheck == 1) {
alreadyCheck++;
oidcBtnElt.html("");
}
else{
else {
alreadyCheck++;
isCheckDone = true;
oidcBtnElt.html(htmlvalue);
@ -189,7 +201,7 @@ Template.userFormsLayout.events({
}
}
}
else{
else {
alreadyCheck = 1;
}
},
@ -221,7 +233,7 @@ async function authentication(event, templateInstance) {
switch (result) {
case 'ldap':
return new Promise(resolve => {
Meteor.loginWithLDAP(match, password, function() {
Meteor.loginWithLDAP(match, password, function () {
resolve(FlowRouter.go('/'));
});
});
@ -233,7 +245,7 @@ async function authentication(event, templateInstance) {
{
provider,
},
function() {
function () {
resolve(FlowRouter.go('/'));
},
);
@ -241,7 +253,7 @@ async function authentication(event, templateInstance) {
case 'cas':
return new Promise(resolve => {
Meteor.loginWithCas(match, password, function() {
Meteor.loginWithCas(match, password, function () {
resolve(FlowRouter.go('/'));
});
});
@ -267,7 +279,6 @@ function getUserAuthenticationMethod(defaultAuthenticationMethod, match) {
Meteor.subscribe('user-authenticationMethod', match, {
onReady() {
const user = Users.findOne();
const authenticationMethod = user
? user.authenticationMethod
: defaultAuthenticationMethod;

@ -5,6 +5,16 @@ const emailField = AccountsTemplates.removeField('email');
let disableRegistration = false;
let disableForgotPassword = false;
let passwordLoginDisabled = false;
let oidcRedirectionEnabled = false;
let oauthServerUrl = "home";
let oauthDashboardUrl = "";
Meteor.call('isOidcRedirectionEnabled', (_, result) => {
if(result)
{
oidcRedirectionEnabled = true;
}
});
Meteor.call('isPasswordLoginDisabled', (_, result) => {
if (result) {
@ -14,6 +24,18 @@ Meteor.call('isPasswordLoginDisabled', (_, result) => {
}
});
Meteor.call('getOauthServerUrl', (_, result) => {
if (result) {
oauthServerUrl = result;
}
});
Meteor.call('getOauthDashboardUrl', (_, result) => {
if (result) {
oauthDashboardUrl = result;
}
});
Meteor.call('isDisableRegistration', (_, result) => {
if (result) {
disableRegistration = true;
@ -59,11 +81,19 @@ AccountsTemplates.configure({
showForgotPasswordLink: !disableForgotPassword,
forbidClientAccountCreation: disableRegistration,
onLogoutHook() {
const homePage = 'home';
if (FlowRouter.getRouteName() === homePage) {
FlowRouter.reload();
} else {
FlowRouter.go(homePage);
// here comeslogic for redirect
if(oidcRedirectionEnabled)
{
window.location = oauthServerUrl + oauthDashboardUrl;
}
else
{
const homePage = 'home';
if (FlowRouter.getRouteName() === homePage) {
FlowRouter.reload();
} else {
FlowRouter.go(homePage);
}
}
},
});

@ -229,6 +229,12 @@ if (Meteor.isServer) {
]);
}
function loadOidcConfig(service){
check(service, String);
var config = ServiceConfiguration.configurations.findOne({service: service});
return config;
}
function sendInvitationEmail(_id) {
const icode = InvitationCodes.findOne(_id);
const author = Users.findOne(Meteor.userId());
@ -495,6 +501,12 @@ if (Meteor.isServer) {
};
},
getOauthServerUrl(){
return process.env.OAUTH2_SERVER_URL;
},
getOauthDashboardUrl(){
return process.env.DASHBOARD_URL;
},
getDefaultAuthenticationMethod() {
return process.env.DEFAULT_AUTHENTICATION_METHOD;
},
@ -502,6 +514,12 @@ if (Meteor.isServer) {
isPasswordLoginDisabled() {
return process.env.PASSWORD_LOGIN_ENABLED === 'false';
},
isOidcRedirectionEnabled(){
return process.env.OIDC_REDIRECTION_ENABLED === 'true' && Object.keys(loadOidcConfig("oidc")).length > 0;
},
getServiceConfiguration(service){
return loadOidcConfig(service);
}
});
}

@ -7,11 +7,11 @@ if (Meteor.isClient) {
callback = options;
options = null;
}
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
Oidc.requestCredential(options, credentialRequestCompleteCallback);
};
} else {
}
else {
Accounts.addAutopublishFields({
// not sure whether the OIDC api can be used from the browser,
// thus not sure if we should be sending access tokens; but we do it

@ -47,6 +47,26 @@ See example below:
NOTE: orgs & teams won't be updated if they already exist.
5. Manages admin rights as well. If user is in Group which has isAdmin: set to true, user will get admin
privileges in Wekan as well.
5. Manages admin rights as well. If user is in Group which has isAdmin: set to true, user will get admin
privileges in Wekan as well.
If no adjustments (e.g. 1-3) are made on oidc provider's side, user will receive his/her admin rights from before.
## For further empowerment of oidc as sso solution
If you want to be redirected to your oidc provider on LOGIN without going the extra loop of signing in.
On LOGOUT you will be redirected to the oidc provider as well.
Add to your .env file:
OIDC_REDIRECTION_ENABLED=true
OAUTH2_SERVER_URL=http://localhost:9000
DASHBOARD_URL=/if/session-end/wekan/
Example for authentik.
The latter specifies the OIDC Dashboard you'll get redirected on logout
Flow:
You need to have an oidc provider configured to get this feature
Make sure to have
Authorize Application (default-provider-authorization-implicit-consent)
enabled

@ -12,56 +12,61 @@ Oidc.requestCredential = function (options, credentialRequestCompleteCallback) {
options = {};
}
var config = ServiceConfiguration.configurations.findOne({service: 'oidc'});
if (!config) {
credentialRequestCompleteCallback && credentialRequestCompleteCallback(
new ServiceConfiguration.ConfigError('Service oidc not configured.'));
return;
}
Meteor.call("getServiceConfiguration", "oidc",(_, result) => {
if (result) {
var config = result;
var credentialToken = Random.secret();
var loginStyle = OAuth._loginStyle('oidc', config, options);
// options
options = options || {};
options.client_id = config.clientId;
options.response_type = options.response_type || 'code';
options.redirect_uri = OAuth._redirectUri('oidc', config);
options.state = OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl);
options.scope = config.requestPermissions || 'openid profile email';
var credentialToken = Random.secret();
var loginStyle = OAuth._loginStyle('oidc', config, options);
if (config.loginStyle && config.loginStyle == 'popup') {
options.display = 'popup';
}
// options
options = options || {};
options.client_id = config.clientId;
options.response_type = options.response_type || 'code';
options.redirect_uri = OAuth._redirectUri('oidc', config);
options.state = OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl);
options.scope = config.requestPermissions || 'openid profile email';
var loginUrl = config.serverUrl + config.authorizationEndpoint;
// check if the loginUrl already contains a "?"
var first = loginUrl.indexOf('?') === -1;
for (var k in options) {
if (first) {
loginUrl += '?';
first = false;
}
else {
loginUrl += '&'
}
loginUrl += encodeURIComponent(k) + '=' + encodeURIComponent(options[k]);
}
if (config.loginStyle && config.loginStyle == 'popup') {
options.display = 'popup';
}
//console.log('XXX: loginURL: ' + loginUrl)
var loginUrl = config.serverUrl + config.authorizationEndpoint;
// check if the loginUrl already contains a "?"
var first = loginUrl.indexOf('?') === -1;
for (var k in options) {
if (first) {
loginUrl += '?';
first = false;
options.popupOptions = options.popupOptions || {};
var popupOptions = {
width: options.popupOptions.width || 320,
height: options.popupOptions.height || 450
};
OAuth.launchLogin({
loginService: 'oidc',
loginStyle: loginStyle,
loginUrl: loginUrl,
credentialRequestCompleteCallback: credentialRequestCompleteCallback,
credentialToken: credentialToken,
popupOptions: popupOptions,
});
}
else {
loginUrl += '&'
else
{
credentialRequestCompleteCallback && credentialRequestCompleteCallback(
new ServiceConfiguration.ConfigError('Service oidc not configured.'));
return;
}
loginUrl += encodeURIComponent(k) + '=' + encodeURIComponent(options[k]);
}
//console.log('XXX: loginURL: ' + loginUrl)
});
options.popupOptions = options.popupOptions || {};
var popupOptions = {
width: options.popupOptions.width || 320,
height: options.popupOptions.height || 450
};
OAuth.launchLogin({
loginService: 'oidc',
loginStyle: loginStyle,
loginUrl: loginUrl,
credentialRequestCompleteCallback: credentialRequestCompleteCallback,
credentialToken: credentialToken,
popupOptions: popupOptions,
});
};

@ -19,7 +19,6 @@ var serviceData = {};
var userinfo = {};
OAuth.registerService('oidc', 2, null, function (query) {
var debug = process.env.DEBUG || false;
var token = getToken(query);

@ -108,7 +108,7 @@ Meteor.startup(() => {
// OAUTH2_ID_TOKEN_WHITELIST_FIELDS || [],
// OAUTH2_REQUEST_PERMISSIONS || 'openid profile email',
},
);
);
} else if (
process.env.CAS_ENABLED === 'true' ||
process.env.CAS_ENABLED === true

Loading…
Cancel
Save