diff --git a/bot/scripts/createEnv.sh b/bot/scripts/createEnv.sh index c22c4f13a..66056f063 100644 --- a/bot/scripts/createEnv.sh +++ b/bot/scripts/createEnv.sh @@ -12,6 +12,7 @@ if [[ "$ENV" != "PROD" ]]; then echo "DEV_SERVER_ID=769803764021592104" >> "$DOT_ENV_PATH" # Where is testing taking place? echo "BOT_CLIENT_SECRET=${NON_PROD_BOT_CLIENT_SECRET:-"_"}" >> "$DOT_ENV_PATH" echo "BOT_CLIENT_ID=${NON_PROD_BOT_CLIENT_ID:-"_"}" >> "$DOT_ENV_PATH" + echo "MONGO_URL=${NON_PROD_MONGO_URL:-"_"}" >> "$DOT_ENV_PATH" echo "CONF_JSON5_LOCATION=./conf.json5" >> "$DOT_ENV_PATH" else # Prod environment @@ -19,5 +20,6 @@ else echo "DISCORD_TOKEN=${PROD_DISCORD_TOKEN:-"_"}" >> "$DOT_ENV_PATH" echo "BOT_CLIENT_SECRET=${PROD_BOT_CLIENT_SECRET:-"_"}" >> "$DOT_ENV_PATH" echo "BOT_CLIENT_ID=${PROD_BOT_CLIENT_ID:-"_"}" >> "$DOT_ENV_PATH" + echo "MONGO_URL=${PROD_MONGO_URL:-"_"}" >> "$DOT_ENV_PATH" echo "CONF_JSON5_LOCATION=./conf.json5" >> "$DOT_ENV_PATH" fi diff --git a/bot/src/mongo/mongo.ts b/bot/src/mongo/mongo.ts new file mode 100644 index 000000000..af91fa65d --- /dev/null +++ b/bot/src/mongo/mongo.ts @@ -0,0 +1,18 @@ +import { Snowflake } from "discord.js/typings/index.js"; +import monk from "monk"; +import { integer } from "tachi-common"; +import { ProcessEnv } from "../setup"; + +const monkDB = monk(ProcessEnv.MONGO_URL); + +export interface DiscordUserMapDocument { + userID: integer; + discordID: Snowflake; + tachiApiToken: string; +} + +const db = { + discordUserMap: monkDB.get("discordUserMap") +}; + +export default db; diff --git a/bot/src/server/server.ts b/bot/src/server/server.ts index 654d57b44..5e96f8fea 100644 --- a/bot/src/server/server.ts +++ b/bot/src/server/server.ts @@ -2,9 +2,10 @@ import express, { Express } from "express"; import { LoggerLayers } from "../config"; import { createLayeredLogger } from "../utils/logger"; import { ValidateWebhookRequest } from "./middleware"; -import { WebhookEvents, APITokenDocument } from "tachi-common"; +import { WebhookEvents, APITokenDocument, PublicUserDocument } from "tachi-common"; import { BotConfig, ProcessEnv } from "../setup"; -import { RequestTypes, TachiServerV1Request } from "../utils/fetch-tachi"; +import { RequestTypes, TachiServerV1Get, TachiServerV1Request } from "../utils/fetch-tachi"; +import db from "../mongo/mongo"; const app: Express = express(); @@ -48,21 +49,21 @@ app.post("/webhook", ValidateWebhookRequest, (req, res) => { const statusCode = 200; switch (webhookEvent.type) { - case "class-update/v1": - case "goal-achieved/v1": - case "milestone-achieved/v1": + case "class-update/v1": + case "goal-achieved/v1": + case "milestone-achieved/v1": // @TODO add actual webhook handlers. // These will set the value of statusCode. - break; - default: + break; + default: // According to the types, this should never happen. // However, tachi-server/common may recieve an update // to define new webhooks, and the bot might not // get around to updating in time. - return res.status(501).json({ - success: false, - description: `The type ${(webhookEvent as WebhookEvents).type} is unsupported.` - }); + return res.status(501).json({ + success: false, + description: `The type ${(webhookEvent as WebhookEvents).type} is unsupported.` + }); } return res.sendStatus(statusCode); @@ -86,7 +87,7 @@ app.get("/oauth/callback", async (req, res) => { return res.status(400).send("Bad Request."); } - const tokenRes = await TachiServerV1Request(RequestTypes.POST, "/oauth/token", { + const tokenRes = await TachiServerV1Request(RequestTypes.POST, "/oauth/token", null, { code: req.query.code, client_id: ProcessEnv.BOT_CLIENT_ID, client_secret: ProcessEnv.BOT_CLIENT_SECRET, @@ -101,9 +102,26 @@ app.get("/oauth/callback", async (req, res) => { }); } - // const discordID = req.query.context; - // const apiToken = tokenRes.body.token; - // @todo Store discordID & apiToken somewhere. + const discordID = req.query.context; + const apiToken = tokenRes.body.token!; + + const whoamiRes = await TachiServerV1Get("/api/v1/users/me", apiToken); + + if (!whoamiRes.success) { + logger.error("Failed to request user with token we just got?", { discordID }); + // @todo actually make this html file + return res.sendFile("../pages/account-link-failed.html"); + } + + const user = whoamiRes.body; + + logger.info(`Saving user-discord-link for ${user.username} (#${user.id}).`); + + await db.discordUserMap.insert({ + discordID, + tachiApiToken: apiToken, + userID: user.id + }); return res.sendFile("../pages/account-linked.html"); }); diff --git a/bot/src/setup.ts b/bot/src/setup.ts index 2754f8724..41b2a51b2 100644 --- a/bot/src/setup.ts +++ b/bot/src/setup.ts @@ -37,7 +37,7 @@ function ParseBotConfig(fileLoc = "conf.json5"): BotConfig { SERVER_PORT: Prudence.isPositiveNonZeroInteger, TACHI_SERVER_LOCATION: "string", OUR_URL: "string", - GENERIC_ERROR_MESSAGE: "string" + GENERIC_ERROR_MESSAGE: "string", }); if (err) { @@ -54,6 +54,7 @@ export interface ProcessEnvironment { DISCORD_TOKEN: string; BOT_CLIENT_SECRET: string; BOT_CLIENT_ID: string; + MONGO_URL: string; } function ParseEnvVars() { @@ -63,7 +64,8 @@ function ParseEnvVars() { ENV: "string", DISCORD_TOKEN: "string", BOT_CLIENT_SECRET: "string", - BOT_CLIENT_ID: "string" + BOT_CLIENT_ID: "string", + MONGO_URL: "string", }, {}, { allowExcessKeys: true } diff --git a/bot/src/utils/fetch-tachi.ts b/bot/src/utils/fetch-tachi.ts index f29641e3c..23750cbad 100644 --- a/bot/src/utils/fetch-tachi.ts +++ b/bot/src/utils/fetch-tachi.ts @@ -30,6 +30,7 @@ export enum RequestTypes { export async function TachiServerV1Request( method: Exclude, url: string, + token?: string | null, body: unknown = {} ): Promise> { const realUrl = PrependTachiUrl(url, "1"); @@ -41,7 +42,8 @@ export async function TachiServerV1Request( const res = await axios(realUrl, { method, headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", + Authorization: token ? `Bearer ${token}` : undefined, }, data: JSON.stringify(body) }); @@ -69,6 +71,7 @@ export async function TachiServerV1Request( */ export async function TachiServerV1Get( url: string, + token?: string, params: Record = {} ): Promise> { try { @@ -76,7 +79,9 @@ export async function TachiServerV1Get( const realUrl = `${PrependTachiUrl(url, "1")}?${urlParams.toString()}`; - const res = await axios(realUrl, { method: RequestTypes.GET }); + const res = await axios(realUrl, { method: RequestTypes.GET, headers: { + Authorization: token ? `Bearer ${token}` : undefined, + } }); const json = (await res.data) as APIResponse; const contents = { ...json, statusCode: res.status };