Skip to content

Commit

Permalink
Merge pull request #33 from RappyLabyAddons/feat/adminActions
Browse files Browse the repository at this point in the history
Allow admins to perform actions
  • Loading branch information
RappyTV committed Jun 19, 2024
2 parents 25a0ca3 + 6a50b93 commit dd697d9
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 75 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
"max": 5,
"seconds": 60
},
{
"method": "POST",
"regex": "\/players\/\\w+\/admin/?",
"max": 2,
"seconds": 30
},
{
"method": "POST",
"regex": "\/players\/\\w+/position\/?",
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "globaltags",
"version": "1.2.3",
"version": "1.2.4",
"module": "src/index.js",
"scripts": {
"dev": "bun --watch src/index.ts",
Expand All @@ -14,12 +14,10 @@
"dependencies": {
"@elysiajs/swagger": "^1.0.3",
"axios": "^1.6.7",
"body-parser": "^1.20.2",
"chalk": "^5.3.0",
"cron": "^3.1.7",
"discord.js": "^14.14.1",
"elysia": "^1.0.10",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.1",
"moment": "^2.29.4",
"mongoose": "latest"
Expand Down
16 changes: 10 additions & 6 deletions src/bot/buttons/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ export default class Actions extends Button {
.setCustomId(`ban`)
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setLabel(`Clear tag`)
.setCustomId(`clearTag`)
.setStyle(ButtonStyle.Danger)
.setLabel(`Unban`)
.setCustomId(`unban`)
.setStyle(ButtonStyle.Success)
),
new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setLabel(`Unban`)
.setCustomId(`unban`)
.setStyle(ButtonStyle.Success)
.setLabel(`Clear tag`)
.setCustomId(`clearTag`)
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setLabel(`Set tag`)
.setCustomId(`setTag`)
.setStyle(ButtonStyle.Primary)
)
]

Expand Down
37 changes: 37 additions & 0 deletions src/bot/buttons/SetTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ButtonInteraction, CacheType, Message, GuildMember, User, EmbedBuilder, ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
import Button from "../structs/Button";
import players from "../../database/schemas/players";
import { validation } from "../../../config.json";
import { colors } from "../bot";

export default class SetTag extends Button {
constructor() {
super("setTag");
}

async trigger(interaction: ButtonInteraction<CacheType>, message: Message<boolean>, member: GuildMember, user: User) {
const player = await players.findOne({ uuid: message.embeds[0].fields[0].value.replaceAll(`\``, ``) });
if(!player) return interaction.reply({ embeds: [new EmbedBuilder().setColor(colors.error).setDescription(`❌ Player not found!`)], ephemeral: true });

const input = new TextInputBuilder()
.setLabel(`New tag`)
.setCustomId(`tag`)
.setPlaceholder(`Enter a tag`)
.setRequired(true)
.setMinLength(validation.tag.min)
.setMaxLength(validation.tag.max)
.setStyle(TextInputStyle.Short);

if(player.tag) input.setValue(player.tag);

const modal = new ModalBuilder()
.setTitle(`Set new tag`)
.setCustomId(`setTag`)
.addComponents(
new ActionRowBuilder<TextInputBuilder>()
.addComponents(input)
)

interaction.showModal(modal);
}
}
20 changes: 20 additions & 0 deletions src/bot/modals/SetTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CacheType, Message, GuildMember, User, EmbedBuilder, ModalSubmitInteraction, ModalSubmitFields } from "discord.js";
import players from "../../database/schemas/players";
import { colors } from "../bot";
import Modal from "../structs/Modal";

export default class SetTag extends Modal {
constructor() {
super("setTag");
}

async submit(interaction: ModalSubmitInteraction<CacheType>, message: Message<boolean>, fields: ModalSubmitFields, member: GuildMember, user: User) {
const player = await players.findOne({ uuid: message.embeds[0].fields[0].value.replaceAll(`\``, ``) });
if(!player) return interaction.reply({ embeds: [new EmbedBuilder().setColor(colors.error).setDescription(`❌ Player not found!`)], ephemeral: true });

player.tag = fields.getTextInputValue('tag');
player.save();

interaction.reply({ embeds: [new EmbedBuilder().setColor(colors.success).setDescription(`✅ The tag was successfully set!`)], ephemeral: true });
}
}
19 changes: 16 additions & 3 deletions src/libs/SessionValidator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { verify } from "jsonwebtoken";
import players from "../database/schemas/players";
const publicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt3rCKqrQYcmSEE8zyQTA7flKIe1pr7GHY58lTF74Pw/ZZYzxmScYteXp8XBvrQfPj4U/v9Vum8IPg6GHOv1Gde3rY5ydfunEKi/w4ibVN5buPpndzcNaMoQvEJ/B5VLIzCvLc5HepFKbKFOGu8XoFz8NZY0lUfGLR0rcDsHWZLHPhqYsIsUd9snkWkHaIKD7l9xTd77PpLZiBwCPnVhh3invFY2OnCL6BfiJhhud/aDaAzFW981J9EhyACbuac2qu6Uz2bKX/7Af01gUs48MbKUx8YirBWLD7j/CJMWorTT467It4mAvDlw43s3Py9IvxCzEFnOIftIv+7wwv1RjVQIDAQAB\n-----END PUBLIC KEY-----";

