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

Discord slash command code to grant AWS access #276

Merged
merged 36 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fff804c
Discord slash command code to grant AWS access
vikhyat187 Oct 19, 2024
0421825
Merge branch 'develop' into grant-aws-access
vikhyat187 Oct 19, 2024
f3d33e3
added test cases for the discord command
vikhyat187 Oct 19, 2024
50579bf
Merge branch 'Real-Dev-Squad:grant-aws-access' into grant-aws-access
vikhyat187 Oct 19, 2024
87a08d7
Merge pull request #277 from vikhyat187/grant-aws-access
vikhyat187 Oct 19, 2024
b9e8d03
Merge branch 'develop' into grant-aws-access
vikhyat187 Oct 19, 2024
9a958f6
added uuid as types
vikhyat187 Oct 19, 2024
7433c40
Merge branch 'Real-Dev-Squad:grant-aws-access' into grant-aws-access
vikhyat187 Oct 19, 2024
90f46f4
Merge pull request #278 from vikhyat187/grant-aws-access
vikhyat187 Oct 19, 2024
4b218f0
Merge branch 'develop' into grant-aws-access
vikhyat187 Oct 23, 2024
725e491
Revert "remove feature flag (#275)" (#281)
vinit717 Oct 20, 2024
0d5a3fd
removed uuid, code refactoring and fixed test cases post changes
vikhyat187 Oct 25, 2024
7130af6
Merge branch 'Real-Dev-Squad:grant-aws-access' into grant-aws-access
vikhyat187 Oct 25, 2024
49f45f0
Merge pull request #283 from vikhyat187/grant-aws-access
vikhyat187 Oct 25, 2024
75875f6
correcting package.json file
vikhyat187 Oct 25, 2024
fa37aa3
Merge pull request #284 from vikhyat187/grant-aws-access
vikhyat187 Oct 25, 2024
fb649cf
lint fix
vikhyat187 Oct 25, 2024
fd71a85
code refactoring to call the API outside if/else
vikhyat187 Oct 26, 2024
89a8350
Updated the command options to valid one
vikhyat187 Oct 29, 2024
fddc63a
fix test cases
vikhyat187 Oct 29, 2024
2bc33d0
Fixing test case - changing the return type to Promise<void>
vikhyat187 Oct 29, 2024
a9f8156
lint fix
vikhyat187 Oct 29, 2024
ef03bd0
Updated the logic of signing JWT into seperate file
vikhyat187 Nov 1, 2024
1ebb5f2
added documentation for website backend API
vikhyat187 Nov 2, 2024
7b72896
added feature flag to backend API
vikhyat187 Nov 7, 2024
865d751
Merge branch 'develop' into grant-aws-access
vikhyat187 Nov 7, 2024
a5c84a8
Updated the backend route to /aws/groups/access
vikhyat187 Nov 8, 2024
7016a87
Reverted the register command change
vikhyat187 Nov 10, 2024
dd153f3
remove package lock changes
vikhyat187 Nov 10, 2024
7cc0e36
added the group id to config file and added more info to the async pr…
vikhyat187 Nov 11, 2024
cb6770b
remove the unused import
vikhyat187 Nov 11, 2024
b70a6b6
Merge branch 'develop' into grant-aws-access
vikhyat187 Nov 12, 2024
dab6d0f
added feature flag
vikhyat187 Nov 13, 2024
e04b977
Merge branch 'grant-aws-access' of https://github.com/Real-Dev-Squad/…
vikhyat187 Nov 13, 2024
82f1af8
fix the register command, as the value of ENV variable was being pass…
vikhyat187 Nov 13, 2024
3239471
code refactor and made FF mandatory
vikhyat187 Nov 13, 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
9,324 changes: 2,270 additions & 7,054 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"format-fix": "prettier --write .",
"fix": "npm run lint-fix && npm run format-fix",
"ngrok": "ngrok http 8787",
"register": "ts-node-esm src/register.ts"
"register": "npx ts-node src/register.ts"
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
},
"keywords": [],
"author": "",
Expand All @@ -25,6 +25,7 @@
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.2",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"eslint": "^8.31.0",
Expand All @@ -42,7 +43,8 @@
"discord-interactions": "^3.2.0",
"dotenv": "^16.0.3",
"itty-router": "^3.0.11",
"node-fetch": "^3.3.0"
"node-fetch": "^3.3.0",
"uuid": "^10.0.0"
},
"pre-commit": [
"lint-check",
Expand Down
36 changes: 36 additions & 0 deletions src/constants/commands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { config } from "dotenv";
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved

config();

export const HELLO = {
name: "hello",
description: "Replies with hello in the channel",
Expand Down Expand Up @@ -27,6 +31,38 @@ export const GROUP_INVITE = {
},
],
};
export const GRANT_AWS_ACCESS = {
name: "grant-aws-access",
description: "This command is to grant AWS access to the discord users.",
options: [
{
name: "user-name",
description: "User to be granted the AWS access",
type: 6, //user Id to be grant the access
required: true,
},
{
name: "aws-group-name",
description: "AWS group name",
type: 3,
required: true,
choices: [
{
name: "S3 read only access",
value: process.env.S3_READ_ONLY_ACCESS_AWS_GROUP_ID,
},
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
{
name: "EC2 deployment access",
value: process.env.EC2_DEPLOYMENT_ACCESS_AWS_GROUP_ID,
},
{
name: "DDB read only access",
value: process.env.DDB_READ_ONLY_ACCESS_AWS_GROUP_ID,
},
],
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
},
],
};

