diff --git a/website/src/controllers/api-v3/challenges.js b/website/src/controllers/api-v3/challenges.js index 8bec5eae6e..321f3c66c5 100644 --- a/website/src/controllers/api-v3/challenges.js +++ b/website/src/controllers/api-v3/challenges.js @@ -100,9 +100,9 @@ api.getChallenges = { async handler (req, res) { let user = res.locals.user; - let groups = user.guilds || []; + let groups = user.guilds.slice(0); // slice is used to clone the array so we don't modify it directly if (user.party._id) groups.push(user.party._id); - groups.push('habitrpg'); // Public challenges + groups.push('habitrpg'); // tavern challenges let challenges = await Challenge.find({ $or: [ @@ -143,11 +143,9 @@ api.getChallenge = { let user = res.local.user; let challengeId = req.params.challengeId; - let challenge = await Challenge.findOne({_id: challengeId}).exec(); // TODO populate + let challenge = await Challenge.findById(challengeId).exec(); - // If the challenge does not exist, or if it exists but user is not a member, not the leader and not an admin -> throw error - // TODO support challenges in groups I'm a member of - if (!challenge || (user.challenges.indexOf(challengeId) === -1 && challenge.leader !== user._id && !user.contributor.admin)) { // eslint-disable-line no-extra-parens + if (!challenge || !challenge.hasAccess(user)) { throw new NotFound(res.t('challengeNotFound')); } @@ -233,7 +231,7 @@ api.deleteChallenge = { let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id && !user.contributor.admin) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); + if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); res.respond(200, {}); // Close channel in background @@ -264,7 +262,7 @@ api.selectChallengeWinner = { let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id && !user.contributor.admin) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); + if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); let winner = await User.findOne({_id: req.params.winnerId}).exec(); if (!winner || winner.challenges.indexOf(challenge._id) === -1) throw new NotFound(res.t('winnerNotFound', {userId: req.parama.winnerId})); diff --git a/website/src/models/challenge.js b/website/src/models/challenge.js index b675a7833e..fcfb5cc1e3 100644 --- a/website/src/models/challenge.js +++ b/website/src/models/challenge.js @@ -31,6 +31,24 @@ schema.plugin(baseModel, { noSet: ['_id', 'memberCount', 'challengeCount', 'tasksOrder'], }); +// Return true if user has access to the challenge +schema.methods.hasAccess = function hasAccessToChallenge (user) { + let userGroups = user.guilds.slice(0); + if (user.party._id) userGroups.push(user.party._id); + userGroups.push('habitrpg'); // tavern challenges + return this.leader === user._id || user.contributor.admin || userGroups.indexOf(this.groupId) !== -1; +}; + +// Return true if user is a member of the challenge +schema.methods.isMember = function isChallengeMember (user) { + return user.challenges.indexOf(this._id) !== -1; +}; + +// Return true if the user can modify (close, selectWinner, ...) the challenge +schema.methods.canModify = function canModifyChallenge (user) { + return user.contributor.admin || this.leader === user._id; +}; + // Takes a Task document and return a plain object of attributes that can be synced to the user function _syncableAttrs (task) { let t = task.toObject(); // lodash doesn't seem to like _.omit on Document