Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add purpose field and make discord invite API be able to generate invites n times by superusers #2218

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
44328ad
feat: add purpose field in generate discord invite for user with feat…
devdeadviz Oct 18, 2024
ddfc548
feat: add functionality to be able to create n times discord invites …
devdeadviz Oct 22, 2024
2eab530
Merge branch 'develop' into feat/add-purpose-in-discord-api
devdeadviz Oct 22, 2024
becf40e
feat: add unit tests for generateInviteForUser
devdeadviz Oct 25, 2024
9f7dc73
feat: add joi validation for generateInviteForUser function body
devdeadviz Oct 25, 2024
9545994
Merge branch 'develop' into feat/add-purpose-in-discord-api
Achintya-Chatterjee Oct 25, 2024
bdd12a3
Merge branch 'develop' into feat/add-purpose-in-discord-api
devdeadviz Oct 28, 2024
99a8364
Merge branch 'develop' into feat/add-purpose-in-discord-api
Achintya-Chatterjee Oct 29, 2024
ef00ad8
fix: add suggested changes
devdeadviz Oct 30, 2024
a3b15c4
feat(e2e): add integrations test for discord-actions invite api
devdeadviz Oct 30, 2024
84d0960
chore: remove creation of random id while storing discord invite to d…
devdeadviz Oct 30, 2024
3cc2444
refactor: getUserDiscordInvite query to fetch purpose when dev is true
devdeadviz Oct 30, 2024
5734b0c
Merge branch 'develop' into feat/add-purpose-in-discord-api
devdeadviz Nov 22, 2024
470399b
refactor: remove non needed else block
devdeadviz Nov 25, 2024
13f5a73
refactor: add status code in fetch invite api
devdeadviz Nov 26, 2024
f63f564
fix: dev condition to only work in truthy case
devdeadviz Nov 26, 2024
3c0a55f
Merge branch 'develop' into feat/add-purpose-in-discord-api
devdeadviz Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 39 additions & 18 deletions controllers/discordactions.js
devdeadviz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@
const nickNameUpdatedUsers = [];
let counter = 0;
for (let i = 0; i < usersToBeEffected.length; i++) {
const { discordId, username, first_name: firstName } = usersToBeEffected[i];

Check warning on line 289 in controllers/discordactions.js

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Variable Assigned to Object Injection Sink
try {
if (counter % 10 === 0 && counter !== 0) {
await new Promise((resolve) => setTimeout(resolve, 5500));
Expand All @@ -302,7 +302,7 @@
if (message) {
counter++;
totalNicknamesUpdated.count++;
nickNameUpdatedUsers.push(usersToBeEffected[i].id);

Check warning on line 305 in controllers/discordactions.js

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Generic Object Injection Sink
}
}
} catch (error) {
Expand Down Expand Up @@ -409,15 +409,17 @@

const generateInviteForUser = async (req, res) => {
try {
const { userId } = req.query;
const { userId, dev } = req.query;
const userIdForInvite = userId || req.userData.id;
let inviteLink = "";

const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite);

if (!modelResponse.notFound) {
return res.status(409).json({
message: "User invite is already present!",
});
if (!dev) {
devdeadviz marked this conversation as resolved.
Show resolved Hide resolved
const modelResponse = await discordRolesModel.getUserDiscordInvite(userIdForInvite);
if (!modelResponse.notFound) {
return res.status(409).json({
message: "User invite is already present!",
});
}
}

const channelId = config.get("discordNewComersChannelId");
Expand All @@ -437,14 +439,25 @@
const discordInviteResponse = await response.json();

const inviteCode = discordInviteResponse.data.code;
const inviteLink = `discord.gg/${inviteCode}`;
inviteLink = `discord.gg/${inviteCode}`;

await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink });
if (dev) {
const purpose = req.body.purpose;
await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink, purpose });

return res.status(201).json({
message: "invite generated successfully",
inviteLink,
});
return res.status(201).json({
message: "invite generated successfully",
inviteLink,
purpose,
});
} else {
await discordRolesModel.addInviteToInviteModel({ userId: userIdForInvite, inviteLink });

return res.status(201).json({
message: "invite generated successfully",
inviteLink,
});
}
devdeadviz marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
logger.error(`Error in generating invite for user: ${err}`);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
Expand All @@ -453,7 +466,7 @@

const getUserDiscordInvite = async (req, res) => {
try {
const { userId } = req.query;
const { userId, dev } = req.query;
const isSuperUser = req.userData.roles.super_user;

if (userId && !isSuperUser) return res.boom.forbidden("User should be super user to get link for other users");
Expand All @@ -466,10 +479,18 @@
return res.boom.notFound("User invite doesn't exist");
}

return res.json({
message: "Invite returned successfully",
inviteLink: invite?.inviteLink,
});
if (dev) {
return res.json({
message: "Invite returned successfully",
inviteLink: invite?.inviteLink,
purpose: invite?.purpose,
});
} else {
return res.json({
message: "Invite returned successfully",
inviteLink: invite?.inviteLink,
});
}
devdeadviz marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
logger.error(`Error in fetching user invite: ${err}`);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
Expand Down
16 changes: 16 additions & 0 deletions middlewares/validators/discordactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,24 @@ const validateUpdateUsersNicknameStatusBody = async (req, res, next) => {
}
};

export const validateGenerateInviteForUserBody = async (req, res, next) => {
const schema = Joi.object().strict().keys({
purpose: Joi.string().trim(),
});

try {
await schema.validateAsync(req.body);
next();
} catch (error) {
const errorMessages = error.details.map((detail) => detail.message);
logger.error(`Error while validating invite creation payload: ${errorMessages}`);
res.boom.badRequest(errorMessages);
devdeadviz marked this conversation as resolved.
Show resolved Hide resolved
}
};

