From 46802f995d43647d2fe1e688164681ef7a00224a Mon Sep 17 00:00:00 2001 From: janderedev Date: Mon, 24 Jan 2022 19:01:18 +0100 Subject: [PATCH] server list --- api/src/index.ts | 1 + api/src/routes/dash/servers.ts | 21 ++++++ api/src/utils.ts | 7 +- bot/src/bot/modules/api/servers.ts | 54 ++++++++++++++++ bot/src/bot/modules/api_communication.ts | 11 ++-- bot/src/bot/util.ts | 17 +++++ web/src/App.tsx | 5 +- web/src/pages/DashboardHome.tsx | 81 ++++++++++++++++++++++++ web/src/pages/ServerDashboard.tsx | 22 +++++++ web/src/{utils.tsx => utils.ts} | 10 ++- 10 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 api/src/routes/dash/servers.ts create mode 100644 bot/src/bot/modules/api/servers.ts create mode 100644 web/src/pages/DashboardHome.tsx create mode 100644 web/src/pages/ServerDashboard.tsx rename web/src/{utils.tsx => utils.ts} (75%) diff --git a/api/src/index.ts b/api/src/index.ts index 701bd61..2f53c66 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -25,6 +25,7 @@ export { logger, app, db, PORT, SESSION_LIFETIME } import('./routes/internal/ws'), import('./routes/root'), import('./routes/login'), + import('./routes/dash/servers'), ]); logger.done('All routes and middlewares loaded'); })(); diff --git a/api/src/routes/dash/servers.ts b/api/src/routes/dash/servers.ts new file mode 100644 index 0000000..7a34fde --- /dev/null +++ b/api/src/routes/dash/servers.ts @@ -0,0 +1,21 @@ +import { app } from '../..'; +import { Request, Response } from 'express'; +import { isAuthenticated } from '../../utils'; +import { botReq } from '../internal/ws'; + +type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string } + +app.get('/dash/servers', async (req: Request, res: Response) => { + const user = await isAuthenticated(req, res, true); + if (!user) return; + + const response = await botReq('getUserServers', { user }); + if (!response.success) { + return res.status(response.statusCode ?? 500).send({ error: response.error }); + } + + if (!response.servers) return res.status(404).send({ error: 'Not found' }); + + const servers: Server[] = response.servers; + res.send({ servers }); +}); diff --git a/api/src/utils.ts b/api/src/utils.ts index 4a22b7d..387aa31 100644 --- a/api/src/utils.ts +++ b/api/src/utils.ts @@ -1,4 +1,4 @@ -import { Request } from "express"; +import { Request, Response } from "express"; import { FindOneResult } from "monk"; import { db } from "."; @@ -15,13 +15,16 @@ class Session { * @param req * @returns false if not authenticated, otherwise the (Revolt) user ID */ -async function isAuthenticated(req: Request): Promise { +async function isAuthenticated(req: Request, res?: Response, send401?: boolean): Promise { const user = req.header('x-auth-user'); const token = req.header('x-auth-token'); if (!user || !token) return false; const info = await getSessionInfo(user, token); + if (res && send401 && !info.valid) { + res.status(401).send({ error: 'Unauthorized' }); + } return info.valid ? user : false; } diff --git a/bot/src/bot/modules/api/servers.ts b/bot/src/bot/modules/api/servers.ts new file mode 100644 index 0000000..b699e37 --- /dev/null +++ b/bot/src/bot/modules/api/servers.ts @@ -0,0 +1,54 @@ +import { User } from 'revolt.js/dist/maps/Users'; +import { client } from '../../..'; +import { getPermissionLevel, isBotManager } from '../../util'; +import { wsEvents, WSResponse } from '../api_communication'; + +type ReqData = { user: string } + +wsEvents.on('req:getUserServers', async (data: ReqData, cb: (data: WSResponse) => void) => { + try { + let user: User; + try { + user = client.users.get(data.user) || await client.users.fetch(data.user); + } catch(e) { + cb({ success: false, error: 'The requested user could not be found', statusCode: 404 }); + return; + } + + const mutuals = await user.fetchMutual(); + + type ServerResponse = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string } + + const promises: Promise[] = []; + + for (const sid of mutuals.servers) { + promises.push(new Promise(async (resolve, reject) => { + try { + const server = client.servers.get(sid); + if (!server) return reject('Server not found'); + const perms = await getPermissionLevel(user, server); + resolve({ + id: sid, + perms, + name: server.name, + bannerURL: server.generateBannerURL(), + iconURL: server.generateIconURL({}), + }); + } catch(e) { + console.error(e); + reject(`${e}`); + } + })); + } + + cb({ + success: true, + servers: (await Promise.allSettled(promises)).map( + p => p.status == 'fulfilled' ? p.value : undefined + ), + }); + } catch(e) { + console.error(e); + cb({ success: false, error: `${e}` }); + } +}); diff --git a/bot/src/bot/modules/api_communication.ts b/bot/src/bot/modules/api_communication.ts index 45d5bc1..4a0ce7a 100644 --- a/bot/src/bot/modules/api_communication.ts +++ b/bot/src/bot/modules/api_communication.ts @@ -9,7 +9,6 @@ import { client as bot } from '../..'; import { EventEmitter } from "events"; import { parseUser } from "../util"; import PendingLogin from "../../struct/PendingLogin"; -import { LogLevel } from "log75"; import { ulid } from "ulid"; const wsEvents = new EventEmitter(); @@ -17,6 +16,8 @@ const { API_WS_URL, API_WS_TOKEN } = process.env; const wsQueue: { [key: string]: string }[] = []; let client: ws|undefined = undefined; +type WSResponse = { success: false, error: string, statusCode?: number } | { success: true, [key: string]: any } + if (!API_WS_URL || !API_WS_TOKEN) logger.info("$API_WS_URL or $API_WS_TOKEN not found."); else { @@ -92,7 +93,7 @@ wsEvents.on('req:test', (data: any, res: (data: any) => void) => { res({ received: data }); }); -wsEvents.on('req:requestLogin', async (data: any, cb: (data: any) => void) => { +wsEvents.on('req:requestLogin', async (data: any, cb: (data: WSResponse) => void) => { try { const user = await parseUser(data.user); if (!user) @@ -126,8 +127,10 @@ wsEvents.on('req:requestLogin', async (data: any, cb: (data: any) => void) => { cb({ success: true, uid: user._id, nonce, code }); } catch(e) { console.error(e); - cb({ success: false, error: e }); + cb({ success: false, error: `${e}` }); } }); -export { wsEvents, wsSend } +export { wsEvents, wsSend, WSResponse } + +import('./api/servers'); diff --git a/bot/src/bot/util.ts b/bot/src/bot/util.ts index c35ea5e..9995717 100644 --- a/bot/src/bot/util.ts +++ b/bot/src/bot/util.ts @@ -99,6 +99,22 @@ async function checkSudoPermission(message: Message): Promise { return true; } } +async function getPermissionLevel(user: User|Member, server: Server): Promise<0|1|2> { + if (isSudo(user instanceof User ? user : (user.user || await client.users.fetch(user._id.user)))) return 2; + + const member = user instanceof User ? await server.fetchMember(user) : user; + if (user instanceof Member) user = user.user!; + + if (hasPerm(member, 'ManageServer')) return 2; + if (hasPerm(member, 'KickMembers')) return 1; + + const config = (await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig; + + if (config.botManagers?.includes(user._id)) return 2; + if (config.moderators?.includes(user._id)) return 1; + + return 0; +} function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean { let p = ServerPermission[perm]; @@ -292,6 +308,7 @@ export { getOwnMemberInServer, isModerator, isBotManager, + getPermissionLevel, parseUser, parseUserOrId, storeInfraction, diff --git a/web/src/App.tsx b/web/src/App.tsx index 348a06e..56a0279 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -4,6 +4,8 @@ import './App.css'; import '@revoltchat/ui/src/styles/dark.css'; import '@revoltchat/ui/src/styles/common.css'; import RequireAuth from './components/RequireAuth'; +import DashboardHome from './pages/DashboardHome'; +import ServerDashboard from './pages/ServerDashboard'; const API_URL = 'http://localhost:9000'; @@ -12,7 +14,8 @@ function App() { } /> - among us} /> + } /> + } /> ); diff --git a/web/src/pages/DashboardHome.tsx b/web/src/pages/DashboardHome.tsx new file mode 100644 index 0000000..4246e7e --- /dev/null +++ b/web/src/pages/DashboardHome.tsx @@ -0,0 +1,81 @@ +import axios from 'axios'; +import { useNavigate } from 'react-router-dom'; +import { FunctionComponent, useCallback, useEffect, useState } from "react"; +import { Button } from '@revoltchat/ui/lib/components/atoms/inputs/Button'; +import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1'; +import { H2 } from '@revoltchat/ui/lib/components/atoms/heading/H2'; +import { API_URL } from "../App"; +import { getAuthHeaders } from "../utils"; + +type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string } + +function permissionName(p: number) { + switch(p) { + case 0: return 'User'; + case 1: return 'Moderator'; + case 2: return 'Manager'; + default: return 'Unknown'; + } +} + +const Dashboard: FunctionComponent = () => { + const [loading, setLoading] = useState(true); + const [servers, setServers] = useState([] as Server[]); + const navigate = useNavigate(); + + const loadServers = useCallback(async () => { + try { + const res = await axios.get(API_URL + '/dash/servers', { headers: await getAuthHeaders() }); + setServers(res.data.servers); + setLoading(false); + } catch(e) { + console.error(e); + } + }, []); + + useEffect(() => { loadServers() }, []); + + return ( +
+

dashbord

+
+ + { + servers.map(server =>
+ +
+

{server.name} ({permissionName(server.perms)})

+ {server.id} +
+
+ +
+
+
) + } +
+ ); +} + +export default Dashboard; diff --git a/web/src/pages/ServerDashboard.tsx b/web/src/pages/ServerDashboard.tsx new file mode 100644 index 0000000..23985c0 --- /dev/null +++ b/web/src/pages/ServerDashboard.tsx @@ -0,0 +1,22 @@ +import localforage from "localforage"; +import axios from 'axios'; +import { FunctionComponent, useCallback, useEffect, useState } from "react"; +import { Button } from '@revoltchat/ui/lib/components/atoms/inputs/Button'; +import { InputBox } from '@revoltchat/ui/lib/components/atoms/inputs/InputBox'; +import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1'; +import { H2 } from '@revoltchat/ui/lib/components/atoms/heading/H2'; +import { API_URL } from "../App"; +import { getAuthHeaders } from "../utils"; +import { useParams } from "react-router-dom"; + +type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string } + +const ServerDashboard: FunctionComponent = () => { + const { serverid } = useParams(); + + return ( + sus + ); +} + +export default ServerDashboard; diff --git a/web/src/utils.tsx b/web/src/utils.ts similarity index 75% rename from web/src/utils.tsx rename to web/src/utils.ts index 1635a83..7b6b1e7 100644 --- a/web/src/utils.tsx +++ b/web/src/utils.ts @@ -2,6 +2,14 @@ import axios from "axios"; import localforage from "localforage"; import { API_URL } from "./App"; +async function getAuthHeaders() { + const auth: any = await localforage.getItem('auth'); + return { + 'x-auth-user': auth.user, + 'x-auth-token': auth.token, + } +} + async function getAuth(): Promise { const auth: any = await localforage.getItem('auth'); if (!auth) return false; @@ -19,4 +27,4 @@ async function getAuth(): Promise { } catch(e) { return false } // todo: dont assume we're logged out if death } -export { getAuth } \ No newline at end of file +export { getAuth, getAuthHeaders } \ No newline at end of file