export function validJWTSession(token: string, uuid: string, equal: boolean): boolean {
type SessionData = {
uuid: string | null,
equal: boolean,
isAdmin: boolean
}

export async function getJWTSession(token: string, uuid: string): Promise<SessionData> {
const tokenUuid = getUuidByJWT(token);
if(equal) return tokenUuid === uuid;
else return !!tokenUuid;
if(!tokenUuid) return { uuid: tokenUuid, equal: tokenUuid == uuid, isAdmin: false };
const data = await players.findOne({ uuid: tokenUuid });
if(!data) return { uuid: tokenUuid, equal: tokenUuid == uuid, isAdmin: false };
return {
uuid: tokenUuid,
equal: uuid == tokenUuid,
isAdmin: data.admin
}
}

type LabyPayload = {
Expand Down
30 changes: 7 additions & 23 deletions src/routes/ban.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Elysia, { t } from "elysia";
import { getUuidByJWT, validJWTSession } from "../libs/SessionValidator";
import { getUuidByJWT, getJWTSession } from "../libs/SessionValidator";
import players from "../database/schemas/players";
import fetchI18n from "../middleware/FetchI18n";

Expand All @@ -8,13 +8,8 @@ export default new Elysia({
}).use(fetchI18n).get(`/`, async ({ error, params, headers, i18n }) => { // Get ban info
const uuid = params.uuid.replaceAll(`-`, ``);
const { authorization } = headers;
const authenticated = authorization && validJWTSession(authorization, uuid, false);

if(authorization == `0`) return error(401, { error: i18n(`error.premiumAccount`) });
if(!authenticated) return error(401, { error: i18n(`error.notAllowed`) });

const executor = await players.findOne({ uuid: getUuidByJWT(authorization)! });
if(!executor || !executor.admin) return error(403, { error: i18n(`error.notAllowed`) });
const session = await getJWTSession(authorization, uuid);
if(!session.isAdmin) return error(403, { error: i18n(`error.notAllowed`) });

const player = await players.findOne({ uuid });
if(!player) return error(404, { error: i18n(`error.playerNotFound`) });
Expand All @@ -24,16 +19,10 @@ export default new Elysia({
params: t.Object({ uuid: t.String() }),
headers: t.Object({ authorization: t.String({ error: `error.notAllowed` }) }, { error: `error.notAllowed` })
}).post(`/`, async ({ error, params, headers, body, i18n }) => { // Ban player
console.log(body.reason);
const uuid = params.uuid.replaceAll(`-`, ``);
const { authorization } = headers;
const authenticated = authorization && validJWTSession(authorization, uuid, false);

if(authorization == `0`) return error(401, { error: i18n(`error.premiumAccount`) });
if(!authenticated) return error(401, { error: i18n(`error.notAllowed`) });

const executor = await players.findOne({ uuid: getUuidByJWT(authorization)! });
if(!executor || !executor.admin) return error(403, { error: i18n(`error.notAllowed`) });
const session = await getJWTSession(authorization, uuid);
if(!session.isAdmin) return error(403, { error: i18n(`error.notAllowed`) });

const player = await players.findOne({ uuid });
if(!player) return error(404, { error: i18n(`error.playerNotFound`) });
Expand All @@ -53,13 +42,8 @@ export default new Elysia({
}).delete(`/`, async ({ error, params, headers, i18n }) => { // Unban player
const uuid = params.uuid.replaceAll(`-`, ``);
const { authorization } = headers;
const authenticated = authorization && validJWTSession(authorization, uuid, false);

if(authorization == `0`) return error(401, { error: i18n(`error.premiumAccount`) });
if(!authenticated) return error(401, { error: i18n(`error.notAllowed`) });

const executor = await players.findOne({ uuid: getUuidByJWT(authorization)! });
if(!executor || !executor.admin) return error(403, { error: i18n(`error.notAllowed`) });
const session = await getJWTSession(authorization, uuid);
if(!session.isAdmin) return error(403, { error: i18n(`error.notAllowed`) });

const player = await players.findOne({ uuid });
if(!player) return error(404, { error: i18n(`error.playerNotFound`) });
Expand Down
10 changes: 4 additions & 6 deletions src/routes/icon.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Elysia, { t } from "elysia";
import { validJWTSession } from "../libs/SessionValidator";
import { getJWTSession } from "../libs/SessionValidator";
import players from "../database/schemas/players";
import * as config from "../../config.json";
import fetchI18n from "../middleware/FetchI18n";
Expand All @@ -10,10 +10,8 @@ export default new Elysia({
const uuid = params.uuid.replaceAll(`-`, ``);
const icon = body.icon.toUpperCase();
const { authorization } = headers;
const authenticated = authorization && validJWTSession(authorization, uuid, true);

if(authorization == `0`) return error(401, { error: i18n(`error.premiumAccount`) });
if(!authenticated) return error(401, { error: i18n(`error.notAllowed`) });
const session = await getJWTSession(authorization, uuid);
if(!session.equal && !session.isAdmin) return error(403, { error: i18n(`error.notAllowed`) });

const player = await players.findOne({ uuid });
if(!player) return error(404, { error: i18n(`error.noTag`) });
Expand All @@ -25,7 +23,7 @@ export default new Elysia({
player.icon = icon;
await player.save();

return { message: i18n(`icon.success`) };
return { message: i18n(`icon.success.${session.equal ? 'self' : 'admin'}`) };
}, {
detail: {
tags: ['Settings'],
Expand Down
10 changes: 4 additions & 6 deletions src/routes/position.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Elysia, { t } from "elysia";
import { validJWTSession } from "../libs/SessionValidator";
import { getJWTSession } from "../libs/SessionValidator";
import players from "../database/schemas/players";
import fetchI18n from "../middleware/FetchI18n";

Expand All @@ -9,10 +9,8 @@ export default new Elysia({
const uuid = params.uuid.replaceAll(`-`, ``);
const position = body.position.toUpperCase();
const { authorization } = headers;
const authenticated = authorization && validJWTSession(authorization, uuid, true);

if(authorization == `0`) return error(401, { error: i18n(`error.premiumAccount`) });
if(!authenticated) return error(401, { error: i18n(`error.notAllowed`) });
const session = await getJWTSession(authorization, uuid);
if(!session.equal && !session.isAdmin) return error(403, { error: i18n(`error.notAllowed`) });

const player = await players.findOne({ uuid });
if(!player) return error(404, { error: i18n(`error.noTag`) });
Expand All @@ -24,7 +22,7 @@ export default new Elysia({
player.position = position as "ABOVE" | "BELOW" | "RIGHT" | "LEFT";
await player.save();

return { message: i18n(`position.success`) };
return { message: i18n(`position.success.${session.equal ? 'self' : 'admin'}`) };
}, {
detail: {
tags: ['Settings'],
Expand Down
8 changes: 3 additions & 5 deletions src/routes/report.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Elysia, { t } from "elysia";
import { getUuidByJWT, validJWTSession } from "../libs/SessionValidator";
import { getUuidByJWT, getJWTSession } from "../libs/SessionValidator";
import players from "../database/schemas/players";
import { NotificationType, sendMessage } from "../libs/DiscordNotifier";
import fetchI18n from "../middleware/FetchI18n";
Expand All @@ -9,10 +9,8 @@ export default new Elysia({
}).use(fetchI18n).post(`/`, async ({ error, params, headers, body, i18n }) => { // Report player
const uuid = params.uuid.replaceAll(`-`, ``);
const { authorization } = headers;
const authenticated = authorization && validJWTSession(authorization, uuid, false);

if(authorization == `0`) return error(401, { error: i18n(`error.premiumAccount`) });
if(!authenticated) return error(401, { error: i18n(`error.notAllowed`) });
const session = await getJWTSession(authorization, uuid);
if(!session.uuid) return error(403, { error: i18n(`error.notAllowed`) });

const player = await players.findOne({ uuid });
if(!player) return error(404, { error: i18n(`error.playerNoTag`) });
Expand Down
Loading

0 comments on commit dd697d9

Please sign in to comment.