export const MENTION_EACH = {
name: "mention-each",
Expand Down
1 change: 1 addition & 0 deletions src/constants/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const RDS_BASE_STAGING_API_URL = "https://staging-api.realdevsquad.com";
export const RDS_BASE_DEVELOPMENT_API_URL = "http://localhost:3000"; // If needed, modify the URL to your local API server run through ngrok

export const DISCORD_BASE_URL = "https://discord.com/api/v10";
export const AWS_IAM_SIGNIN_URL = "https://realdevsquad.awsapps.com/start#/";
export const DISCORD_AVATAR_BASE_URL = "https://cdn.discordapp.com/avatars";

export const VERIFICATION_SITE_URL = "https://my.realdevsquad.com";
Expand Down
14 changes: 14 additions & 0 deletions src/controllers/baseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
USER,
REMOVE,
GROUP_INVITE,
GRANT_AWS_ACCESS,
} from "../constants/commands";
import { updateNickName } from "../utils/updateNickname";
import { discordEphemeralResponse } from "../utils/discordEphemeralResponse";
Expand All @@ -44,6 +45,7 @@ import {
import { DevFlag } from "../typeDefinitions/filterUsersByRole";
import { kickEachUser } from "./kickEachUser";
import { groupInvite } from "./groupInvite";
import { grantAWSAccessCommand } from "./grantAWSAccessCommand";

export async function baseHandler(
message: discordMessageRequest,
Expand Down Expand Up @@ -79,6 +81,18 @@ export async function baseHandler(
return await mentionEachUser(transformedArgument, env, ctx);
}

case getCommandName(GRANT_AWS_ACCESS): {
const data = message.data?.options as Array<messageRequestDataOptions>;
const transformedArgument = {
member: message.member,
userDetails: data[0],
awsGroupDetails: data[1],
channelId: message.channel_id,
};

return await grantAWSAccessCommand(transformedArgument, env, ctx);
}

case getCommandName(REMOVE): {
const data = message.data?.options as Array<messageRequestDataOptions>;
const transformedArgument = {
Expand Down
32 changes: 32 additions & 0 deletions src/controllers/grantAWSAccessCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { discordTextResponse } from "../utils/discordResponse";
import { SUPER_USER_ONE, SUPER_USER_TWO } from "../constants/variables";
import { env } from "../typeDefinitions/default.types";
import {
messageRequestMember,
messageRequestDataOptions,
} from "../typeDefinitions/discordMessage.types";
import { grantAWSAccess } from "../utils/awsAccess";

export async function grantAWSAccessCommand(
transformedArgument: {
member: messageRequestMember;
userDetails: messageRequestDataOptions;
awsGroupDetails: messageRequestDataOptions;
channelId: number;
},
env: env,
ctx: ExecutionContext
) {
const isUserSuperUser = [SUPER_USER_ONE, SUPER_USER_TWO].includes(
samarpan1738 marked this conversation as resolved.
Show resolved Hide resolved
transformedArgument.member.user.id.toString()
);
if (!isUserSuperUser) {
const responseText = `You're not authorized to make this request.`;
return discordTextResponse(responseText);
}
const roleId = transformedArgument.userDetails.value;
const groupId = transformedArgument.awsGroupDetails.value;
const channelId = transformedArgument.channelId;

return grantAWSAccess(roleId, groupId, env, ctx, channelId);
}
2 changes: 2 additions & 0 deletions src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
USER,
REMOVE,
GROUP_INVITE,
GRANT_AWS_ACCESS,
} from "./constants/commands";
import { config } from "dotenv";
import { DISCORD_BASE_URL } from "./constants/urls";
Expand Down Expand Up @@ -41,6 +42,7 @@ async function registerGuildCommands(
NOTIFY_ONBOARDING,
REMOVE,
GROUP_INVITE,
GRANT_AWS_ACCESS,
];

try {
Expand Down
96 changes: 96 additions & 0 deletions src/utils/awsAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import jwt from "@tsndr/cloudflare-worker-jwt";
import { v4 as uuidv4 } from "uuid";
import { env } from "../typeDefinitions/default.types";
import config from "../../config/config";
import { discordTextResponse } from "./discordResponse";
import { DISCORD_BASE_URL, AWS_IAM_SIGNIN_URL } from "../constants/urls";

export async function processAWSAccessRequest(
discordUserId: string,
awsGroupId: string,
env: env,
TraceId: string,
channelId: number
) {
const authToken = await jwt.sign(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we already have this? why write it again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't get this comment, we are using this in similar manner in one of the other API too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So at every place we are manually signing, So don't already have a common thing to do this, if not then please create one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have created a common utility for this.

{ name: "Cloudflare Worker", exp: Math.floor(Date.now() / 1000) + 2 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the name here?

Copy link
Contributor Author

@vikhyat187 vikhyat187 Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the website backend, we check if the auth token has the name "cloudfare workers" to validate the token coming from right source.

Ref : https://github.com/Real-Dev-Squad/website-backend/blob/f8d76e1936647759ef7fdfd513f7ff03de226346/middlewares/authorizeBot.js#L18

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is a string helping in validating the right source?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we create a token in the discord slash commands with name present in payload, this is signed by using the private key and in website backend we validate if the same text is being received post validating the token.
@prakashchoudhary07

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to discuss on this one

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env.BOT_PRIVATE_KEY,
{ algorithm: "RS256" }
);

try {
const base_url = config(env).RDS_BASE_API_URL;
const requestData = {
groupId: awsGroupId,
userId: discordUserId,
};

const url = `${base_url}/aws-access/`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create the URL in constant it self?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And please share the doc for using it herE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we mean it like
const url = ${base_url}/aws-access this way we have it stored?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get this which doc?

And please share the doc for using it herE?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please share the AWS API docs like, of what they are doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is our website backend API, will add the comments there.


const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(requestData),
});

if (!response.ok) {
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${env.DISCORD_TOKEN}`,
},
body: JSON.stringify({
content: `<@${discordUserId}> Error occurred while granting AWS access: ${response.status} ${response.statusText}`,
}),
});
} else {
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${env.DISCORD_TOKEN}`,
},
body: JSON.stringify({
content: `AWS access granted successfully <@${discordUserId}>! Please head over to AWS - ${AWS_IAM_SIGNIN_URL}.`,
}),
});
}
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${env.DISCORD_TOKEN}`,
},
body: JSON.stringify({
content: `[TraceId: ${TraceId}] <@${discordUserId}> Error occurred while granting AWS access.`,
}),
});
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
}
}

export async function grantAWSAccess(
discordUserId: string,
awsGroupId: string,
env: env,
ctx: ExecutionContext,
channelId: number
) {
const TraceId = uuidv4();
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
// Immediately send a Discord response to acknowledge the command
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
const initialResponse = discordTextResponse(
`[TraceId: ${TraceId}] <@${discordUserId}> Processing your request to grant AWS access.`
);

ctx.waitUntil(
// Asynchronously call the function to grant AWS access
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
processAWSAccessRequest(discordUserId, awsGroupId, env, TraceId, channelId)
);

// Return the immediate response within 3 seconds
vikhyat187 marked this conversation as resolved.
Show resolved Hide resolved
return initialResponse;
}
Loading
Loading