diff --git a/.eslintrc b/.eslintrc index 88558a63b..0524aabd3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,7 +15,9 @@ "moment": true, "_": true, "Humanize": true, - "GTM": true + "GTM": true, + "$": true, + "KeyboardEvent": true }, "plugins": ["html-erb"], "overrides": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a0446987..e8f0939df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## next deploy +- No longer needed to recompile the assets when switching the authentication provider - Updated the documentation about the minimum docker version - Updated nodejs version to 16.13.2 for dev environment, to reflect production version - Changed the apparence of the modal dialogs (React): no more logo and the close button appears in full-text in the top right corner. diff --git a/app/frontend/src/javascript/controllers/application.js.erb b/app/frontend/src/javascript/controllers/application.js similarity index 56% rename from app/frontend/src/javascript/controllers/application.js.erb rename to app/frontend/src/javascript/controllers/application.js index c78eda622..4179ae26b 100644 --- a/app/frontend/src/javascript/controllers/application.js.erb +++ b/app/frontend/src/javascript/controllers/application.js @@ -1,16 +1,3 @@ -/* eslint-disable - handle-callback-err, - no-return-assign, - no-undef, - standard/no-callback-literal, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ Application.Controllers.controller('ApplicationController', ['$rootScope', '$scope', '$transitions', '$window', '$locale', '$timeout', 'Session', 'AuthService', 'Auth', '$uibModal', '$state', 'growl', 'Notification', '$interval', 'Setting', '_t', 'Version', 'Help', function ($rootScope, $scope, $transitions, $window, $locale, $timeout, Session, AuthService, Auth, $uibModal, $state, growl, Notification, $interval, Setting, _t, Version, Help) { @@ -40,7 +27,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco // Fab-manager's app-version if (user.role === 'admin') { // get the version - $scope.version = Version.get({origin: window.location.origin}); + $scope.version = Version.get({ origin: window.location.origin }); } else { $scope.version = { current: '' }; } @@ -83,111 +70,116 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco */ $scope.signup = function (e) { if (e) { e.preventDefault(); } - <% active_provider = AuthProvider.active %> - <% if active_provider.providable_type != DatabaseProvider.name %> - $window.location.href = '/sso-redirect'; - <% else %> - - return $uibModal.open({ - templateUrl: '/shared/signupModal.html', - size: 'md', - resolve: { - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'recaptcha_site_key', 'confirmation_required', 'address_required']" }).$promise; }] - }, - controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', 'settingsPromise', 'growl', '_t', function ($scope, $uibModalInstance, Group, CustomAsset, settingsPromise, growl, _t) { - // default parameters for the date picker in the account creation modal - $scope.datePicker = { - format: Fablab.uibDateFormat, - opened: false, - options: { - startingDay: Fablab.weekStartingDay, - maxDate: new Date() - } - }; - - // is the phone number required to sign-up? - $scope.phoneRequired = (settingsPromise.phone_required === 'true'); - - // is the address required to sign-up? - $scope.addressRequired = (settingsPromise.address_required === 'true'); - - // reCaptcha v2 site key (or undefined) - $scope.recaptchaSiteKey = settingsPromise.recaptcha_site_key; - - // callback to open the date picker (account creation modal) - $scope.openDatePicker = function ($event) { - $event.preventDefault(); - $event.stopPropagation(); - return $scope.datePicker.opened = true; - }; - - // retrieve the groups (standard, student ...) - Group.query(function (groups) { - $scope.groups = groups; - $scope.enabledGroups = groups.filter(function (g) { return (g.slug !== 'admins') && !g.disabled; }); - }); - - // retrieve the CGU - CustomAsset.get({ name: 'cgu-file' }, function (cgu) { - $scope.cgu = cgu.custom_asset; - }); - - // default user's parameters - $scope.user = { - is_allow_contact: true, - is_allow_newsletter: false, - // reCaptcha response, received from Google (through AJAX) and sent to server for validation - recaptcha: undefined - }; - - // Errors display - $scope.alerts = []; - $scope.closeAlert = function (index) { - $scope.alerts.splice(index, 1); - }; - - // callback for form validation - $scope.ok = function () { - // try to create the account - $scope.alerts = []; - // remove 'organization' attribute - const orga = $scope.user.organization; - delete $scope.user.organization; - // register on server - return Auth.register($scope.user).then(function (user) { - if (user.id) { - // creation successful - $uibModalInstance.close({ user, settings: settingsPromise }); - } else { - // the user was not saved in database, something wrong occurred - growl.error(_t('app.public.common.unexpected_error_occurred')); + if (Fablab.activeProviderType !== 'DatabaseProvider') { + $window.location.href = '/sso-redirect'; + } else { + return $uibModal.open({ + templateUrl: '/shared/signupModal.html', + size: 'md', + resolve: { + settingsPromise: ['Setting', function (Setting) { + return Setting.query({ names: "['phone_required', 'recaptcha_site_key', 'confirmation_required', 'address_required']" }).$promise; + }] + }, + controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', 'settingsPromise', 'growl', '_t', function ($scope, $uibModalInstance, Group, CustomAsset, settingsPromise, growl, _t) { + // default parameters for the date picker in the account creation modal + $scope.datePicker = { + format: Fablab.uibDateFormat, + opened: false, + options: { + startingDay: Fablab.weekStartingDay, + maxDate: new Date() } - }, function (error) { - // creation failed... - // restore organization param - $scope.user.organization = orga; - // display errors - angular.forEach(error.data.errors, function (v, k) { - angular.forEach(v, function (err) { - $scope.alerts.push({ - msg: k + ': ' + err, - type: 'danger' + }; + + // is the phone number required to sign-up? + $scope.phoneRequired = (settingsPromise.phone_required === 'true'); + + // is the address required to sign-up? + $scope.addressRequired = (settingsPromise.address_required === 'true'); + + // reCaptcha v2 site key (or undefined) + $scope.recaptchaSiteKey = settingsPromise.recaptcha_site_key; + + // callback to open the date picker (account creation modal) + $scope.openDatePicker = function ($event) { + $event.preventDefault(); + $event.stopPropagation(); + $scope.datePicker.opened = true; + }; + + // retrieve the groups (standard, student ...) + Group.query(function (groups) { + $scope.groups = groups; + $scope.enabledGroups = groups.filter(function (g) { + return (g.slug !== 'admins') && !g.disabled; + }); + }); + + // retrieve the CGU + CustomAsset.get({ name: 'cgu-file' }, function (cgu) { + $scope.cgu = cgu.custom_asset; + }); + + // default user's parameters + $scope.user = { + is_allow_contact: true, + is_allow_newsletter: false, + // reCaptcha response, received from Google (through AJAX) and sent to server for validation + recaptcha: undefined + }; + + // Errors display + $scope.alerts = []; + $scope.closeAlert = function (index) { + $scope.alerts.splice(index, 1); + }; + + // callback for form validation + $scope.ok = function () { + // try to create the account + $scope.alerts = []; + // remove 'organization' attribute + const orga = $scope.user.organization; + delete $scope.user.organization; + // register on server + return Auth.register($scope.user).then(function (user) { + if (user.id) { + // creation successful + $uibModalInstance.close({ + user, + settings: settingsPromise + }); + } else { + // the user was not saved in database, something wrong occurred + growl.error(_t('app.public.common.unexpected_error_occurred')); + } + }, function (error) { + // creation failed... + // restore organization param + $scope.user.organization = orga; + // display errors + angular.forEach(error.data.errors, function (v, k) { + angular.forEach(v, function (err) { + $scope.alerts.push({ + msg: k + ': ' + err, + type: 'danger' + }); }); }); }); - }); - }; - }] - }).result['finally'](null).then(function (res) { - // when the account was created successfully, set the session to the newly created account - if(res.settings.confirmation_required === 'true') { - Auth._currentUser = null; - growl.info(_t('app.public.common.you_will_receive_confirmation_instructions_by_email_detailed')); - } else { - $scope.setCurrentUser(res.user); - } - }); - <% end %> + }; + }] + }).result.finally(null).then(function (res) { + // when the account was created successfully, set the session to the newly created account + if (res.settings.confirmation_required === 'true') { + Auth._currentUser = null; + growl.info(_t('app.public.common.you_will_receive_confirmation_instructions_by_email_detailed')); + } else { + $scope.setCurrentUser(res.user); + } + }); + } }; /** @@ -205,7 +197,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco $scope.alerts.splice(index, 1); }; - return $scope.changePassword = function () { + $scope.changePassword = function () { $scope.alerts = []; return $http.put('/users/password.json', { user: $scope.user }).then(function () { $uibModalInstance.close(); }).catch(function (data) { angular.forEach(data.data.errors, function (v, k) { @@ -219,7 +211,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco }); }; }] - }).result['finally'](null).then(function () { + }).result.finally(null).then(function () { growl.success(_t('app.public.common.your_password_was_successfully_changed')); return Auth.login().then(function (user) { $scope.setCurrentUser(user); @@ -244,7 +236,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco let toggler = $(event.target); if (!toggler.data('toggle')) { toggler = toggler.closest('[data-toggle^="class"]'); } - const $class = toggler.data()['toggle']; + const $class = toggler.data().toggle; const $target = toggler.data('target') || toggler.attr('data-link'); if ($class) { @@ -267,7 +259,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco while (patt.test(cn)) { cn = cn.replace(patt, ' '); } - return it.className = $.trim(cn); + it.className = $.trim(cn); }); } @@ -281,7 +273,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco /** * Open the modal dialog showing that an upgrade is available */ - $scope.versionModal = function() { + $scope.versionModal = function () { if ($scope.version.up_to_date) return; if ($rootScope.currentUser.role !== 'admin') return; @@ -289,10 +281,10 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco templateUrl: '/admin/versions/upgradeModal.html', controller: 'VersionModalController', resolve: { - version() { return $scope.version; } + version () { return $scope.version; } } }); - } + }; /** * Trigger the contextual help "feature tour". @@ -303,10 +295,10 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco // we wrap the event triggering into a $timeout to prevent conflicting with current $apply $timeout(function () { - var evt = new KeyboardEvent('keydown', { key: 'F1' }); + const evt = new KeyboardEvent('keydown', { key: 'F1' }); window.dispatchEvent(evt); }); - } + }; /* PRIVATE SCOPE */ /** @@ -405,116 +397,126 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco * Open the modal window allowing the user to log in. */ const openLoginModal = function (toState, toParams, callback) { - <% active_provider = AuthProvider.active %> - <% if active_provider.providable_type != DatabaseProvider.name %> - $window.location.href = '/sso-redirect'; - <% else %> - return $uibModal.open({ - templateUrl: '/shared/deviseModal.html', - backdrop: 'static', - size: 'sm', - resolve: { - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['confirmation_required']" }).$promise; }] - }, - controller: ['$scope', '$uibModalInstance', '_t', 'settingsPromise', function ($scope, $uibModalInstance, _t, settingsPromise) { - const user = ($scope.user = {}); + if (Fablab.activeProviderType !== 'DatabaseProvider') { + $window.location.href = '/sso-redirect'; + } else { + return $uibModal.open({ + templateUrl: '/shared/deviseModal.html', + backdrop: 'static', + size: 'sm', + resolve: { + settingsPromise: ['Setting', function (Setting) { + return Setting.query({ names: "['confirmation_required']" }).$promise; + }] + }, + controller: ['$scope', '$uibModalInstance', '_t', 'settingsPromise', function ($scope, $uibModalInstance, _t, settingsPromise) { + const user = ($scope.user = {}); - // email confirmation required before user sign-in? - $scope.confirmationRequired = settingsPromise.confirmation_required; + // email confirmation required before user sign-in? + $scope.confirmationRequired = settingsPromise.confirmation_required; - $scope.login = function () { - Auth.login(user).then(function (user) { - // Authentication succeeded ... - $uibModalInstance.close(user); - if (callback && (typeof callback === 'function')) { - return callback(user); + $scope.login = function () { + Auth.login(user).then(function (user) { + // Authentication succeeded ... + $uibModalInstance.close(user); + if (callback && (typeof callback === 'function')) { + return callback(user); + } } - } - , function (error) { - console.error(`Authentication failed: ${JSON.stringify(error)}`); - $scope.alerts = []; - return $scope.alerts.push({ - msg: error.data.error, - type: 'danger' + , function (error) { + console.error(`Authentication failed: ${JSON.stringify(error)}`); + $scope.alerts = []; + return $scope.alerts.push({ + msg: error.data.error, + type: 'danger' + }); }); + }; + // handle modal behaviors. The provided reason will be used to define the following actions + $scope.dismiss = function () { + $uibModalInstance.dismiss('cancel'); + }; + + $scope.openSignup = function (e) { + e.preventDefault(); + return $uibModalInstance.dismiss('signup'); + }; + + $scope.openConfirmationNewModal = function (e) { + e.preventDefault(); + return $uibModalInstance.dismiss('confirmationNew'); + }; + + $scope.openResetPassword = function (e) { + e.preventDefault(); + return $uibModalInstance.dismiss('resetPassword'); + }; + }] + }).result.finally(null).then(function (user) { + // what to do when the modal is closed + + // authentication succeeded, set the session, gather the notifications and redirect + GTM.trackLogin(); + $scope.setCurrentUser(user); + + if ((toState !== null) && (toParams !== null)) { + return $state.go(toState, toParams); + } + }, function (reason) { + // authentication did not end successfully + if (reason === 'signup') { + // open sign-up modal + $scope.signup(); + } else if (reason === 'resetPassword') { + // open the 'reset password' modal + return $uibModal.open({ + templateUrl: '/shared/passwordNewModal.html', + size: 'sm', + controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { + $scope.user = { email: '' }; + $scope.sendReset = function () { + $scope.alerts = []; + return $http.post('/users/password.json', { user: $scope.user }).then(function () { + $uibModalInstance.close(); + }).catch(function () { + $scope.alerts.push({ + msg: _t('app.public.common.your_email_address_is_unknown'), + type: 'danger' + }); + }); + }; + }] + }).result.finally(null).then(function () { + growl.info(_t('app.public.common.you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password')); }); - }; - // handle modal behaviors. The provided reason will be used to define the following actions - $scope.dismiss = function () { $uibModalInstance.dismiss('cancel'); }; - - $scope.openSignup = function (e) { - e.preventDefault(); - return $uibModalInstance.dismiss('signup'); - }; - - $scope.openConfirmationNewModal = function(e) { - e.preventDefault(); - return $uibModalInstance.dismiss('confirmationNew'); - }; - - $scope.openResetPassword = function (e) { - e.preventDefault(); - return $uibModalInstance.dismiss('resetPassword'); - }; - }] - }).result['finally'](null).then(function (user) { - // what to do when the modal is closed - - // authentication succeeded, set the session, gather the notifications and redirect - GTM.trackLogin(); - $scope.setCurrentUser(user); - - if ((toState !== null) && (toParams !== null)) { - return $state.go(toState, toParams); - } - }, function (reason) { - // authentication did not end successfully - if (reason === 'signup') { - // open sign-up modal - $scope.signup(); - } else if (reason === 'resetPassword') { - // open the 'reset password' modal - return $uibModal.open({ - templateUrl: '/shared/passwordNewModal.html', - size: 'sm', - controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { - $scope.user = { email: '' }; - return $scope.sendReset = function () { - $scope.alerts = []; - return $http.post('/users/password.json', { user: $scope.user }).then(function () { $uibModalInstance.close(); }).catch(function () { - $scope.alerts.push({ - msg: _t('app.public.common.your_email_address_is_unknown'), - type: 'danger' + } else if (reason === 'confirmationNew') { + // open the 'reset password' modal + return $uibModal.open({ + templateUrl: '/shared/ConfirmationNewModal.html', + size: 'sm', + controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { + $scope.user = { email: '' }; + $scope.submitConfirmationNewForm = function () { + $scope.alerts = []; + return $http.post('/users/confirmation.json', { user: $scope.user }).then(function () { + $uibModalInstance.close(); + }).catch(function (res) { + $scope.alerts.push({ + msg: res.data.errors.email[0], + type: 'danger' + }); }); - }); - }; - }] - }).result['finally'](null).then(function () { growl.info(_t('app.public.common.you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password')); }); - } else if (reason === 'confirmationNew') { - // open the 'reset password' modal - return $uibModal.open({ - templateUrl: '/shared/ConfirmationNewModal.html', - size: 'sm', - controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { - $scope.user = { email: '' }; - return $scope.submitConfirmationNewForm = function () { - $scope.alerts = []; - return $http.post('/users/confirmation.json', { user: $scope.user }).then(function () { $uibModalInstance.close(); }).catch(function (res) { - $scope.alerts.push({ - msg: res.data.errors.email[0], - type: 'danger' - }); - }); - }; - }] - }).result['finally'](null).then(function () { growl.info(_t('app.public.common.you_will_receive_confirmation_instructions_by_email_detailed')); }); - } - }); - // otherwise the user just closed the modal - <% end %> + }; + }] + }).result.finally(null).then(function () { + growl.info(_t('app.public.common.you_will_receive_confirmation_instructions_by_email_detailed')); + }); + } + }); + // otherwise the user just closed the modal + } }; - /** * Detect if the current page (tab/window) is active of put as background. * When the status changes, the callback is triggered with the new status as parameter @@ -538,7 +540,8 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco if (evt.type in evtMap) { if (typeof callback === 'function') { callback(evtMap[evt.type]); } } else { - if (typeof callback === 'function') { callback(this[hidden] ? 'hidden' : 'visible'); } + const visibility = this[hidden] ? 'hidden' : 'visible'; + if (typeof callback === 'function') { callback(visibility); } } }; @@ -579,5 +582,5 @@ Application.Controllers.controller('VersionModalController', ['$scope', '$uibMod // callback to close the modal $scope.close = function () { $uibModalInstance.dismiss(); - } + }; }]); diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index 7b4789c0b..58fea3bfa 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -41,6 +41,7 @@ Fablab.defaultHost = "<%= Rails.application.secrets.default_host %>"; Fablab.trackingId = "<%= Setting.get('tracking_id') %>"; Fablab.adminSysId = parseInt("<%= User.adminsys&.id %>", 10); + Fablab.activeProviderType = "<%= AuthProvider.active&.providable_type %>"; // i18n stuff Fablab.locale = "<%= Rails.application.secrets.app_locale %>"; diff --git a/lib/tasks/fablab/auth.rake b/lib/tasks/fablab/auth.rake index 7d2c1d033..437025dfd 100644 --- a/lib/tasks/fablab/auth.rake +++ b/lib/tasks/fablab/auth.rake @@ -6,20 +6,27 @@ namespace :fablab do desc 'switch the active authentication provider' task :switch_provider, [:provider] => :environment do |_task, args| - raise 'FATAL ERROR: You must pass a provider name to activate' unless args.provider + unless args.provider + puts "\e[0;31mERROR\e[0m: You must pass a provider name to activate" + next + end if AuthProvider.find_by(name: args.provider).nil? providers = AuthProvider.all.inject('') { |str, item| str + item[:name] + ', ' } - raise "FATAL ERROR: the provider '#{args.provider}' does not exists. Available providers are: #{providers[0..-3]}" + puts "\e[0;31mERROR\e[0m: the provider '#{args.provider}' does not exists. Available providers are: #{providers[0..-3]}" + next end - raise "FATAL ERROR: the provider '#{args.provider}' is already enabled" if AuthProvider.active.name == args.provider + if AuthProvider.active.name == args.provider + puts "\e[0;31mERROR\e[0m: the provider '#{args.provider}' is already enabled" + next + end # disable previous provider prev_prev = AuthProvider.previous prev_prev&.update_attribute(:status, 'pending') - AuthProvider.active.update_attribute(:status, 'previous') + AuthProvider.active.update_attribute(:status, 'previous') unless AuthProvider.active.name == 'DatabaseProvider::SimpleAuthProvider' # enable given provider AuthProvider.find_by(name: args.provider).update_attribute(:status, 'active') @@ -38,14 +45,11 @@ namespace :fablab do # ask the user to restart the application next if Rails.env.test? - puts "\nActivation successful" - - puts "\n/!\\ WARNING: Please consider the following, otherwise the authentication will be bogus:" - puts "\t1) CLEAN the cache with `rails tmp:clear`" - puts "\t2) REBUILD the assets with `rails assets:precompile`" - puts "\t3) RESTART the application" - puts "\t4) NOTIFY the current users with `rails fablab:auth:notify_changed`\n\n" + puts "\n\e[0;32m#{args.provider} successfully enabled\e[0m" + puts "\n\e[0;33m⚠ WARNING\e[0m: Please consider the following, otherwise the authentication will be bogus:" + puts "\t1) RESTART the application" + puts "\t2) NOTIFY the current users with `rails fablab:auth:notify_changed`\n\n" end desc 'notify users that the auth provider has changed' @@ -64,5 +68,10 @@ namespace :fablab do puts "\nUsers successfully notified\n\n" end + + desc 'display the current active authentication provider' + task current: :environment do + puts "Current active authentication provider: #{AuthProvider.active.name}" + end end end