diff --git a/server/core/auth.js b/server/core/auth.js index 87d9fcce..ca2dd513 100644 --- a/server/core/auth.js +++ b/server/core/auth.js @@ -19,6 +19,7 @@ module.exports = { }, groups: {}, validApiKeys: [], + revokationList: require('./cache').init(), /** * Initialize the authentication module @@ -111,10 +112,28 @@ module.exports = { authenticate(req, res, next) { WIKI.auth.passport.authenticate('jwt', {session: false}, async (err, user, info) => { if (err) { return next() } + let mustRevalidate = false // Expired but still valid within N days, just renew - if (info instanceof Error && info.name === 'TokenExpiredError' && - moment().subtract(ms(WIKI.config.auth.tokenRenewal), 'ms').isBefore(info.expiredAt)) { + if (info instanceof Error && info.name === 'TokenExpiredError' && moment().subtract(ms(WIKI.config.auth.tokenRenewal), 'ms').isBefore(info.expiredAt)) { + mustRevalidate = true + } + + // Check if user / group is in revokation list + if (user) { + if (WIKI.auth.revokationList.has(`u${_.toString(user.id)}`)) { + mustRevalidate = true + } + for (const gid of user.groups) { + if (WIKI.auth.revokationList.has(`g${_.toString(gid)}`)) { + mustRevalidate = true + } + } + } + + // Revalidate and renew token + if (mustRevalidate) { + console.info('MUST REVALIDATE') const jwtPayload = jwt.decode(securityHelper.extractJWT(req)) try { const newToken = await WIKI.models.users.refreshToken(jwtPayload.id) @@ -380,6 +399,9 @@ module.exports = { WIKI.events.inbound.on('reloadAuthStrategies', () => { WIKI.auth.activateStrategies() }) + WIKI.events.inbound.on('addAuthRevoke', (args) => { + WIKI.auth.revokeUserTokens(args) + }) }, /** @@ -410,5 +432,13 @@ module.exports = { manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page) } } + }, + + /** + * Add user / group ID to JWT revokation list, forcing all requests to be validated against the latest permissions + */ + revokeUserTokens ({ id, kind = 'u' }) { + console.info(Math.ceil(ms(WIKI.config.auth.tokenRenewal) / 1000)) + WIKI.auth.revokationList.set(`${kind}${_.toString(id)}`, true, Math.ceil(ms(WIKI.config.auth.tokenRenewal) / 1000)) } } diff --git a/server/graph/resolvers/group.js b/server/graph/resolvers/group.js index 401ebb9f..3f66089d 100644 --- a/server/graph/resolvers/group.js +++ b/server/graph/resolvers/group.js @@ -42,6 +42,10 @@ module.exports = { throw new gql.GraphQLError('User is already assigned to group.') } await grp.$relatedQuery('users').relate(usr.id) + + WIKI.auth.revokeUserTokens({ id: usr.id, kind: 'u' }) + WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' }) + return { responseResult: graphHelper.generateSuccess('User has been assigned to group.') } @@ -62,8 +66,13 @@ module.exports = { }, async delete(obj, args) { await WIKI.models.groups.query().deleteById(args.id) + + WIKI.auth.revokeUserTokens({ id: args.id, kind: 'g' }) + WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'g' }) + await WIKI.auth.reloadGroups() WIKI.events.outbound.emit('reloadGroups') + return { responseResult: graphHelper.generateSuccess('Group has been deleted.') } @@ -78,6 +87,10 @@ module.exports = { throw new gql.GraphQLError('Invalid User ID') } await grp.$relatedQuery('users').unrelate().where('userId', usr.id) + + WIKI.auth.revokeUserTokens({ id: usr.id, kind: 'u' }) + WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' }) + return { responseResult: graphHelper.generateSuccess('User has been unassigned from group.') } @@ -95,6 +108,9 @@ module.exports = { pageRules: JSON.stringify(args.pageRules) }).where('id', args.id) + WIKI.auth.revokeUserTokens({ id: args.id, kind: 'g' }) + WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'g' }) + await WIKI.auth.reloadGroups() WIKI.events.outbound.emit('reloadGroups') diff --git a/server/graph/resolvers/user.js b/server/graph/resolvers/user.js index 78a245aa..56a6202b 100644 --- a/server/graph/resolvers/user.js +++ b/server/graph/resolvers/user.js @@ -73,6 +73,10 @@ module.exports = { throw new WIKI.Error.UserDeleteProtected() } await WIKI.models.users.deleteUser(args.id, args.replaceId) + + WIKI.auth.revokeUserTokens({ id: args.id, kind: 'u' }) + WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'u' }) + return { responseResult: graphHelper.generateSuccess('User deleted successfully') } @@ -124,6 +128,9 @@ module.exports = { } await WIKI.models.users.query().patch({ isActive: false }).findById(args.id) + WIKI.auth.revokeUserTokens({ id: args.id, kind: 'u' }) + WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'u' }) + return { responseResult: graphHelper.generateSuccess('User deactivated successfully') }