diff --git a/config/default.schema.json b/config/default.schema.json index c3eeca73b4..a294dd051b 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -625,6 +625,11 @@ "type": "boolean", "default": false, "description": "Enables the rooms feature" + }, + "FEATURE_EXTERNAL_SYSTEM_LOGOUT_ENABLED": { + "type": "boolean", + "default": false, + "description": "Enables the external system logout feature" } }, "allOf": [ diff --git a/controllers/login.js b/controllers/login.js index 7e52b7f0f4..cf0c744b9d 100644 --- a/controllers/login.js +++ b/controllers/login.js @@ -484,4 +484,21 @@ router.get('/logout/', (req, res, next) => { .catch(next); }); +router.get('/logout/external/', async (req, res, next) => { + let redirectUri = '/logout/'; + if (Configuration.has('OAUTH2_LOGOUT_URI')) { + redirectUri = Configuration.get('OAUTH2_LOGOUT_URI'); + } + + if (res.locals.isExternalLogoutAllowed) { + try { + await api(req, { version: 'v3' }).post('/logout/external'); + } catch (err) { + logger.error('error during external logout.', formatError(err)); + } + } + + res.redirect(redirectUri); +}); + module.exports = router; diff --git a/helpers/authentication.js b/helpers/authentication.js index 3e0b02e32b..854287989a 100644 --- a/helpers/authentication.js +++ b/helpers/authentication.js @@ -64,7 +64,7 @@ const clearCookie = async (req, res, options = { destroySession: false }) => { }); }); } - + res.clearCookie('jwt'); // this is deprecated and only used for cookie removal from now on, // and can be removed after one month (max cookie lifetime from life systems) @@ -101,6 +101,26 @@ const isAuthenticated = (req) => { }; const populateCurrentUser = async (req, res) => { + async function setExternalSystemFromJwt(decodedJwt) { + if (!('systemId' in decodedJwt) && !decodedJwt.systemId) { + return; + } + + try { + const response = await api(req, { version: 'v3' }).get(`/systems/public/${decodedJwt.systemId}`); + const hasEndSessionEndpoint = 'oauthConfig' in response + && 'endSessionEndpoint' in response.oauthConfig + && response.oauthConfig.endSessionEndpoint; + + res.locals.isExternalLogoutAllowed = Configuration.get('FEATURE_EXTERNAL_SYSTEM_LOGOUT_ENABLED') + && hasEndSessionEndpoint; + res.locals.systemName = response.displayName; + } catch (err) { + const metadata = { error: err.toString() }; + logger.error('Unable to find out the external login system used by user', metadata); + } + } + let payload = {}; if (isJWT(req)) { try { @@ -129,6 +149,8 @@ const populateCurrentUser = async (req, res) => { } if (payload && payload.userId) { + await setExternalSystemFromJwt(payload); + if (res.locals.currentUser && res.locals.currentSchoolData) { return Promise.resolve(res.locals.currentSchoolData); } diff --git a/locales/de.json b/locales/de.json index c2e7be541a..22ae8f8630 100644 --- a/locales/de.json +++ b/locales/de.json @@ -3231,4 +3231,4 @@ "createAfterFirstSave": "H5P Inhalte können erst nach dem ersten Speichern erstellt werden." } } -} \ No newline at end of file +} diff --git a/locales/en.json b/locales/en.json index dd4761968e..459a04f9d8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -3231,4 +3231,4 @@ "createAfterFirstSave": "H5P contents can only be created after the first save." } } -} \ No newline at end of file +} diff --git a/locales/es.json b/locales/es.json index 102d999ef0..3c79f69d26 100644 --- a/locales/es.json +++ b/locales/es.json @@ -3231,4 +3231,4 @@ "createAfterFirstSave": "Los contenidos H5P solo se pueden crear después del primer guardado." } } -} \ No newline at end of file +} diff --git a/locales/uk.json b/locales/uk.json index 2fa7939b44..7cbab54ba3 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -3240,4 +3240,4 @@ "createAfterFirstSave": "Вміст H5P можна створити лише після першого збереження." } } -} \ No newline at end of file +} diff --git a/views/lib/topbar.hbs b/views/lib/topbar.hbs index bab2671335..33df1d2c38 100644 --- a/views/lib/topbar.hbs +++ b/views/lib/topbar.hbs @@ -59,7 +59,22 @@