Browse Source

Implement logging function

ghorsington 4 years ago
parent
commit
0d9f2d2372

+ 6 - 0
.vscode/launch.json

@@ -29,5 +29,11 @@
             "cwd": "${workspaceFolder}/web",
             "autoAttachChildProcesses": true
         }
+    ],
+    "compounds": [
+        {
+            "name": "Bot+WebServer",
+            "configurations": ["Launch NoctBot", "Launch WebServer"]
+        }
     ]
 }

+ 5 - 0
bot/package.json

@@ -29,6 +29,8 @@
       "@google-cloud/translate": "^4.1.1",
       "@types/cheerio": "^0.22.12",
       "@types/dotenv": "^6.1.1",
+      "@types/koa": "^2.0.49",
+      "@types/koa-router": "^7.0.42",
       "@types/lowdb": "^1.0.9",
       "@types/node-schedule": "^1.2.3",
       "@types/request-promise-native": "^1.0.16",
@@ -44,6 +46,9 @@
       "html2bbcode": "^1.2.6",
       "interval-promise": "^1.2.0",
       "jimp": "^0.5.4",
+      "koa": "^2.8.1",
+      "koa-body": "^4.1.1",
+      "koa-router": "^7.4.0",
       "lowdb": "^1.0.0",
       "module-alias": "^2.2.0",
       "node-schedule": "^1.3.2",

+ 3 - 0
bot/src/main.ts

@@ -24,6 +24,7 @@ import { createConnection, getConnectionOptions } from "typeorm";
 import { getNumberEnums } from "./util";
 import { DB_ENTITIES } from "@shared/db/entities";
 import { BOT_COMMAND_DESCRIPTOR } from "./model/command";
+import { startServer as startRPCServer } from "./rpc_service";
 
 
 const REACT_PROBABILITY = 0.3;
@@ -149,6 +150,8 @@ async function main() {
         entities: DB_ENTITIES
     });
 
+    startRPCServer();
+
     let commandsPath = path.resolve(path.dirname(module.filename), "commands");
     let files = fs.readdirSync(commandsPath);
 

+ 52 - 0
bot/src/rpc_service.ts

@@ -0,0 +1,52 @@
+import { createServerRouter } from "typescript-rest-rpc/lib/server";
+import Koa from "koa";
+import KoaRouter from "koa-router";
+import koaBody from "koa-body";
+import { Backend, UserInfo } from "@shared/rpc/backend";
+import { getRepository } from "typeorm";
+import { KnownUser } from "@shared/db/entity/KnownUser";
+import { client } from "./client";
+import { GuildMember } from "discord.js";
+import { isAuthorisedAsync } from "./util";
+
+class BackendImpl implements Backend {
+    async getModeratorUserInfo({ id }: { id: string }): Promise<UserInfo> {
+        let repo = getRepository(KnownUser);
+        
+        let member: GuildMember;
+        for(let [_, guild] of client.guilds) {
+            try {
+                member = await guild.fetchMember(id);
+                break;
+            } catch(e) {
+                member = null;
+            }
+        }
+
+        if(!member || !(await isAuthorisedAsync(member)))
+            return undefined;
+
+        return <UserInfo>{
+            userId: member.user.id,
+            username: member.user.tag,
+            avatarURL: member.user.displayAvatarURL,
+            guildId: member.guild.id,
+            timestamp: new Date()
+        };
+    }
+}
+
+const PORT = 3010;
+let app : Koa;
+let router : KoaRouter;
+export function startServer() {
+    app = new Koa();
+    app.use(koaBody({ multipart: true }));
+
+    router = createServerRouter("/rpc", new BackendImpl());
+    app.use(router.routes());
+    app.use(router.allowedMethods());
+
+    app.listen(PORT);
+    console.log(`Started RPC service at ${PORT}`);
+}

+ 1 - 1
docker-compose.yml

@@ -25,7 +25,7 @@ services:
       TYPEORM_PASSWORD: ${DB_PASSWORD}
       TYPEORM_DATABASE: ${DB_NAME}
     ports:
-      - 3010:3000
+      - 3010:3010
 
   web:
     image: noctbot_web

+ 11 - 0
shared/src/rpc/backend.ts

@@ -0,0 +1,11 @@
+export interface Backend {
+    getModeratorUserInfo({ id }: { id: string }): Promise<UserInfo>;
+}
+
+export interface UserInfo {
+    username: string;
+    avatarURL: string;
+    userId: string;
+    guildId: string;
+    timestamp: Date;
+}

+ 4 - 0
web/package.json

