diff --git a/client/components/common/nav-header.vue b/client/components/common/nav-header.vue index de23014d..740de13f 100644 --- a/client/components/common/nav-header.vue +++ b/client/components/common/nav-header.vue @@ -462,8 +462,7 @@ export default { } }, logout () { - Cookies.remove('jwt') - window.location.assign('/') + window.location.assign('/logout') }, goHome () { window.location.assign('/') diff --git a/server/controllers/auth.js b/server/controllers/auth.js index df204e7e..037652d4 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -109,10 +109,11 @@ router.post('/login', bruteforce.prevent, async (req, res, next) => { /** * Logout */ -router.get('/logout', function (req, res) { +router.get('/logout', async (req, res) => { + const redirURL = await WIKI.models.users.logout({ req, res }) req.logout() res.clearCookie('jwt') - res.redirect('/') + res.redirect(redirURL) }) /** diff --git a/server/models/users.js b/server/models/users.js index 411389f6..f86b51dc 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -271,6 +271,9 @@ module.exports = class User extends Model { throw new Error('You are not authorized to login.') } + /** + * Login a user + */ static async login (opts, context) { if (_.has(WIKI.auth.strategies, opts.strategy)) { const selStrategy = _.get(WIKI.auth.strategies, opts.strategy) @@ -307,6 +310,9 @@ module.exports = class User extends Model { } } + /** + * Perform post-login checks + */ static async afterLoginChecks (user, context, { skipTFA, skipChangePwd } = { skipTFA: false, skipChangePwd: false }) { // Get redirect target user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions', 'redirectOnLogin') @@ -380,6 +386,9 @@ module.exports = class User extends Model { }) } + /** + * Generate a new token for a user + */ static async refreshToken(user) { if (_.isSafeInteger(user)) { user = await WIKI.models.users.query().findById(user).withGraphFetched('groups').modifyGraph('groups', builder => { @@ -427,6 +436,9 @@ module.exports = class User extends Model { } } + /** + * Verify a TFA login + */ static async loginTFA ({ securityCode, continuationToken, setup }, context) { if (securityCode.length === 6 && continuationToken.length > 1) { const user = await WIKI.models.userKeys.validateToken({ @@ -819,6 +831,18 @@ module.exports = class User extends Model { } } + /** + * Logout the current user + */ + static async logout (context) { + if (!context.req.user || context.req.user.id === 2) { + return '/' + } + const usr = await WIKI.models.users.query().findById(context.req.user.id).select('providerKey') + const provider = _.find(WIKI.auth.strategies, ['key', usr.providerKey]) + return provider.logout ? provider.logout(provider.config) : '/' + } + static async getGuestUser () { const user = await WIKI.models.users.query().findById(2).withGraphJoined('groups').modifyGraph('groups', builder => { builder.select('groups.id', 'permissions') diff --git a/server/modules/authentication/google/authentication.js b/server/modules/authentication/google/authentication.js index b9ffdb41..23eb40af 100644 --- a/server/modules/authentication/google/authentication.js +++ b/server/modules/authentication/google/authentication.js @@ -30,5 +30,8 @@ module.exports = { } }) ) + }, + logout (conf) { + return '/' } } diff --git a/server/modules/authentication/keycloak/authentication.js b/server/modules/authentication/keycloak/authentication.js index 0eb79af1..782a62a1 100644 --- a/server/modules/authentication/keycloak/authentication.js +++ b/server/modules/authentication/keycloak/authentication.js @@ -42,5 +42,15 @@ module.exports = { } }) ) + }, + logout (conf) { + if (!conf.logoutUpstream) { + return '/' + } else if (conf.logoutURL && conf.logoutURL.length > 5) { + return `${conf.logoutURL}?redirect_uri=${encodeURIComponent(WIKI.config.host)}` + } else { + WIKI.logger.warn('Keycloak logout URL is not configured!') + return '/' + } } } diff --git a/server/modules/authentication/keycloak/definition.yml b/server/modules/authentication/keycloak/definition.yml index 1c581a77..df8ca666 100644 --- a/server/modules/authentication/keycloak/definition.yml +++ b/server/modules/authentication/keycloak/definition.yml @@ -8,10 +8,49 @@ website: https://www.keycloak.org/ useForm: false isAvailable: true props: - host: String - realm: String - clientId: String - clientSecret: String - authorizationURL: String - userInfoURL: String - tokenURL: String + host: + type: String + title: Host + hint: e.g. https://your.keycloak-host.com + order: 1 + realm: + type: String + title: Realm + hint: The realm this application belongs to. + order: 2 + clientId: + type: String + title: Client ID + hint: Application Client ID + order: 3 + clientSecret: + type: String + title: Client Secret + hint: Application Client Secret + order: 4 + authorizationURL: + type: String + title: Authorization Endpoint URL + hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/auth + order: 5 + tokenURL: + type: String + title: Token Endpoint URL + hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/token + order: 6 + userInfoURL: + type: String + title: User Info Endpoint URL + hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/userinfo + order: 7 + logoutUpstream: + type: Boolean + title: Logout from Keycloak on Logout + hint: Should the user be redirected to Keycloak logout mechanism upon logout + order: 8 + logoutURL: + type: String + title: Logout Endpoint URL + hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/logout + order: 9 +