Skip to content

Commit fff804c

Browse files
committed
Discord slash command code to grant AWS access
1 parent bf79d4c commit fff804c

File tree

8 files changed

+2467
-7073
lines changed

8 files changed

+2467
-7073
lines changed

package-lock.json

Lines changed: 2279 additions & 7071 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"format-fix": "prettier --write .",
1616
"fix": "npm run lint-fix && npm run format-fix",
1717
"ngrok": "ngrok http 8787",
18-
"register": "ts-node-esm src/register.ts"
18+
"register": "npx ts-node src/register.ts"
1919
},
2020
"keywords": [],
2121
"author": "",
@@ -42,7 +42,8 @@
4242
"discord-interactions": "^3.2.0",
4343
"dotenv": "^16.0.3",
4444
"itty-router": "^3.0.11",
45-
"node-fetch": "^3.3.0"
45+
"node-fetch": "^3.3.0",
46+
"uuid": "^10.0.0"
4647
},
4748
"pre-commit": [
4849
"lint-check",

src/constants/commands.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { config } from "dotenv";
2+
3+
config();
4+
15
export const HELLO = {
26
name: "hello",
37
description: "Replies with hello in the channel",
@@ -27,6 +31,38 @@ export const GROUP_INVITE = {
2731
},
2832
],
2933
};
34+
export const GRANT_AWS_ACCESS = {
35+
name: "grant-aws-access",
36+
description: "This command is to grant AWS access to the discord users.",
37+
options: [
38+
{
39+
name: "user-name",
40+
description: "User to be granted the AWS access",
41+
type: 6, //user Id to be grant the access
42+
required: true,
43+
},
44+
{
45+
name: "aws-group-name",
46+
description: "AWS group name",
47+
type: 3,
48+
required: true,
49+
choices: [
50+
{
51+
name: "S3 read only access",
52+
value: process.env.S3_READ_ONLY_ACCESS_AWS_GROUP_ID,
53+
},
54+
{
55+
name: "EC2 deployment access",
56+
value: process.env.EC2_DEPLOYMENT_ACCESS_AWS_GROUP_ID,
57+
},
58+
{
59+
name: "DDB read only access",
60+
value: process.env.DDB_READ_ONLY_ACCESS_AWS_GROUP_ID,
61+
},
62+
],
63+
},
64+
],
65+
};
3066