module.exports = {
validateGroupRoleBody,
validateMemberRoleBody,
validateUpdateUsersNicknameStatusBody,
validateGenerateInviteForUserBody,
};
9 changes: 8 additions & 1 deletion routes/discordactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
validateGroupRoleBody,
validateMemberRoleBody,
validateUpdateUsersNicknameStatusBody,
validateGenerateInviteForUserBody,
} = require("../middlewares/validators/discordactions");
const checkIsVerifiedDiscord = require("../middlewares/verifydiscord");
const checkCanGenerateDiscordLink = require("../middlewares/checkCanGenerateDiscordLink");
Expand All @@ -36,7 +37,13 @@
router.get("/groups", authenticate, checkIsVerifiedDiscord, getAllGroupRoles);
router.post("/roles", authenticate, checkIsVerifiedDiscord, validateMemberRoleBody, addGroupRoleToMember);
router.get("/invite", authenticate, getUserDiscordInvite);
router.post("/invite", authenticate, checkCanGenerateDiscordLink, generateInviteForUser);
router.post(
"/invite",
authenticate,
Fixed Show fixed Hide fixed

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
This route handler performs
authorization
, but is not rate-limited.
checkCanGenerateDiscordLink,
validateGenerateInviteForUserBody,
generateInviteForUser
);
router.delete("/roles", authenticate, checkIsVerifiedDiscord, deleteRole);
router.get("/roles", authenticate, checkIsVerifiedDiscord, getGroupsRoleId);
router.patch(
Expand Down
21 changes: 21 additions & 0 deletions test/integration/discordactions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,27 @@ describe("Discord actions", function () {
expect(res.body.message).to.be.equal("User should be super user to generate link for other users");
});

it("Should return the invite and the purpose for other user if the dev=true is provided in the query and the user is super user", async function () {
fetchStub.returns(
Promise.resolve({
status: 201,
json: () => Promise.resolve({ data: { code: "xyz" } }),
})
);

const res = await chai
.request(app)
.post(`/discord-actions/invite?dev=true`)
.set("cookie", `${cookieName}=${superUserAuthToken}`)
.send({
purpose: "testing",
});
expect(res).to.have.status(201);
expect(res.body.message).to.be.equal("invite generated successfully");
expect(res.body.inviteLink).to.be.equal("discord.gg/xyz");
expect(res.body.purpose).to.be.equal("testing");
});

// eslint-disable-next-line mocha/no-skipped-tests
it.skip("should return 403 if the user has discord id in their user object, which means user is already in discord", async function () {
const res = await chai
Expand Down
31 changes: 31 additions & 0 deletions test/unit/middlewares/discordactions-validators.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const {
validateGroupRoleBody,
validateMemberRoleBody,
validateUpdateUsersNicknameStatusBody,
validateGenerateInviteForUserBody,
} = require("../../../middlewares/validators/discordactions");
const { expect } = require("chai");

Expand Down Expand Up @@ -127,4 +128,34 @@ describe("Middleware | Validators | discord actions", function () {
expect(nextSpy.callCount).to.be.equal(0);
});
});

describe("validateGenerateInviteForUserBody", function () {
it("lets the request pass to the next function", async function () {
const res = {};
const req = {
body: {
purpose: "testing",
},
};
const nextSpy = Sinon.spy();
await validateGenerateInviteForUserBody(req, res, nextSpy);
expect(nextSpy.calledOnce).to.be.equal(true);
});

it("stops the propogation to the next function", async function () {
const res = {
boom: {
badRequest: () => {},
},
};
const nextSpy = Sinon.spy();
const req = {
body: {},
};
await validateMemberRoleBody(req, res, nextSpy).catch((err) => {
expect(err).to.be.an.instanceOf(Error);
});
expect(nextSpy.callCount).to.be.equal(0);
});
});
});
49 changes: 49 additions & 0 deletions test/unit/models/discordactions.test.js
devdeadviz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { generateDiscordInviteLink } from "../../../utils/discord-actions";
const chai = require("chai");
const expect = chai.expect;
const sinon = require("sinon");
Expand Down Expand Up @@ -802,6 +803,54 @@ describe("discordactions", function () {
});
});

describe("generateInviteForUser", function () {
let fetchStub;

beforeEach(async function () {
fetchStub = sinon.stub(global, "fetch");
});

afterEach(function () {
fetchStub.restore();
});

it("should return the invite link and purpose", async function () {
const inviteLink = "discord.gg/xyz";
const discordInviteLink = {
data: {
code: "xyz",
},
};
fetchStub.resolves({
ok: true,
json: () => discordInviteLink,
});

const result = await generateDiscordInviteLink();
expect(result).to.be.equal(inviteLink);

const inviteObject = { userId: "123456", inviteLink: "discord.gg/xyz", purpose: "testing" };
await addInviteToInviteModel(inviteObject);

const invite = await getUserDiscordInvite("123456");
expect(invite).to.have.property("id");
expect(invite.notFound).to.be.equal(false);
expect(invite.userId).to.be.equal("123456");
expect(invite.inviteLink).to.be.equal("discord.gg/xyz");
expect(invite.purpose).to.be.equal("testing");
});

it("should resolve with an error", async function () {
const error = new Error("Error");
fetchStub.rejects(error);
try {
await generateDiscordInviteLink();
} catch (err) {
expect(err).to.be.equal(error);
}
});
});

describe("groupUpdateLastJoinDate", function () {
beforeEach(function () {
sinon.stub(discordRoleModel, "doc").returns({
Expand Down
Loading