瀏覽代碼

feat: logout by auth strategy + keycloak implementation

NGPixel 4 年之前
父節點
當前提交
062a0b7979

+ 1 - 2
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('/')

+ 3 - 2
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)
 })
 
 /**

+ 24 - 0
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')

+ 3 - 0
server/modules/authentication/google/authentication.js

@@ -30,5 +30,8 @@ module.exports = {
         }
       })
     )
+  },
+  logout (conf) {
+    return '/'
   }
 }

+ 10 - 0
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 '/'
+    }
   }
 }

+ 46 - 7
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
+