From 3dd2e6ada10c3ddc4513ce8b9cedbeaecec7547f Mon Sep 17 00:00:00 2001 From: hippoz <10706925-hippoz@users.noreply.gitlab.com> Date: Tue, 1 Feb 2022 03:49:12 +0200 Subject: [PATCH] add basic token creation and message sending --- WatchedGuild.js | 80 ++++++++++++++++++++++++++++++++ common.js | 28 ++++++++++++ config.js | 7 +++ index.js | 16 +++++++ package.json | 4 +- public/index.html | 49 ++++++++++++++++++++ routes/api.js | 56 +++++++++++++++++++++++ tokens.js | 56 +++++++++++++++++++++++ yarn.lock | 114 +++++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 407 insertions(+), 3 deletions(-) create mode 100644 WatchedGuild.js create mode 100644 common.js create mode 100644 config.js create mode 100644 public/index.html create mode 100644 routes/api.js create mode 100644 tokens.js diff --git a/WatchedGuild.js b/WatchedGuild.js new file mode 100644 index 0000000..1796711 --- /dev/null +++ b/WatchedGuild.js @@ -0,0 +1,80 @@ +const { EventEmitter } = require("events"); + +class WatchedGuild extends EventEmitter { + constructor() { + super(); + + this.knownWebhooks = new Map(); + this.eventStack = []; + this.upstreamGuildId = null; + } + + pushEvent(e) { + this.eventStack.push(e); + this.emit("pushed", e); + } + + consumeEvent() { + return this.eventStack.pop(); + } + + consumeAll() { + const events = [...this.eventStack]; + this.eventStack = []; + return events; + } + + hasEvents() { + return this.eventStack.length > 0; + } + + _pushMessageEvent(message) { + this.pushEvent({ + eventType: "messageCreate", + message: message.toJSON() + }); + } + + discordConnect(bot) { + this.bot = bot; + this.bot.on("messageCreate", (message) => { + if (message.guildId !== this.upstreamGuildId) + return; + + this._pushMessageEvent(message); + }); + } + + async discordSendMessage(messageContent, channelId, username, avatarURL=undefined) { + if (!this.bot) + throw new Error("Bot not connected"); + + let webhook = this.knownWebhooks.get(channelId); + if (!webhook) { + webhook = (await this.bot.getChannelWebhooks(channelId)) + .filter(w => w.name == "well_known__bridge")[0]; + + if (!webhook) + webhook = await this.bot.createChannelWebhook(channelId, { + name: "well_known__bridge" + }, "This webhook was created by the bridge API bot."); + + this.knownWebhooks.set(channelId, webhook); + } + + await this.bot.executeWebhook(webhook.id, webhook.token, { + allowedMentions: { + everyone: false, + roles: false, + users: true + }, + content: messageContent, + tts: false, + wait: true, + avatarURL, + username + }); + } +} + +module.exports = WatchedGuild; diff --git a/common.js b/common.js new file mode 100644 index 0000000..cf0b699 --- /dev/null +++ b/common.js @@ -0,0 +1,28 @@ +const Eris = require("eris"); +const { discordToken, watchedGuildIds } = require("./config"); +const WatchedGuild = require("./WatchedGuild"); + +const bot = new Eris(discordToken, { + intents: [ + "guildMessages" + ] +}); + +const guildMap = new Map(); + +bot.on("ready", () => { + console.log("discord bot: ready"); + watchedGuildIds.forEach(id => { + const watchedGuild = new WatchedGuild(); + watchedGuild.upstreamGuildId = id; + watchedGuild.discordConnect(bot); + guildMap.set(id, watchedGuild); + }); +}); + +bot.connect(); + +module.exports = { + bot, + guildMap +}; diff --git a/config.js b/config.js new file mode 100644 index 0000000..1fcc5fe --- /dev/null +++ b/config.js @@ -0,0 +1,7 @@ +module.exports = { + mainHttpListenPort: 4050, + watchedGuildIds: ["822089558886842418"], + jwtSecret: process.env.JWT_SECRET, + discordToken: process.env.DISCORD_TOKEN, + dangerousAdminMode: true +}; diff --git a/index.js b/index.js index e69de29..9368aef 100644 --- a/index.js +++ b/index.js @@ -0,0 +1,16 @@ +const express = require("express"); +const { mainHttpListenPort } = require("./config"); +const app = express(); +const apiRoute = require("./routes/api"); + +app.use(express.json()); +app.use("/", express.static("public/")); +app.use("/api/v1", apiRoute); + +app.get("/", (req, res) => { + res.send("hello"); +}); + +app.listen(mainHttpListenPort, () => { + console.log(`server main: listen on ${mainHttpListenPort}`); +}); \ No newline at end of file diff --git a/package.json b/package.json index 26ec4a0..2ebc709 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "main": "index.js", "license": "MIT", "dependencies": { - "express": "^4.17.2" + "eris": "^0.16.1", + "express": "^4.17.2", + "jsonwebtoken": "^8.5.1" } } diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..89b0668 --- /dev/null +++ b/public/index.html @@ -0,0 +1,49 @@ + + + + + + + Document + + + + + + + + \ No newline at end of file diff --git a/routes/api.js b/routes/api.js new file mode 100644 index 0000000..91c7155 --- /dev/null +++ b/routes/api.js @@ -0,0 +1,56 @@ +const express = require("express"); +const { guildMap } = require("../common"); +const { dangerousAdminMode } = require("../config"); +const { checkAuth, createToken } = require("../tokens"); +const router = express(); + +router.get("/", (req, res) => { + res.status(200).send({ error: false, message: "SUCCESS_API_OK" }); +}); + +router.post("/tokens/create", async (req, res) => { + if (!dangerousAdminMode) + return res.status(403).send({ error: true, message: "ERROR_FEATURE_DISABLED" }); + + const { username, avatarURL, discordID, guildAccess } = req.body; + if (!username || !discordID || !guildAccess) + return res.status(400).send({ error: true, message: "ERROR_BAD_REQUEST" }); + + try { + const token = await createToken({ username, avatarURL, discordID, guildAccess }); + res.status(200).send({ error: false, message: "SUCCESS_TOKEN_CREATED", token }); + } catch(e) { + res.status(500).send({ error: true, message: "ERROR_TOKEN_CREATE_FAILURE" }); + } +}); + +router.post("/guilds/:guildId/channels/:channelId/messages/create", checkAuth(async (req, res) => { + const messageContent = req.body.content; + if (!messageContent) + return res.status(400).send({ error: true, message: "ERROR_NO_MESSAGE_CONTENT" }); + + const guildId = req.params.guildId; + if (!guildId) + return res.status(400).send({ error: true, message: "ERROR_NO_GUILD_ID" }); + + const channelId = req.params.channelId; + if (!channelId) + return res.status(400).send({ error: true, message: "ERROR_NO_CHANNEL_ID" }); + + + const { username, avatarURL, guildAccess } = req.user; + + if (guildAccess.indexOf(guildId) === -1) + return res.status(403).send({ error: true, message: "ERROR_NO_GUILD_ACCESS" }); + + const guild = guildMap.get(guildId); + try { + await guild.discordSendMessage(messageContent, channelId, username, avatarURL); + res.status(201).send({ error: false, message: "SUCCESS_MESSAGE_CREATED" }); + } catch(e) { + console.error("server main: api: message create: error: ", e); + res.status(500).send({ error: true, message: "ERROR_MESSAGE_SEND_FAILURE" }); + } +})); + +module.exports = router; \ No newline at end of file diff --git a/tokens.js b/tokens.js new file mode 100644 index 0000000..55ebfd6 --- /dev/null +++ b/tokens.js @@ -0,0 +1,56 @@ +const jsonwebtoken = require("jsonwebtoken"); +const { jwtSecret } = require("./config"); + +function createToken({ username, avatarURL, discordID, guildAccess }) { + return new Promise((resolve, reject) => { + jsonwebtoken.sign({ username, avatarURL, discordID, guildAccess }, jwtSecret, (err, token) => { + if (err) + return reject(err); + + resolve(token); + }); + }); +} + +function decodeToken(token) { + return new Promise((resolve, reject) => { + jsonwebtoken.verify(token, jwtSecret, (err, token) => { + if (err) + return reject(err); + + resolve(token); + }); + }); +} + +function checkAuth(callback) { + return async (req, res) => { + const token = req.get("authorization"); + if (token) { + let user; + try { + user = await decodeToken(token); + } catch(e) { + res.status(403).send({ error: true, message: "ERROR_FORBIDDEN" }); + return; + } + if (user) { + req.user = user; + req.authenticated = true; + return await callback(req, res); + } else { + res.status(401).send({ error: true, message: "ERROR_UNAUTHORIZED" }); + return; + } + } else { + res.status(401).send({ error: true, message: "ERROR_UNAUTHORIZED" }); + return; + } + }; +} + +module.exports = { + createToken, + decodeToken, + checkAuth +}; diff --git a/yarn.lock b/yarn.lock index 5795407..f3e1991 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31,6 +31,11 @@ body-parser@1.19.1: raw-body "2.4.2" type-is "~1.6.18" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + bytes@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" @@ -75,6 +80,13 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -85,6 +97,16 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +eris@^0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/eris/-/eris-0.16.1.tgz#44b0a9220944fc73dd74538cd614826bfbfcde61" + integrity sha512-fqjgaddSvUlUjA7s85OvZimLrgCwX58Z6FXOIxdNFJdT6XReJ/LOWZKdew2CaalM8BvN2JKzn98HmKYb3zMhKg== + dependencies: + ws "^8.2.3" + optionalDependencies: + opusscript "^0.0.8" + tweetnacl "^1.0.3" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -182,6 +204,74 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -219,7 +309,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.3: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -236,6 +326,11 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +opusscript@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/opusscript/-/opusscript-0.0.8.tgz#00b49e81281b4d99092d013b1812af8654bd0a87" + integrity sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -274,7 +369,7 @@ raw-body@2.4.2: iconv-lite "0.4.24" unpipe "1.0.0" -safe-buffer@5.2.1: +safe-buffer@5.2.1, safe-buffer@^5.0.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -284,6 +379,11 @@ safe-buffer@5.2.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" @@ -328,6 +428,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -350,3 +455,8 @@ vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +ws@^8.2.3: + version "8.4.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" + integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==