diff --git a/package-lock.json b/package-lock.json
index f9147905b1..106bc559f7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2347,6 +2347,19 @@
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"optional": true
},
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -6259,6 +6272,22 @@
"optional": true,
"requires": {
"glob": "^7.1.3"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
},
"yallist": {
@@ -6813,6 +6842,22 @@
"requires": {
"glob": "^7.0.3",
"minimatch": "^3.0.3"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
},
"fill-range": {
@@ -6928,6 +6973,21 @@
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"requires": {
"glob": "^7.1.3"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
}
}
@@ -7413,16 +7473,34 @@
"dev": true
},
"glob": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
- "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.1.tgz",
+ "integrity": "sha512-cF7FYZZ47YzmCu7dDy50xSRRfO3ErRfrXuLZcNIuyiJEco0XSrGtuilG19L5xp3NcwTx7Gn+X6Tv3fmsUPTbow==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
- "minimatch": "^3.0.4",
+ "minimatch": "^5.0.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
}
},
"glob-parent": {
@@ -7450,6 +7528,19 @@
"unique-stream": "^2.0.2"
},
"dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -7744,6 +7835,19 @@
"slash": "^3.0.0"
},
"dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
"ignore": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
@@ -9219,6 +9323,22 @@
"dev": true,
"requires": {
"glob": "^7.1.3"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
}
}
@@ -10534,6 +10654,22 @@
"dev": true,
"requires": {
"glob": "^7.1.3"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
}
}
@@ -12708,6 +12844,21 @@
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
},
"run-async": {
diff --git a/package.json b/package.json
index 91f189b711..4cdfd2d4dc 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"express": "^4.17.3",
"express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0",
- "glob": "^7.2.0",
+ "glob": "^8.0.1",
"got": "^11.8.3",
"gulp": "^4.0.0",
"gulp-babel": "^8.0.0",
diff --git a/test/api/unit/libs/items/utils.test.js b/test/api/unit/libs/items/utils.test.js
index 6a0e7ce34d..d1bf1f126e 100644
--- a/test/api/unit/libs/items/utils.test.js
+++ b/test/api/unit/libs/items/utils.test.js
@@ -99,23 +99,26 @@ describe('Items Utils', () => {
expect(castItemVal('items.food.Cake_Invalid', '5')).to.equal(5);
});
- it('converts values for mounts paths to numbers', () => {
- expect(castItemVal('items.mounts.Cactus-Base', 'true')).to.equal(true);
- expect(castItemVal('items.mounts.Aether-Invisible', 'false')).to.equal(false);
- expect(castItemVal('items.mounts.Aether-Invalid', 'true')).to.equal(true);
- expect(castItemVal('items.mounts.Aether-Invalid', 'truish')).to.equal(true);
- expect(castItemVal('items.mounts.Aether-Invalid', 0)).to.equal(false);
- });
-
it('converts values for quests paths to numbers', () => {
expect(castItemVal('items.quests.atom3', '5')).to.equal(5);
expect(castItemVal('items.quests.invalid', '5')).to.equal(5);
});
- it('converts values for owned gear', () => {
+ it('converts values for mounts paths to true/null', () => {
+ // mounts are never false but can be null (function contains more details)
+ expect(castItemVal('items.mounts.Cactus-Base', 'true')).to.equal(true);
+ expect(castItemVal('items.mounts.Aether-Invisible', 'null')).to.equal(null);
+ expect(castItemVal('items.mounts.Aether-Invisible', 'false')).to.equal(null);
+ expect(castItemVal('items.mounts.Aether-Invalid', 'true')).to.equal(true);
+ expect(castItemVal('items.mounts.Aether-Invalid', 'truthy')).to.equal(true);
+ expect(castItemVal('items.mounts.Aether-Invalid', 0)).to.equal(null);
+ });
+
+ it('converts values for owned gear to true/false', () => {
expect(castItemVal('items.gear.owned.shield_warrior_0', 'true')).to.equal(true);
expect(castItemVal('items.gear.owned.invalid', 'false')).to.equal(false);
- expect(castItemVal('items.gear.owned.invalid', 'thruthy')).to.equal(true);
+ expect(castItemVal('items.gear.owned.invalid', 'null')).to.equal(false);
+ expect(castItemVal('items.gear.owned.invalid', 'truthy')).to.equal(true);
expect(castItemVal('items.gear.owned.invalid', 0)).to.equal(false);
});
});
diff --git a/test/api/unit/middlewares/ensureAccessRight.test.js b/test/api/unit/middlewares/ensureAccessRight.test.js
index d163ae704d..d87cb6d035 100644
--- a/test/api/unit/middlewares/ensureAccessRight.test.js
+++ b/test/api/unit/middlewares/ensureAccessRight.test.js
@@ -4,8 +4,7 @@ import {
generateReq,
generateNext,
} from '../../../helpers/api-unit.helper';
-import i18n from '../../../../website/common/script/i18n';
-import { ensureAdmin, ensureSudo, ensureNewsPoster } from '../../../../website/server/middlewares/ensureAccessRight';
+import { ensurePermission } from '../../../../website/server/middlewares/ensureAccessRight';
import { NotAuthorized } from '../../../../website/server/libs/errors';
import apiError from '../../../../website/server/libs/apiError';
@@ -20,20 +19,20 @@ describe('ensure access middlewares', () => {
});
context('ensure admin', () => {
- it('returns not authorized when user is not an admin', () => {
- res.locals = { user: { contributor: { admin: false } } };
+ it('returns not authorized when user is not in userSupport', () => {
+ res.locals = { user: { permissions: { userSupport: false } } };
- ensureAdmin(req, res, next);
+ ensurePermission('userSupport')(req, res, next);
const calledWith = next.getCall(0).args;
- expect(calledWith[0].message).to.equal(i18n.t('noAdminAccess'));
+ expect(calledWith[0].message).to.equal(apiError('noPrivAccess'));
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
});
- it('passes when user is an admin', () => {
- res.locals = { user: { contributor: { admin: true } } };
+ it('passes when user is an userSuppor', () => {
+ res.locals = { user: { permissions: { userSupport: true } } };
- ensureAdmin(req, res, next);
+ ensurePermission('userSupport')(req, res, next);
expect(next).to.be.calledOnce;
expect(next.args[0]).to.be.empty;
@@ -42,40 +41,40 @@ describe('ensure access middlewares', () => {
context('ensure newsPoster', () => {
it('returns not authorized when user is not a newsPoster', () => {
- res.locals = { user: { contributor: { newsPoster: false } } };
+ res.locals = { user: { permissions: { news: false } } };
- ensureNewsPoster(req, res, next);
+ ensurePermission('news')(req, res, next);
const calledWith = next.getCall(0).args;
- expect(calledWith[0].message).to.equal(apiError('noNewsPosterAccess'));
+ expect(calledWith[0].message).to.equal(apiError('noPrivAccess'));
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
});
it('passes when user is a newsPoster', () => {
- res.locals = { user: { contributor: { newsPoster: true } } };
+ res.locals = { user: { permissions: { news: true } } };
- ensureNewsPoster(req, res, next);
+ ensurePermission('news')(req, res, next);
expect(next).to.be.calledOnce;
expect(next.args[0]).to.be.empty;
});
});
- context('ensure sudo', () => {
- it('returns not authorized when user is not a sudo user', () => {
- res.locals = { user: { contributor: { sudo: false } } };
+ context('ensure coupons', () => {
+ it('returns not authorized when user does not have access to coupon calls', () => {
+ res.locals = { user: { permissions: { coupons: false } } };
- ensureSudo(req, res, next);
+ ensurePermission('coupons')(req, res, next);
const calledWith = next.getCall(0).args;
- expect(calledWith[0].message).to.equal(apiError('noSudoAccess'));
+ expect(calledWith[0].message).to.equal(apiError('noPrivAccess'));
expect(calledWith[0] instanceof NotAuthorized).to.equal(true);
});
- it('passes when user is a sudo user', () => {
- res.locals = { user: { contributor: { sudo: true } } };
+ it('passes when user has access to coupon calls', () => {
+ res.locals = { user: { permissions: { coupons: true } } };
- ensureSudo(req, res, next);
+ ensurePermission('coupons')(req, res, next);
expect(next).to.be.calledOnce;
expect(next.args[0]).to.be.empty;
diff --git a/test/api/unit/models/group.test.js b/test/api/unit/models/group.test.js
index 59efba1134..fd5cea9b05 100644
--- a/test/api/unit/models/group.test.js
+++ b/test/api/unit/models/group.test.js
@@ -1029,7 +1029,7 @@ describe('Group Model', () => {
expect(toJSON.chat.length).to.equal(1);
});
- it('shows messages with >= 2 flag to admins', async () => {
+ it('shows messages with >= 2 flag to moderators', async () => {
party.chat = [{
flagCount: 3,
info: {
@@ -1037,12 +1037,12 @@ describe('Group Model', () => {
quest: 'basilist',
},
}];
- const admin = new User({ 'contributor.admin': true });
+ const admin = new User({ 'permissions.moderator': true });
const toJSON = await Group.toJSONCleanChat(party, admin);
expect(toJSON.chat.length).to.equal(1);
});
- it('doesn\'t show flagged messages to non-admins', async () => {
+ it('doesn\'t show flagged messages to non-moderators', async () => {
party.chat = [{
flagCount: 3,
info: {
diff --git a/test/api/unit/models/user.test.js b/test/api/unit/models/user.test.js
index d559ba0025..06307e39a3 100644
--- a/test/api/unit/models/user.test.js
+++ b/test/api/unit/models/user.test.js
@@ -877,7 +877,7 @@ describe('User Model', () => {
expect(user.isNewsPoster()).to.equal(false);
- user.contributor.newsPoster = true;
+ user.permissions = { news: true };
expect(user.isNewsPoster()).to.equal(true);
});
diff --git a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js
index 8e4fc6a432..fc26d4ce0d 100644
--- a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js
+++ b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js
@@ -202,7 +202,7 @@ describe('GET challenges/groups/:groupId', () => {
publicGuild = group;
await user.update({
- 'contributor.admin': true,
+ 'permissions.challengeAdmin': true,
});
officialChallenge = await generateChallenge(user, group, {
diff --git a/test/api/v3/integration/challenges/GET-challenges_user.test.js b/test/api/v3/integration/challenges/GET-challenges_user.test.js
index 0ff9385891..cc9c3aabd7 100644
--- a/test/api/v3/integration/challenges/GET-challenges_user.test.js
+++ b/test/api/v3/integration/challenges/GET-challenges_user.test.js
@@ -231,7 +231,7 @@ describe('GET challenges/user', () => {
publicGuild = group;
await user.update({
- 'contributor.admin': true,
+ 'permissions.challengeAdmin': true,
});
officialChallenge = await generateChallenge(user, group, {
diff --git a/test/api/v3/integration/challenges/POST-challenges.test.js b/test/api/v3/integration/challenges/POST-challenges.test.js
index 8896c3e79d..e1b3dc63a0 100644
--- a/test/api/v3/integration/challenges/POST-challenges.test.js
+++ b/test/api/v3/integration/challenges/POST-challenges.test.js
@@ -203,8 +203,8 @@ describe('POST /challenges', () => {
it('sets challenge as official if created by admin and official flag is set', async () => {
await groupLeader.update({
- contributor: {
- admin: true,
+ permissions: {
+ challengeAdmin: true,
},
});
diff --git a/test/api/v3/integration/chat/DELETE-chat_id.test.js b/test/api/v3/integration/chat/DELETE-chat_id.test.js
index 58203ea94d..0d53dfa999 100644
--- a/test/api/v3/integration/chat/DELETE-chat_id.test.js
+++ b/test/api/v3/integration/chat/DELETE-chat_id.test.js
@@ -22,7 +22,7 @@ describe('DELETE /groups/:groupId/chat/:chatId', () => {
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
message = message.message;
userThatDidNotCreateChat = await generateUser();
- admin = await generateUser({ 'contributor.admin': true });
+ admin = await generateUser({ 'permissions.moderator': true });
});
context('Chat errors', () => {
diff --git a/test/api/v3/integration/chat/POST-chat.flag.test.js b/test/api/v3/integration/chat/POST-chat.flag.test.js
index 50f47b3572..acff8b507b 100644
--- a/test/api/v3/integration/chat/POST-chat.flag.test.js
+++ b/test/api/v3/integration/chat/POST-chat.flag.test.js
@@ -17,7 +17,7 @@ describe('POST /chat/:chatId/flag', () => {
beforeEach(async () => {
user = await generateUser({ balance: 1, 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
- admin = await generateUser({ balance: 1, 'contributor.admin': true });
+ admin = await generateUser({ balance: 1, 'permissions.moderator': true });
anotherUser = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
newUser = await generateUser({ 'auth.timestamps.created': moment().subtract(1, 'days').toDate() });
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
diff --git a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js
index 9bbc122244..e9f77e5561 100644
--- a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js
+++ b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js
@@ -23,7 +23,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
groupWithChat = group;
author = groupLeader;
nonAdmin = await generateUser({ 'auth.timestamps.created': moment().subtract(USER_AGE_FOR_FLAGGING + 1, 'days').toDate() });
- admin = await generateUser({ 'contributor.admin': true });
+ admin = await generateUser({ 'permissions.moderator': true });
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
message = message.message;
diff --git a/test/api/v3/integration/coupons/GET-coupons.test.js b/test/api/v3/integration/coupons/GET-coupons.test.js
index 24cc230e49..0839f22dba 100644
--- a/test/api/v3/integration/coupons/GET-coupons.test.js
+++ b/test/api/v3/integration/coupons/GET-coupons.test.js
@@ -14,18 +14,18 @@ describe('GET /coupons/', () => {
user = await generateUser();
});
- it('returns an error if user has no sudo permission', async () => {
+ it('returns an error if user has no coupons permission', async () => {
await user.get('/user'); // needed so the request after this will authenticate with the correct cookie session
await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: apiError('noSudoAccess'),
+ message: apiError('noPrivAccess'),
});
});
it('should return the coupons in CSV format ordered by creation date', async () => {
await user.update({
- 'contributor.sudo': true,
+ 'permissions.coupons': true,
});
const coupons = await user.post('/coupons/generate/wondercon?count=11');
diff --git a/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js b/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js
index d2b0fdec44..31830a0689 100644
--- a/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js
+++ b/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js
@@ -15,7 +15,7 @@ describe('POST /coupons/enter/:code', () => {
beforeEach(async () => {
user = await generateUser();
sudoUser = await generateUser({
- 'contributor.sudo': true,
+ 'permissions.coupons': true,
});
});
diff --git a/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js b/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js
index 591cf1a567..f0c5bfe436 100644
--- a/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js
+++ b/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js
@@ -14,19 +14,19 @@ describe('POST /coupons/generate/:event', () => {
beforeEach(async () => {
user = await generateUser({
- 'contributor.sudo': true,
+ 'permissions.coupons': true,
});
});
- it('returns an error if user has no sudo permission', async () => {
+ it('returns an error if user has no coupons permission', async () => {
await user.update({
- 'contributor.sudo': false,
+ 'permissions.coupons': false,
});
await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: apiError('noSudoAccess'),
+ message: apiError('noPrivAccess'),
});
});
@@ -48,7 +48,7 @@ describe('POST /coupons/generate/:event', () => {
it('should generate coupons', async () => {
await user.update({
- 'contributor.sudo': true,
+ 'permissions.coupons': true,
});
const coupons = await user.post('/coupons/generate/wondercon?count=2');
diff --git a/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js b/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js
index 5d6bef2b91..a206d245c7 100644
--- a/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js
+++ b/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js
@@ -21,7 +21,7 @@ describe('POST /coupons/validate/:code', () => {
it('returns true if coupon code is valid', async () => {
const sudoUser = await generateUser({
- 'contributor.sudo': true,
+ 'permissions.coupons': true,
});
const [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
diff --git a/test/api/v3/integration/debug/POST-debug_make-admin.test.js b/test/api/v3/integration/debug/POST-debug_make-admin.test.js
index 311d477994..c03fa4627f 100644
--- a/test/api/v3/integration/debug/POST-debug_make-admin.test.js
+++ b/test/api/v3/integration/debug/POST-debug_make-admin.test.js
@@ -3,7 +3,7 @@ import {
generateUser,
} from '../../../../helpers/api-integration/v3';
-describe('POST /debug/make-admin (pended for v3 prod testing)', () => {
+describe('POST /debug/make-admin', () => {
let user;
before(async () => {
@@ -14,12 +14,12 @@ describe('POST /debug/make-admin (pended for v3 prod testing)', () => {
nconf.set('IS_PROD', false);
});
- it('makes user an admine', async () => {
+ it('makes user an admin', async () => {
await user.post('/debug/make-admin');
await user.sync();
- expect(user.contributor.admin).to.eql(true);
+ expect(user.permissions.fullAccess).to.eql(true);
});
it('returns error when not in production mode', async () => {
diff --git a/test/api/v3/integration/groups/GET-groups.test.js b/test/api/v3/integration/groups/GET-groups.test.js
index aaea5b397d..74c988e0b1 100644
--- a/test/api/v3/integration/groups/GET-groups.test.js
+++ b/test/api/v3/integration/groups/GET-groups.test.js
@@ -219,11 +219,19 @@ describe('GET /groups', () => {
it('returns 30 guilds per page ordered by number of members', async () => {
await user.update({ balance: 9000 });
- const groups = await Promise.all(_.times(60, i => generateGroup(user, {
- name: `public guild ${i} - is member`,
- type: 'guild',
- privacy: 'public',
- })));
+ const delay = () => new Promise(resolve => setTimeout(resolve, 40));
+ const promises = [];
+
+ for (let i = 0; i < 60; i += 1) {
+ promises.push(generateGroup(user, {
+ name: `public guild ${i} - is member`,
+ type: 'guild',
+ privacy: 'public',
+ }));
+ await delay(); // eslint-disable-line no-await-in-loop
+ }
+
+ const groups = await Promise.all(promises);
// update group number 32 and not the first to make sure sorting works
await groups[32].update({ name: 'guild with most members', memberCount: 199 });
diff --git a/test/api/v3/integration/groups/GET-groups_id.test.js b/test/api/v3/integration/groups/GET-groups_id.test.js
index 801ee3d434..7d1657db18 100644
--- a/test/api/v3/integration/groups/GET-groups_id.test.js
+++ b/test/api/v3/integration/groups/GET-groups_id.test.js
@@ -315,7 +315,7 @@ describe('GET /groups/:id', () => {
beforeEach(async () => {
admin = await generateUser({
- 'contributor.admin': true,
+ 'permissions.moderator': true,
});
});
diff --git a/test/api/v3/integration/groups/POST-groups.test.js b/test/api/v3/integration/groups/POST-groups.test.js
index 934281209f..b507fc8165 100644
--- a/test/api/v3/integration/groups/POST-groups.test.js
+++ b/test/api/v3/integration/groups/POST-groups.test.js
@@ -2,6 +2,7 @@ import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
+import { model as Group } from '../../../../../website/server/models/group';
describe('POST /group', () => {
let user;
@@ -203,6 +204,23 @@ describe('POST /group', () => {
expect(updatedUser.balance).to.eql(user.balance - 1);
});
+
+ it('does not deduct the gems from user when guild creation fails', async () => {
+ const stub = sinon.stub(Group.prototype, 'save').rejects();
+ const promise = user.post('/groups', {
+ name: groupName,
+ type: groupType,
+ privacy: groupPrivacy,
+ });
+
+ await expect(promise).to.eventually.be.rejected;
+
+ const updatedUser = await user.get('/user');
+
+ expect(updatedUser.balance).to.eql(user.balance);
+
+ stub.restore();
+ });
});
});
diff --git a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
index d91267ed2b..7b7fc0e56e 100644
--- a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
+++ b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
@@ -32,7 +32,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
invitedUser = invitees[0]; // eslint-disable-line prefer-destructuring
member = members[0]; // eslint-disable-line prefer-destructuring
member2 = members[1]; // eslint-disable-line prefer-destructuring
- adminUser = await generateUser({ 'contributor.admin': true });
+ adminUser = await generateUser({ 'permissions.moderator': true });
});
context('All Groups', () => {
diff --git a/test/api/v3/integration/groups/PUT-groups.test.js b/test/api/v3/integration/groups/PUT-groups.test.js
index 61bb6370bc..1ff67958da 100644
--- a/test/api/v3/integration/groups/PUT-groups.test.js
+++ b/test/api/v3/integration/groups/PUT-groups.test.js
@@ -20,7 +20,7 @@ describe('PUT /group', () => {
},
members: 1,
});
- adminUser = await generateUser({ 'contributor.admin': true });
+ adminUser = await generateUser({ 'permissions.moderator': true });
groupToUpdate = group;
leader = groupLeader;
nonLeader = members[0]; // eslint-disable-line prefer-destructuring
@@ -104,11 +104,11 @@ describe('PUT /group', () => {
// Update the bannedWordsAllowed property for the group
const response = await groupLeader.put(`/groups/${group._id}`, updateGroupDetails);
- expect(groupLeader.contributor.admin).to.eql(true);
+ expect(groupLeader.permissions.fullAccess).to.eql(true);
expect(response.bannedWordsAllowed).to.eql(true);
});
- it('does not allow for a non-admin to update the bannedWordsAllow property for an existing guild', async () => {
+ it('does not allow for a non-moderator to update the bannedWordsAllow property for an existing guild', async () => {
const { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'public guild',
@@ -128,7 +128,6 @@ describe('PUT /group', () => {
// Update the bannedWordsAllowed property for the group
const response = await groupLeader.put(`/groups/${group._id}`, updateGroupDetails);
- expect(groupLeader.contributor.admin).to.eql(undefined);
expect(response.bannedWordsAllowed).to.eql(undefined);
});
});
diff --git a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js
index bcafdcfd80..107f729a7d 100644
--- a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js
+++ b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js
@@ -7,9 +7,14 @@ import {
describe('GET /heroes/:heroId', () => {
let user;
+ const heroFields = [
+ '_id', 'id', 'auth', 'balance', 'contributor', 'flags', 'items',
+ 'lastCron', 'party', 'preferences', 'profile', 'purchased', 'secret',
+ ];
+
before(async () => {
user = await generateUser({
- contributor: { admin: true },
+ permissions: { userSupport: true },
});
});
@@ -19,7 +24,7 @@ describe('GET /heroes/:heroId', () => {
await expect(nonAdmin.get(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: t('noAdminAccess'),
+ message: t('noPrivAccess'),
});
});
@@ -49,10 +54,7 @@ describe('GET /heroes/:heroId', () => {
});
const heroRes = await user.get(`/hall/heroes/${hero._id}`);
- expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
- '_id', 'id', 'balance', 'profile', 'purchased',
- 'contributor', 'auth', 'items', 'secret',
- ]);
+ expect(heroRes).to.have.all.keys(heroFields); // works as: object has all and only these keys
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
expect(heroRes.secret.text).to.be.eq('Super Hero');
@@ -64,10 +66,7 @@ describe('GET /heroes/:heroId', () => {
});
const heroRes = await user.get(`/hall/heroes/${hero.auth.local.username}`);
- expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
- '_id', 'id', 'balance', 'profile', 'purchased',
- 'contributor', 'auth', 'items', 'secret',
- ]);
+ expect(heroRes).to.have.all.keys(heroFields);
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
});
diff --git a/test/api/v3/integration/hall/GET-hall_heroes_party_groupId.test.js b/test/api/v3/integration/hall/GET-hall_heroes_party_groupId.test.js
new file mode 100644
index 0000000000..ea61800d0c
--- /dev/null
+++ b/test/api/v3/integration/hall/GET-hall_heroes_party_groupId.test.js
@@ -0,0 +1,61 @@
+import { v4 as generateUUID } from 'uuid';
+import {
+ generateUser,
+ generateGroup,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import apiError from '../../../../../website/server/libs/apiError';
+
+describe('GET /heroes/party/:groupId', () => {
+ let user; // admin user
+
+ before(async () => {
+ user = await generateUser({
+ 'permissions.userSupport': true,
+ });
+ });
+
+ it('requires the caller to be an admin', async () => {
+ const nonAdmin = await generateUser();
+ const party = await generateGroup(nonAdmin, { type: 'party', privacy: 'private' });
+ await expect(nonAdmin.get(`/hall/heroes/party/${party._id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: apiError('noPrivAccess'),
+ });
+ });
+
+ it('validates req.params.groupId', async () => {
+ await expect(user.get('/hall/heroes/party/invalidUUID')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('handles non-existing party', async () => {
+ const dummyId = generateUUID();
+ await expect(user.get(`/hall/heroes/party/${dummyId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: apiError('groupWithIDNotFound', { groupId: dummyId }),
+ });
+ });
+
+ it('returns only necessary party data given group id', async () => {
+ const nonAdmin = await generateUser();
+ const party = await generateGroup(nonAdmin, { type: 'party', privacy: 'private' });
+
+ const partyRes = await user.get(`/hall/heroes/party/${party._id}`);
+
+ expect(partyRes).to.have.all.keys([ // works as: object has all and only these keys
+ '_id', 'id', 'balance', 'challengeCount', 'leader', 'leaderOnly', 'memberCount',
+ 'purchased', 'quest', 'summary',
+ ]);
+ expect(partyRes.summary).to.eq(' ');
+ // NB: 'summary' is NOT a field that the API route retrieves!
+ // It must not be retrieved for privacy reasons.
+ // However the group model automatically adds a summary for reasons given here:
+ // https://github.com/HabitRPG/habitica/blob/8da36bf27c62ba0397a6af260c20d35a17f3d911/website/server/models/group.js#L161-L170
+ });
+});
diff --git a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js
index 5e9d042ea5..2f98e89b3f 100644
--- a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js
+++ b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js
@@ -1,4 +1,5 @@
import { v4 as generateUUID } from 'uuid';
+import { model as User } from '../../../../../website/server/models/user';
import {
generateUser,
translate as t,
@@ -8,15 +9,12 @@ describe('PUT /heroes/:heroId', () => {
let user;
const heroFields = [
- '_id', 'balance', 'profile', 'purchased',
- 'contributor', 'auth', 'items', 'flags',
- 'secret',
+ '_id', 'auth', 'balance', 'contributor', 'flags', 'items', 'lastCron',
+ 'party', 'preferences', 'profile', 'purchased', 'secret', 'permissions',
];
before(async () => {
- user = await generateUser({
- contributor: { admin: true },
- });
+ user = await generateUser({ 'permissions.userSupport': true });
});
it('requires the caller to be an admin', async () => {
@@ -25,7 +23,7 @@ describe('PUT /heroes/:heroId', () => {
await expect(nonAdmin.put(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: t('noAdminAccess'),
+ message: t('noPrivAccess'),
});
});
@@ -57,8 +55,7 @@ describe('PUT /heroes/:heroId', () => {
});
// test response
- // works as: object has all and only these keys
- expect(heroRes).to.have.all.keys(heroFields);
+ expect(heroRes).to.have.all.keys(heroFields); // works as: object has all and only these keys
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
@@ -134,7 +131,6 @@ describe('PUT /heroes/:heroId', () => {
});
// test response
- // works as: object has all and only these keys
expect(heroRes).to.have.all.keys(heroFields);
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
@@ -159,7 +155,6 @@ describe('PUT /heroes/:heroId', () => {
});
// test response
- // works as: object has all and only these keys
expect(heroRes).to.have.all.keys(heroFields);
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
@@ -215,7 +210,6 @@ describe('PUT /heroes/:heroId', () => {
});
// test response
- // works as: object has all and only these keys
expect(heroRes).to.have.all.keys(heroFields);
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
@@ -226,4 +220,35 @@ describe('PUT /heroes/:heroId', () => {
await hero.sync();
expect(hero.items.special.snowball).to.equal(5);
});
+
+ it('does not accidentally update API Token', async () => {
+ // This test has been included because hall.js will contain code to produce
+ // a truncated version of the API Token, and we want to be sure that
+ // the real Token is not modified by bugs in that code.
+ const hero = await generateUser();
+ const originalToken = hero.apiToken;
+
+ // make any change to the user except the Token
+ await user.put(`/hall/heroes/${hero._id}`, {
+ contributor: { text: 'Astronaut' },
+ });
+
+ const updatedHero = await User.findById(hero._id).exec();
+ expect(updatedHero.apiToken).to.equal(originalToken);
+ expect(updatedHero.apiTokenObscured).to.not.exist;
+ });
+
+ it('does update API Token when admin changes it', async () => {
+ const hero = await generateUser();
+ const originalToken = hero.apiToken;
+
+ // change the user's API Token
+ await user.put(`/hall/heroes/${hero._id}`, {
+ changeApiToken: true,
+ });
+
+ const updatedHero = await User.findById(hero._id).exec();
+ expect(updatedHero.apiToken).to.not.equal(originalToken);
+ expect(updatedHero.apiTokenObscured).to.not.exist;
+ });
});
diff --git a/test/api/v3/integration/members/POST-send_private_message.test.js b/test/api/v3/integration/members/POST-send_private_message.test.js
index a66c682286..a51ca111b8 100644
--- a/test/api/v3/integration/members/POST-send_private_message.test.js
+++ b/test/api/v3/integration/members/POST-send_private_message.test.js
@@ -176,7 +176,7 @@ describe('POST /members/send-private-message', () => {
it('allows admin to send when sender has blocked the admin', async () => {
userToSendMessage = await generateUser({
- 'contributor.admin': 1,
+ 'permissions.moderator': true,
});
const receiver = await generateUser({ 'inbox.blocks': [userToSendMessage._id] });
@@ -204,7 +204,7 @@ describe('POST /members/send-private-message', () => {
it('allows admin to send when to user has opted out of messaging', async () => {
userToSendMessage = await generateUser({
- 'contributor.admin': 1,
+ 'permissions.moderator': true,
});
const receiver = await generateUser({ 'inbox.optOut': true });
diff --git a/test/api/v3/integration/tasks/GET-tasks_id.test.js b/test/api/v3/integration/tasks/GET-tasks_id.test.js
index a699815491..2e9353a9a3 100644
--- a/test/api/v3/integration/tasks/GET-tasks_id.test.js
+++ b/test/api/v3/integration/tasks/GET-tasks_id.test.js
@@ -105,7 +105,7 @@ describe('GET /tasks/:id', () => {
it('can get challenge task if admin', async () => {
const admin = await generateUser({
- 'contributor.admin': true,
+ 'permissions.challengeAdmin': true,
});
const getTask = await admin.get(`/tasks/${task._id}`);
diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js
index 272dd858fd..7d75753217 100644
--- a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js
+++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js
@@ -60,7 +60,7 @@ describe('POST /tasks/challenge/:challengeId', () => {
});
it('allows non-leader admin to add tasks to a challenge when not a member', async () => {
- const admin = await generateUser({ 'contributor.admin': true });
+ const admin = await generateUser({ 'permissions.challengeAdmin': true });
const task = await admin.post(`/tasks/challenge/${challenge._id}`, {
text: 'test habit from admin',
type: 'habit',
diff --git a/test/api/v3/integration/user/POST-user_reset.test.js b/test/api/v3/integration/user/POST-user_reset.test.js
index 11f771ce5a..760c64df37 100644
--- a/test/api/v3/integration/user/POST-user_reset.test.js
+++ b/test/api/v3/integration/user/POST-user_reset.test.js
@@ -120,7 +120,7 @@ describe('POST /user/reset', () => {
it('does not delete secret', async () => {
const admin = await generateUser({
- contributor: { admin: true },
+ permissions: { userSupport: true },
});
const hero = await generateUser({
diff --git a/test/api/v3/integration/user/PUT-user.test.js b/test/api/v3/integration/user/PUT-user.test.js
index b2d6121741..1a0451318f 100644
--- a/test/api/v3/integration/user/PUT-user.test.js
+++ b/test/api/v3/integration/user/PUT-user.test.js
@@ -135,6 +135,7 @@ describe('PUT /user', () => {
'gem balance': { balance: 100 },
auth: { 'auth.blocked': true, 'auth.timestamps.created': new Date() },
contributor: { 'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text' },
+ permissions: { 'permissions.fullAccess': true, 'permissions.news': true, 'permissions.moderator': 'some text' },
backer: { 'backer.tier': 10, 'backer.npc': 'Bilbo' },
subscriptions: { 'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000 },
'customization gem purchases': { 'purchased.background.tavern': true, 'purchased.skin.bear': true },
diff --git a/test/api/v4/coupon/POST-coupons_enter_code.test.js b/test/api/v4/coupon/POST-coupons_enter_code.test.js
index 71e4c05719..acc6b51a66 100644
--- a/test/api/v4/coupon/POST-coupons_enter_code.test.js
+++ b/test/api/v4/coupon/POST-coupons_enter_code.test.js
@@ -15,7 +15,7 @@ describe('POST /coupons/enter/:code', () => {
beforeEach(async () => {
user = await generateUser();
sudoUser = await generateUser({
- 'contributor.sudo': true,
+ 'permissions.coupons': true,
});
});
diff --git a/test/api/v4/members/GET-purchase_history.test.js b/test/api/v4/members/GET-purchase_history.test.js
index 99623a4561..58d77f099a 100644
--- a/test/api/v4/members/GET-purchase_history.test.js
+++ b/test/api/v4/members/GET-purchase_history.test.js
@@ -8,7 +8,7 @@ describe('GET /members/:memberId/purchase-history', () => {
before(async () => {
user = await generateUser({
- contributor: { admin: true },
+ permissions: { userSupport: true },
});
});
@@ -26,7 +26,7 @@ describe('GET /members/:memberId/purchase-history', () => {
await expect(nonAdmin.get(`/members/${member._id}/purchase-history`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: t('noAdminAccess'),
+ message: t('noPrivAccess'),
});
});
diff --git a/test/api/v4/news/DELETE-news.test.js b/test/api/v4/news/DELETE-news.test.js
index 211c0d706d..5e1d540b2a 100644
--- a/test/api/v4/news/DELETE-news.test.js
+++ b/test/api/v4/news/DELETE-news.test.js
@@ -15,16 +15,16 @@ describe('DELETE /news/:newsID', () => {
};
beforeEach(async () => {
user = await generateUser({
- 'contributor.newsPoster': true,
+ 'permissions.news': true,
});
});
it('disallows access to non-newsPosters', async () => {
- const nonAdminUser = await generateUser({ 'contributor.newsPoster': false });
+ const nonAdminUser = await generateUser({ 'permissions.news': false });
await expect(nonAdminUser.del(`/news/${v4()}`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: 'You don\'t have news poster access.',
+ message: t('noPrivAccess'),
});
});
diff --git a/test/api/v4/news/GET-news.test.js b/test/api/v4/news/GET-news.test.js
index 64db6d8391..9ea0eac2d1 100644
--- a/test/api/v4/news/GET-news.test.js
+++ b/test/api/v4/news/GET-news.test.js
@@ -15,7 +15,7 @@ describe('GET /news', () => {
before(async () => {
api = requester();
const user = await generateUser({
- 'contributor.newsPoster': true,
+ 'permissions.news': true,
});
await Promise.all([
diff --git a/test/api/v4/news/GET-news_id.test.js b/test/api/v4/news/GET-news_id.test.js
index a6b7ed0579..c0fdd46d04 100644
--- a/test/api/v4/news/GET-news_id.test.js
+++ b/test/api/v4/news/GET-news_id.test.js
@@ -15,7 +15,7 @@ describe('GET /news/:newsID', () => {
};
beforeEach(async () => {
user = await generateUser({
- 'contributor.newsPoster': true,
+ 'permissions.news': true,
});
});
diff --git a/test/api/v4/news/POST-news.test.js b/test/api/v4/news/POST-news.test.js
index f102616624..733204a632 100644
--- a/test/api/v4/news/POST-news.test.js
+++ b/test/api/v4/news/POST-news.test.js
@@ -16,16 +16,16 @@ describe('POST /news', () => {
};
beforeEach(async () => {
user = await generateUser({
- 'contributor.newsPoster': true,
+ 'permissions.news': true,
});
});
it('disallows access to non-admins', async () => {
- const nonAdminUser = await generateUser({ 'contributor.newsPoster': false });
+ const nonAdminUser = await generateUser({ 'permissions.news': false });
await expect(nonAdminUser.post('/news')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: 'You don\'t have news poster access.',
+ message: 'You don\'t have the required privileges.',
});
});
diff --git a/test/api/v4/news/PUT-news_newsId.test.js b/test/api/v4/news/PUT-news_newsId.test.js
index 6301b94aa3..172603a45a 100644
--- a/test/api/v4/news/PUT-news_newsId.test.js
+++ b/test/api/v4/news/PUT-news_newsId.test.js
@@ -17,16 +17,16 @@ describe('PUT /news/:newsID', () => {
};
beforeEach(async () => {
user = await generateUser({
- 'contributor.newsPoster': true,
+ 'permissions.news': true,
});
});
it('disallows access to non-admins', async () => {
- const nonAdminUser = await generateUser({ 'contributor.newsPoster': false });
+ const nonAdminUser = await generateUser({ 'permissions.news': false });
await expect(nonAdminUser.put('/news/1234')).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
- message: 'You don\'t have news poster access.',
+ message: 'You don\'t have the required privileges.',
});
});
diff --git a/test/api/v4/user/POST-user_reset.test.js b/test/api/v4/user/POST-user_reset.test.js
index 1c98059cc6..677bf193ce 100644
--- a/test/api/v4/user/POST-user_reset.test.js
+++ b/test/api/v4/user/POST-user_reset.test.js
@@ -120,7 +120,7 @@ describe('POST /user/reset', () => {
it('does not delete secret', async () => {
const admin = await generateUser({
- contributor: { admin: true },
+ permissions: { userSupport: true },
});
const hero = await generateUser({
diff --git a/test/api/v4/user/PUT-user.test.js b/test/api/v4/user/PUT-user.test.js
index 2fe1e32bc1..a588a6f386 100644
--- a/test/api/v4/user/PUT-user.test.js
+++ b/test/api/v4/user/PUT-user.test.js
@@ -84,6 +84,7 @@ describe('PUT /user', () => {
'gem balance': { balance: 100 },
auth: { 'auth.blocked': true, 'auth.timestamps.created': new Date() },
contributor: { 'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text' },
+ permissions: { 'permissions.fullAccess': true, 'permissions.news': true, 'permissions.moderator': 'some text' },
backer: { 'backer.tier': 10, 'backer.npc': 'Bilbo' },
subscriptions: { 'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000 },
'customization gem purchases': { 'purchased.background.tavern': true, 'purchased.skin.bear': true },
diff --git a/website/client/src/components/admin-panel/filters/formatDate.js b/website/client/src/components/admin-panel/filters/formatDate.js
new file mode 100644
index 0000000000..339fc08262
--- /dev/null
+++ b/website/client/src/components/admin-panel/filters/formatDate.js
@@ -0,0 +1,7 @@
+import moment from 'moment';
+
+export default function formatDate (inputDate) {
+ if (!inputDate) return '';
+ const date = moment(inputDate).utcOffset(0).format('YYYY-MM-DD HH:mm');
+ return `${date} UTC`;
+}
diff --git a/website/client/src/components/admin-panel/index.vue b/website/client/src/components/admin-panel/index.vue
new file mode 100644
index 0000000000..acd58c31d2
--- /dev/null
+++ b/website/client/src/components/admin-panel/index.vue
@@ -0,0 +1,81 @@
+
+
+ See error(s) below.
+
+ * The problem should fix itself in about a day.
+ A value of -1 means they owned the Pet but Released it
+ and have not yet rehatched it.
+
+ A value of "null" means they owned the Mount but Released it
+ and have not yet retamed it.
+
+ When there are 0 of these items, we can't tell if
+ they had been owned and were all used, or have never been owned.
+
+ A value of true means they own the item now and can wear it.
+ A value of false means they used to own it but lost it from Death
+ (or an old Rebirth).
+
+ ERROR: User has a Party ID but that Party does not exist.
+ If you are seeing a red error notification on screen now
+ ("Group with id ... not found"), it's refering to this issue.
+
+ Player has had privileges removed or has moderation notes.
+ Admin Panel
+
+
+ Current Avatar Appearance, Drop Count Today
+
+
+
+
@{{ auth.local.username }} / {{ profile.name }}
+ {{ userId }}
+
+ language: {{ preferences.language }}
+
+ Contributor Details
+
+
+ Timestamps, Time Zone, Authentication, Email Address
+ - ERRORS / WARNINGS EXIST
+
+
+
+ ERROR: cron probably crashed before finishing
+ ("auth.timestamps.loggedin" and "lastCron" dates are different).
+
+
+
+
+ ** One of these causes is probably happening if the time zones stay
+ different for more than a day.
+
+ I've given you a new API Token.
+ You'll need to log out of the website and mobile app then log back in
+ otherwise they won't work correctly.
+ If you have trouble logging out, for the website go to
+ https://habitica.com/static/clear-browser-data and click the red button there,
+ and for the Android app, clear its data.
+ For the iOS app, if you can't log out you might need to uninstall it,
+ reboot your phone, then reinstall it.
+ {{ hero.auth.google }}
+ None
+ {{ hero.auth.facebook }}
+ None
+ {{ hero.auth.apple }}
+ None
+ {{ hero.auth }}
+
+ Items
+
+
+
+
+ {{ itemType }}
+
+
+
+
+
+ Party, Quest
+ - ERRORS / WARNINGS EXIST
+
+
Ask a database admin to delete the user's Party ID ({{ userPartyData._id }}).
+
+ User is the party leader
+ Party leader is
+
+ Privileges, Gem Balance
+
+
+ Transactions
+
+