3167
export const MENTION_EACH = {
3268
name: "mention-each",

src/constants/urls.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const RDS_BASE_STAGING_API_URL = "https://staging-api.realdevsquad.com";
33
export const RDS_BASE_DEVELOPMENT_API_URL = "http://localhost:3000"; // If needed, modify the URL to your local API server run through ngrok
44

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

89
export const VERIFICATION_SITE_URL = "https://my.realdevsquad.com";

src/controllers/baseHandler.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
USER,
3030
REMOVE,
3131
GROUP_INVITE,
32+
GRANT_AWS_ACCESS,
3233
} from "../constants/commands";
3334
import { updateNickName } from "../utils/updateNickname";
3435
import { discordEphemeralResponse } from "../utils/discordEphemeralResponse";
@@ -44,6 +45,7 @@ import {
4445
import { DevFlag } from "../typeDefinitions/filterUsersByRole";
4546
import { kickEachUser } from "./kickEachUser";
4647
import { groupInvite } from "./groupInvite";
48+
import { grantAWSAccessCommand } from "./grantAWSAccessCommand";
4749

4850
export async function baseHandler(
4951
message: discordMessageRequest,
@@ -79,6 +81,18 @@ export async function baseHandler(
7981
return await mentionEachUser(transformedArgument, env, ctx);
8082
}
8183

84+
case getCommandName(GRANT_AWS_ACCESS): {
85+
const data = message.data?.options as Array<messageRequestDataOptions>;
86+
const transformedArgument = {
87+
member: message.member,
88+
userDetails: data[0],
89+
awsGroupDetails: data[1],
90+
channelId: message.channel_id,
91+
};
92+
93+
return await grantAWSAccessCommand(transformedArgument, env, ctx);
94+
}
95+
8296
case getCommandName(REMOVE): {
8397
const data = message.data?.options as Array<messageRequestDataOptions>;
8498
const transformedArgument = {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { discordTextResponse } from "../utils/discordResponse";
2+
import { SUPER_USER_ONE, SUPER_USER_TWO } from "../constants/variables";
3+
import { env } from "../typeDefinitions/default.types";
4+
import {
5+
messageRequestMember,
6+
messageRequestDataOptions,
7+
} from "../typeDefinitions/discordMessage.types";
8+
import { grantAWSAccess } from "../utils/awsAccess";
9+
10+
export async function grantAWSAccessCommand(
11+
transformedArgument: {
12+
member: messageRequestMember;
13+
userDetails: messageRequestDataOptions;
14+
awsGroupDetails: messageRequestDataOptions;
15+
channelId: number;
16+
},
17+
env: env,
18+
ctx: ExecutionContext
19+
) {
20+
const isUserSuperUser = [SUPER_USER_ONE, SUPER_USER_TWO].includes(
21+
transformedArgument.member.user.id.toString()
22+
);
23+
if (!isUserSuperUser) {
24+
const responseText = `You're not authorized to make this request.`;
25+
return discordTextResponse(responseText);
26+
}
27+
const roleId = transformedArgument.userDetails.value;
28+
const groupId = transformedArgument.awsGroupDetails.value;
29+
const channelId = transformedArgument.channelId;
30+
31+
return grantAWSAccess(roleId, groupId, env, ctx, channelId);
32+
}

src/register.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
USER,
1111
REMOVE,
1212
GROUP_INVITE,
13+
GRANT_AWS_ACCESS,
1314
} from "./constants/commands";
1415
import { config } from "dotenv";
1516
import { DISCORD_BASE_URL } from "./constants/urls";
@@ -41,6 +42,7 @@ async function registerGuildCommands(
4142
NOTIFY_ONBOARDING,
4243
REMOVE,
4344
GROUP_INVITE,
45+
GRANT_AWS_ACCESS,
4446
];
4547

4648
try {

src/utils/awsAccess.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import jwt from "@tsndr/cloudflare-worker-jwt";
2+
import { v4 as uuidv4 } from "uuid";
3+
import { env } from "../typeDefinitions/default.types";
4+
import config from "../../config/config";
5+
import { discordTextResponse } from "./discordResponse";
6+
import { DISCORD_BASE_URL, AWS_IAM_SIGNIN_URL } from "../constants/urls";
7+
8+
async function processAWSAccessRequest(
9+
discordUserId: string,
10+
awsGroupId: string,
11+
env: env,
12+
TraceId: uuidv4,
13+
channelId: number
14+
) {
15+
const authToken = await jwt.sign(
16+
{ name: "Cloudflare Worker", exp: Math.floor(Date.now() / 1000) + 2 },
17+
env.BOT_PRIVATE_KEY,
18+
{ algorithm: "RS256" }
19+
);
20+
21+
try {
22+
const base_url = config(env).RDS_BASE_API_URL;
23+
const requestData = {
24+
groupId: awsGroupId,
25+
userId: discordUserId,
26+
};
27+
28+
const url = `${base_url}/aws-access/`;
29+
30+
const response = await fetch(url, {
31+
method: "POST",
32+
headers: {
33+
"Content-Type": "application/json",
34+
Authorization: `Bearer ${authToken}`,
35+
},
36+
body: JSON.stringify(requestData),
37+
});
38+
39+
if (!response.ok) {
40+
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
41+
method: "POST",
42+
headers: {
43+
"Content-Type": "application/json",
44+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
45+
},
46+
body: JSON.stringify({
47+
content: `<@${discordUserId}> Error occurred while granting AWS access: ${response.status} ${response.statusText}`,
48+
}),
49+
});
50+
} else {
51+
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
52+
method: "POST",
53+
headers: {
54+
"Content-Type": "application/json",
55+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
56+
},
57+
body: JSON.stringify({
58+
content: `AWS access granted successfully <@${discordUserId}>! Please head over to AWS - ${AWS_IAM_SIGNIN_URL}.`,
59+
}),
60+
});
61+
}
62+
} catch (err) {
63+
console.log(
64+
`[TraceId: ${TraceId}] Failed to grant AWS Access, error - `,
65+
err
66+
);
67+
return fetch(`${DISCORD_BASE_URL}/channels/${channelId}/messages`, {
68+
method: "POST",
69+
headers: {
70+
"Content-Type": "application/json",
71+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
72+
},
73+
body: JSON.stringify({
74+
content: `[TraceId: ${TraceId}] <@${discordUserId}> Error occurred while granting AWS access.`,
75+
}),
76+
});
77+
}
78+
}
79+
80+
export async function grantAWSAccess(
81+
discordUserId: string,
82+
awsGroupId: string,
83+
env: env,
84+
ctx: ExecutionContext,
85+
channelId: number
86+
) {
87+
const TraceId = uuidv4();
88+
// Immediately send a Discord response to acknowledge the command
89+
const initialResponse = discordTextResponse(
90+
`[TraceId: ${TraceId}] <@${discordUserId}> Processing your request to grant AWS access.`
91+
);
92+
93+
ctx.waitUntil(
94+
// Asynchronously call the function to grant AWS access
95+
processAWSAccessRequest(discordUserId, awsGroupId, env, TraceId, channelId)
96+
);
97+
98+
// Return the immediate response within 3 seconds
99+
return initialResponse;
100+
}

0 commit comments

Comments
 (0)