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

Dev to main sync #1859

Merged
merged 3 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 8 additions & 10 deletions controllers/discordactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
const discordRolesModel = require("../models/discordactions");
const discordServices = require("../services/discordService");
const { fetchAllUsers, fetchUser } = require("../models/users");
const { generateCloudFlareHeaders } = require("../utils/discord-actions");
const discordDeveloperRoleId = config.get("discordDeveloperRoleId");
const discordMavenRoleId = config.get("discordMavenRoleId");

Expand Down Expand Up @@ -39,15 +40,13 @@
createdBy: req.userData.id,
date: admin.firestore.Timestamp.fromDate(new Date()),
};
const authToken = jwt.sign({}, config.get("rdsServerlessBot.rdsServerLessPrivateKey"), {
algorithm: "RS256",
expiresIn: config.get("rdsServerlessBot.ttl"),
});

const headers = generateCloudFlareHeaders(req.userData);

const responseForCreatedRole = await fetch(`${DISCORD_BASE_URL}/roles/create`, {
method: "PUT",
body: JSON.stringify(dataForDiscord),
headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` },
headers,
}).then((response) => response.json());

groupRoleData.roleid = responseForCreatedRole.id;
Expand Down Expand Up @@ -132,14 +131,12 @@
const dataForDiscord = {
...req.body,
};
const authToken = jwt.sign({}, config.get("rdsServerlessBot.rdsServerLessPrivateKey"), {
algorithm: "RS256",
expiresIn: config.get("rdsServerlessBot.ttl"),
});
const headers = generateCloudFlareHeaders(req.userData);

const apiCallToDiscord = fetch(`${DISCORD_BASE_URL}/roles/add`, {
method: "PUT",
body: JSON.stringify(dataForDiscord),
headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` },
headers,
});
const discordLastJoinedDateUpdate = discordRolesModel.groupUpdateLastJoinDate({
id: existingRoles.docs[0].id,
Expand Down Expand Up @@ -168,6 +165,7 @@
if (!roleExists || req.userData.id !== userData.user.id) {
res.boom.forbidden("Permission denied. Cannot delete the role.");
}
await discordServices.removeRoleFromUser(roleid, userid, req.userData);

const { wasSuccess } = await discordRolesModel.removeMemberGroup(roleid, userid);
if (wasSuccess) {
Expand Down Expand Up @@ -285,7 +283,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 286 in controllers/discordactions.js

View workflow job for this annotation

GitHub Actions / build (18.x)

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

Check warning on line 302 in controllers/discordactions.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Generic Object Injection Sink
}
}
} catch (error) {
Expand Down
8 changes: 2 additions & 6 deletions models/discordactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,12 @@

const removeMemberGroup = async (roleId, discordId) => {
try {
const discordResponse = await removeRoleFromUser(roleId, discordId);
if (discordResponse) {
const backendResponse = await deleteRoleFromDatabase(roleId, discordId);
return backendResponse;
}
const backendResponse = await deleteRoleFromDatabase(roleId, discordId);
return backendResponse;
} catch (error) {
logger.error(`Error while removing role: ${error}`);
throw new Error(error);
}
return false;
};

const deleteRoleFromDatabase = async (roleId, discordId) => {
Expand Down Expand Up @@ -494,7 +490,7 @@

for (let i = 0; i < nicknameUpdateBatches.length; i++) {
const promises = [];
const usersStatusDocsBatch = nicknameUpdateBatches[i];

Check warning on line 493 in models/discordactions.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Variable Assigned to Object Injection Sink
usersStatusDocsBatch.forEach((document) => {
const doc = document.data();
const userId = doc.userId;
Expand Down
8 changes: 4 additions & 4 deletions services/discordService.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const firestore = require("../utils/firestore");
const { fetchAllUsers } = require("../models/users");
const { generateAuthTokenForCloudflare } = require("../utils/discord-actions");
const { generateAuthTokenForCloudflare, generateCloudFlareHeaders } = require("../utils/discord-actions");
const userModel = firestore.collection("users");
const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl");

Expand Down Expand Up @@ -70,12 +70,12 @@ const addRoleToUser = async (userid, roleid) => {
return response;
};

const removeRoleFromUser = async (roleId, discordId) => {
const removeRoleFromUser = async (roleId, discordId, userData) => {
try {
const authToken = generateAuthTokenForCloudflare();
const headers = generateCloudFlareHeaders(userData);
const data = await fetch(`${DISCORD_BASE_URL}/roles`, {
method: "DELETE",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` },
headers,
body: JSON.stringify({ userid: discordId, roleid: roleId }),
});
const response = await data.json();
Expand Down
10 changes: 7 additions & 3 deletions services/githubService.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,15 @@ const fetchLastMergedPR = async (username) => {
const res = await fetch(createdURL, { headers });

if (!res.ok) {
const errorText = await res.text();
throw new Error(`GitHub API request failed: ${errorText}`);
logger.error(`GitHub API request failed. Status: ${res.status}, URL: ${createdURL}`);
return null;
}

const data = await res.json();

if (!data || !data.items || !data.items.length) {
throw new Error(`No merged PRs found for user ${username}`);
logger.error(`No merged PRs found for user ${username}`);
return null;
}

return data;
Expand All @@ -336,6 +337,9 @@ const fetchLastMergedPR = async (username) => {
const isLastPRMergedWithinDays = async (username, days) => {
try {
const res = await fetchLastMergedPR(username);
if (!res) {
return false;
}
const mergedAt = res.items[0].pull_request.merged_at;
const lastPRMergedDate = new Date(mergedAt);
const currentDate = new Date();
Expand Down
42 changes: 42 additions & 0 deletions test/integration/discordactions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,28 @@ describe("Discord actions", function () {
expect(res.body).to.be.an("object");
expect(res.body.message).to.equal("Role added successfully!");
});
it("should create a reason and pass it down to the bot, on adding the role to the user", async function () {
fetchStub.returns(
Promise.resolve({
status: 200,
json: () => Promise.resolve({}),
})
);

const body = { roleid, userid: userData[0].discordId };
const res = await chai
.request(app)
.post("/discord-actions/roles")
.set("cookie", `${cookieName}=${jwt}`)
.send(body);

expect(res).to.have.status(201);
expect(res.body).to.be.an("object");
expect(res.body.message).to.equal("Role added successfully!");
expect(fetchStub.getCall(0).args[1].headers["X-Audit-Log-Reason"]).to.equal(
`Action initiator's username=>ankur and id=${userId}`
);
});
it("should not allow unknown role to be added to user", async function () {
const res = await chai
.request(app)
Expand Down Expand Up @@ -304,6 +326,26 @@ describe("Discord actions", function () {
});
});

it("should create a reason and pass it down to the bot on deleting the role", async function () {
fetchStub.returns(
Promise.resolve({
status: 200,
json: () => Promise.resolve({ roleId: "1234", wasSuccess: true }),
})
);
const res = await chai
.request(app)
.delete("/discord-actions/roles")
.set("cookie", `${cookieName}=${jwt}`)
.send({ roleid, userid: userData[0].discordId });

expect(res).to.have.status(200);
expect(res.body).to.be.an("object");
expect(res.body.message).to.equal("Role deleted successfully");
expect(fetchStub.getCall(0).args[1].headers["X-Audit-Log-Reason"]).to.equal(
`Action initiator's username=>ankur and id=${userId}`
);
});
it("should not allow unknown role to be deleted from user", async function () {
const res = await chai
.request(app)
Expand Down
16 changes: 16 additions & 0 deletions test/unit/utils/genrateCloudFlareHeaders.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { expect } = require("chai");
const { generateCloudFlareHeaders } = require("../../../utils/discord-actions");

describe("generateCloudFlareHeaders", function () {
it("generates headers with property Content-Type and Authorization", function () {
const data = generateCloudFlareHeaders();
expect(data["Content-Type"]).to.be.eq("application/json");
expect(data.Authorization).to.include("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9");
});
it("generates headers with property Content-Type and Authorization and X-Audit-Log-Reason when id and userName is passed", function () {
const data = generateCloudFlareHeaders({ id: "id", username: "userName" });
expect(data["Content-Type"]).to.be.eq("application/json");
expect(data.Authorization).to.include("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9");
expect(data["X-Audit-Log-Reason"]).to.be.eq("Action initiator's username=>userName and id=id");
});
});
12 changes: 12 additions & 0 deletions utils/discord-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ const generateAuthTokenForCloudflare = () => {
});
return authToken;
};
const generateCloudFlareHeaders = ({ username, id } = {}) => {
const authToken = generateAuthTokenForCloudflare();
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
};
if (username && id) {
headers["X-Audit-Log-Reason"] = `Action initiator's username=>${username} and id=${id}`;
}
return headers;
};

const generateDiscordProfileImageUrl = async (discordId) => {
try {
Expand Down Expand Up @@ -63,5 +74,6 @@ const generateDiscordInviteLink = async () => {
module.exports = {
generateDiscordProfileImageUrl,
generateAuthTokenForCloudflare,
generateCloudFlareHeaders,
generateDiscordInviteLink,
};
Loading