summary refs log tree commit diff
path: root/src-slowcord/login/src
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-09-25 18:24:21 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-09-25 23:35:18 +1000
commitf44f5d7ac2d24ff836c2e1d4b2fa58da04b13052 (patch)
treea6655c41bb3db79c30fd876b06ee60fe9cf70c9b /src-slowcord/login/src
parentAllow edited_timestamp to passthrough in handleMessage (diff)
downloadserver-f44f5d7ac2d24ff836c2e1d4b2fa58da04b13052.tar.xz
Refactor to mono-repo + upgrade packages
Diffstat (limited to 'src-slowcord/login/src')
-rw-r--r--src-slowcord/login/src/index.ts156
1 files changed, 156 insertions, 0 deletions
diff --git a/src-slowcord/login/src/index.ts b/src-slowcord/login/src/index.ts
new file mode 100644
index 00000000..a17e9eaa
--- /dev/null
+++ b/src-slowcord/login/src/index.ts
@@ -0,0 +1,156 @@
+import "dotenv/config";
+import express, { Request, Response } from "express";
+import cookieParser from "cookie-parser";
+import * as util from "@fosscord/util";
+const { initDatabase, generateToken, User, Config, handleFile } = util;
+import path from "path";
+import fetch from "node-fetch";
+
+// apparently dirname doesn't exist in modules, nice
+/* https://stackoverflow.com/a/62892482 */
+import { fileURLToPath } from "url";
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const app = express();
+app.use(cookieParser());
+const port = process.env.PORT;
+
+// ip -> unix epoch that requests will be accepted again
+const rateLimits: { [ip: string]: number; } = {};
+const allowRequestsEveryMs = 0.5 * 1000;	// every half second
+
+const allowedRequestsPerSecond = 50;
+let requestsThisSecond = 0;
+setInterval(() => {
+	requestsThisSecond = 0;
+}, 1000);
+
+const toDataURL = async (url: string) => {
+	const response = await fetch(url);
+	const blob = await response.blob();
+	const buffer = Buffer.from(await blob.text());
+	return `data:${blob.type};base64,${buffer.toString("base64")}`;
+};
+
+class Discord {
+	static getAccessToken = async (req: Request, res: Response) => {
+		const { code } = req.query;
+
+		const body = new URLSearchParams(Object.entries({
+			client_id: process.env.DISCORD_CLIENT_ID as string,
+			client_secret: process.env.DISCORD_SECRET as string,
+			redirect_uri: process.env.DISCORD_REDIRECT as string,
+			code: code as string,
+			grant_type: "authorization_code",
+		})).toString();
+
+		const resp = await fetch("https://discord.com/api/oauth2/token", {
+			method: "POST",
+			headers: {
+				"Content-Type": "application/x-www-form-urlencoded",
+			},
+			body: body
+		});
+
+		const json = await resp.json() as any;
+		if (json.error) return null;
+
+		return {
+			access_token: json.access_token,
+			token_type: json.token_type,
+			expires_in: json.expires_in,
+			refresh_token: json.refresh_token,
+			scope: json.scope,
+		};
+	};
+
+	static getUserDetails = async (token: string) => {
+		const resp = await fetch("https://discord.com/api/users/@me", {
+			headers: {
+				"Authorization": `Bearer ${token}`,
+			}
+		});
+
+		const json = await resp.json() as any;
+		if (!json.username || !json.email) return null;	// eh, deal with bad code later
+
+		return {
+			id: json.id,
+			email: json.email,
+			username: json.username,
+			avatar_url: json.avatar ? `https://cdn.discordapp.com/avatars/${json.id}/${json.avatar}?size=2048` : null,
+		};
+	};
+}
+
+const handlers: { [key: string]: any; } = {
+	"discord": Discord,
+};
+
+app.get("/oauth/:type", async (req, res) => {
+	requestsThisSecond++;
+	if (requestsThisSecond > allowedRequestsPerSecond)
+		return res.sendStatus(429);
+
+	const ip = (req.headers["x-forwarded-for"] as string) || req.socket.remoteAddress as string;
+	console.log(`${ip}`);
+	if (!rateLimits[ip]) {
+		rateLimits[ip] = Date.now() + allowRequestsEveryMs;
+	}
+	else if (rateLimits[ip] > Date.now()) {
+		rateLimits[ip] += allowRequestsEveryMs;
+		console.log(`${new Date()} : user ${ip} was timed out for ${(rateLimits[ip] - Date.now()) / 1000}s`);
+		return res.sendStatus(429);
+	}
+	else {
+		delete rateLimits[ip];
+	}
+
+	const { type } = req.params;
+	const handler = handlers[type];
+	if (!type || !handler) return res.sendStatus(400);
+
+	const data = await handler.getAccessToken(req, res);
+	if (!data) return res.sendStatus(500);
+
+	const details = await handler.getUserDetails(data.access_token);
+	if (!details) return res.sendStatus(500);
+
+	let user = await User.findOne({ where: { email: details.email } });
+	if (!user) {
+		user = await User.register({
+			email: details.email,
+			username: details.username,
+			req
+		});
+
+		if (details.avatar_url) {
+			try {
+				const avatar = await handleFile(`/avatars/${user.id}`, await toDataURL(details.avatar_url) as string);
+				user.avatar = avatar;
+				await user.save();
+			}
+			catch (e) {
+				console.error(e);
+			}
+		}
+	}
+
+	const token = await generateToken(user.id);
+
+	res.cookie("token", token);
+
+	res.sendFile(path.join(__dirname, "../public/login.html"));
+});
+
+app.use(express.static("public", { extensions: ["html"] }));
+
+(async () => {
+	await initDatabase();
+	await Config.init();
+
+	app.listen(port, () => {
+		console.log(`Listening on port ${port}`);
+	});
+})();
\ No newline at end of file