@@ -14,15 +14,19 @@
   "dependencies": {
     "@types/btoa": "^1.2.3",
     "@types/compression": "0.0.36",
+    "@types/cookie-session": "^2.0.37",
     "@types/dotenv": "^6.1.1",
     "@types/express": "^4.17.0",
+    "@types/node-fetch": "^2.5.0",
     "@types/request-promise-native": "^1.0.16",
     "autoprefixer": "^9.6.1",
     "btoa": "^1.2.1",
     "bulma": "^0.7.5",
     "compression": "^1.7.1",
+    "cookie-session": "^1.3.3",
     "dotenv": "^8.0.0",
     "express": "^4.17.1",
+    "node-fetch": "^2.6.0",
     "pg": "^7.11.0",
     "polka": "^0.5.0",
     "request": "^2.88.0",

+ 7 - 6
web/src/routes/_layout.svelte

@@ -1,15 +1,16 @@
-<script>
-	import Nav from '../components/Nav.svelte';
-
-	export let segment;
+<script context="module">
+	export async function preload(page, session) {
+		const { user } = session;
+		if(!session.user && page.path != "/login")
+			return this.redirect(302, "login");
+		return { user };
+	}
 </script>
 
 <style global lang="scss">
 	@import "style/main.scss";
 </style>
 
-<!-- <Nav {segment}/> -->
-
 <main>
 	<slot></slot>
 </main>

+ 28 - 0
web/src/routes/dashboard/_layout.svelte

@@ -0,0 +1,28 @@
+<script>
+    import Nav from "../../components/Nav.svelte";
+    export let segment;
+</script>
+
+
+<section class="section">
+    <div class="container">
+        <div class="columns">
+            <div class="column is-2">
+                <nav>
+                    <div class="buttons are-small">
+                        <a class="button is-primary is-fullwidth">All</a>
+                        <a class="button is-primary is-fullwidth">Medium</a>
+                        <a class="button is-primary is-fullwidth">Size</a>
+                    </div>
+                </nav>
+            </div>
+            <div class="column">
+                Neigh
+            </div>
+        </div>
+    </div>
+</section>
+
+<!-- <main>
+	<slot></slot>
+</main> -->

+ 8 - 0
web/src/routes/dashboard/index.svelte

@@ -0,0 +1,8 @@
+
+<svelte:head>
+  <title>Loading</title>
+</svelte:head>
+
+<div class="content">
+  <h1>Neigh!</h1>
+</div>

+ 9 - 32
web/src/routes/index.svelte

@@ -1,37 +1,14 @@
-<style lang="scss">
-  #login-box {
-    height: 100vh;
-    align-items: center;
-  }
-
-  #login-form {
-    border-radius: 3px;
-    box-shadow: 3px 3px 10px -1px rgba(0, 0, 0, 0.75);
-  }
-
-  .discord-bg {
-    background-color: #7289da;
-    border: none;
-  }
-</style>
+<script context="module">
+    export async function preload(page, session) {
+        const { user } = session;
+        if(user) return this.redirect(300, "dashboard");
+    }
+</script>
 
 <svelte:head>
-  <title>Sapper project template</title>
+  <title>Loading</title>
 </svelte:head>
+
 <div class="content">
-  <div id="login-box" class="columns">
-    <div id="login-form" class="column is-offset-4 is-4 has-background-black">
-      <h1 class="has-text-centered">NoctBot panel</h1>
-      <div class="columns is-mobile is-centered">
-        <div class="column is-narrow">
-          <a class="button discord-bg" href="/login/discord/do">
-            <span class="icon">
-              <i class="fab fa-discord" />
-            </span>
-            <span>Login with Discord</span>
-          </a>
-        </div>
-      </div>
-    </div>
-  </div>
+  <h1>Loading...</h1>
 </div>

+ 55 - 8
web/src/routes/login/discord/callback.ts

@@ -1,24 +1,71 @@
 import { Request, Response, NextFunction } from "express";
 import request from "request-promise-native";
 import { Response as Res } from "request";
+import { botService } from "src/util/rpc_client";
 
