server list

This commit is contained in:
janderedev 2022-01-24 19:01:18 +01:00
parent f01c616a44
commit 46802f995d
Signed by: Lea
GPG key ID: 5D5E18ACB990F57A
10 changed files with 221 additions and 8 deletions

View file

@ -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');
})();

View file

@ -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 });
});

View file

@ -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<string|false> {
async function isAuthenticated(req: Request, res?: Response, send401?: boolean): Promise<string|false> {
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;
}

View file

@ -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<ServerResponse>[] = [];
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}` });
}
});

View file

@ -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');

View file

@ -99,6 +99,22 @@ async function checkSudoPermission(message: Message): Promise<boolean> {
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,

View file

@ -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() {
<BrowserRouter>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/dashboard' element={<RequireAuth><a>among us</a></RequireAuth>} />
<Route path='/dashboard' element={<RequireAuth><DashboardHome /></RequireAuth>} />
<Route path='/dashboard/:serverid' element={<RequireAuth><ServerDashboard /></RequireAuth>} />
</Routes>
</BrowserRouter>
);

View file

@ -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 (
<div>
<H1>dashbord</H1>
<br/>
<p hidden={!loading}>loading</p>
{
servers.map(server => <div className="server-card" style={{ paddingTop: '10px' }} key={server.id}>
<img
src={server.iconURL ?? 'https://dl.insrt.uk/projects/revolt/emotes/trol.png'}
width={48}
height={48}
style={{
float: 'left',
marginLeft: '8px',
marginRight: '12px',
borderRadius: "50%",
}}
/>
<div style={{
float: 'left',
maxWidth: '240px',
textOverflow: 'clip',
overflow: 'clip',
whiteSpace: 'nowrap',
}}>
<H2>{server.name} ({permissionName(server.perms)})</H2>
<code style={{ color: 'var(--foreground)' }}>{server.id}</code>
</div>
<div>
<Button
style={{ position: 'relative', top: '8px', left: '12px' }}
onClick={() => {
navigate(`/dashboard/${server.id}`);
}}
>Open</Button>
</div>
<div style={{ clear: 'both' }} />
</div>)
}
</div>
);
}
export default Dashboard;

View file

@ -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 (
<a>sus</a>
);
}
export default ServerDashboard;

View file

@ -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<false|{ user: string, token: string }> {
const auth: any = await localforage.getItem('auth');
if (!auth) return false;
@ -19,4 +27,4 @@ async function getAuth(): Promise<false|{ user: string, token: string }> {
} catch(e) { return false } // todo: dont assume we're logged out if death
}
export { getAuth }
export { getAuth, getAuthHeaders }