-export async function get(req : Request, res : Response, next: NextFunction) {
-    const CALLBACK_URL = encodeURIComponent(`${process.env.ADMIN_URL}/login/discord/callback`);
+const API_ENDPOINT = "https://discordapp.com/api";
 
+export async function get(req : Request, res : Response, next: NextFunction) {
     if(!req.query.code)
         throw new Error("NoCodeProvided");
 
     let code = req.query.code;
-    let response = await request(`https://discordapp.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${CALLBACK_URL}`, {
+    let response = await request("/oauth2/token", {
         method: "POST",
+        baseUrl: API_ENDPOINT,
+        qs: {
+            grant_type: "authorization_code",
+            code: code,
+            redirect_uri: `${process.env.ADMIN_URL}/login/discord/callback`
+        },
         auth: {
             user: process.env.BOT_CLIENT_ID,
-            pass: process.env.BOT_CLIENT_SECRET,
-            sendImmediately: true
+            pass: process.env.BOT_CLIENT_SECRET
         },
         resolveWithFullResponse: true
     }) as Res;
 
-    //TODO: Saving the tokens with horses and stuff
-    console.log(response.toJSON());
-};
+    let authResponse: AuthResponse = JSON.parse(response.body);
+
+    let userInfoResponse = await request("/users/@me", {
+        method: "GET",
+        baseUrl: API_ENDPOINT,
+        auth: {
+            bearer: authResponse.access_token
+        },
+        resolveWithFullResponse: true
+    });
+
+    let discordUser : DiscordUser = JSON.parse(userInfoResponse.body);
+    let userInfo = await botService.getModeratorUserInfo({id: discordUser.id});
+
+    if(!userInfo){
+        res.redirect(`${process.env.ADMIN_URL}/login/?error=invalid_user`);
+        return;
+    }
+
+    req.session.user = userInfo;
+    res.redirect(`${process.env.ADMIN_URL}/`);
+};
+
+interface AuthResponse {
+    access_token: string;
+    token_type: string;
+    expires_in: number;
+    refresh_token?: string;
+    scope: string;
+}
+
+interface DiscordUser {
+    id: string;
+    username: string;
+    discriminator: string;
+    avatar?: string;
+    bot?: boolean;
+    mfa_enabled?: boolean;
+    locale?: string;
+    verified?: boolean;
+    email?: string;
+    flags?: number;
+    premium_type?: number;
+}

+ 4 - 2
web/src/routes/login/discord/do.ts

@@ -1,8 +1,10 @@
 import {Request, Response, NextFunction } from "express";
 
+const API_ENDPOINT = "https://discordapp.com/api";
+
 export async function get(req : Request, res : Response, next : NextFunction) {
     const CALLBACK_URL = encodeURIComponent(`${process.env.ADMIN_URL}/login/discord/callback`);
-    const SCOPE = encodeURIComponent("identify guilds");
+    const SCOPE = encodeURIComponent("identify");
 
-    res.redirect(`https://discordapp.com/api/oauth2/authorize?client_id=${process.env.BOT_CLIENT_ID}&scope=${SCOPE}&response_type=code&redirect_uri=${CALLBACK_URL}`);
+    res.redirect(`${API_ENDPOINT}/oauth2/authorize?client_id=${process.env.BOT_CLIENT_ID}&scope=${SCOPE}&response_type=code&redirect_uri=${CALLBACK_URL}`);
 };

+ 52 - 0
web/src/routes/login/index.svelte

@@ -0,0 +1,52 @@
+<script context="module">
+    export async function preload(page, session) {
+        const {host, path, params, query} = page;
+        const { user } = session;
+        if(user)
+            return this.redirect(300, "dashboard");
+        if(query.error) {
+            return {
+                errors: error.split(",")
+            }
+        }
+    }
+</script>
+
+<style lang="scss">
+  #login-box {
+    height: 100vh;
+    align-items: center;
+  }
+
+  #login-form {
+    border-radius: 3px;
+    box-shadow: 3px 3px 10px -1px rgba(0, 0, 0, 0.75);
+  }
+
+  .discord-bg {
+    background-color: #7289da;
+    border: none;
+  }
+</style>
+
+<svelte:head>
+  <title>Login</title>
+</svelte:head>
+
+<div class="content">
+  <div id="login-box" class="columns">
+    <div id="login-form" class="column is-offset-4 is-4 has-background-black">
+      <h1 class="has-text-centered">NoctBot panel</h1>
+      <div class="columns is-mobile is-centered">
+        <div class="column is-narrow">
+          <a class="button discord-bg" href="/login/discord/do">
+            <span class="icon">
+              <i class="fab fa-discord" />
+            </span>
+            <span>Login with Discord</span>
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 11 - 1
web/src/server.ts

@@ -2,6 +2,7 @@ import compression from 'compression';
 import * as sapper from '@sapper/server';
 import { createConnection, getConnectionOptions } from "typeorm";
 import express from "express";
+import session from "cookie-session";
 import dotenv from "dotenv";
 import { DB_ENTITIES } from "@shared/db/entities";
 
@@ -29,9 +30,18 @@ async function main() {
 
 	express()
 		.use(
+			session({
+				maxAge: 604800,
+				secret: process.env.ADMIN_COOKIE_KEY,
+				name: "session"
+			}),
 			compression({ threshold: 0 }),
 			express.static("static"),
-			sapper.middleware()
+			sapper.middleware({
+				session: (req, res) => ({
+					user: req.session && req.session.user
+				})
+			})
 		)
 		.listen(PORT, err => {
 			if (err) console.log('error', err);

+ 6 - 0
web/src/util/rpc_client.ts

@@ -0,0 +1,6 @@
+(<any>global).fetch = require("node-fetch");
+import { createClient } from "typescript-rest-rpc/lib/client";
+import { Backend } from "@shared/rpc/backend";
+
+const BOT_ADDRESS = process.env.NODE_ENV == "development" ? "localhost" : "noctbot";
+export const botService : Backend = createClient(`http://${BOT_ADDRESS}:3010/rpc`);

+ 4 - 1
web/tsconfig.json

@@ -16,7 +16,10 @@
         "baseUrl": ".",
         "paths": {
             "@shared/*": ["../shared/src/*"]
-        }
+        },
+        "types": [
+            "node-fetch"
+        ]
     },
     "references": [
         {