const router = require("express").Router(); const RL = require("express-rate-limit"); const safeStorage = require("assistants-safe-storage"); const DiscordOauth2 = require("discord-oauth2"); const oauth = new DiscordOauth2(); module.exports = (app, config, themeConfig) => { const storage = new safeStorage(config.bot.config.token); const scopes = config.guildAfterAuthorization?.use ? ["identify", "guilds", "guilds.join"] : ["identify", "guilds"]; const RateLimits = config.rateLimits || {}; const RateFunctions = {}; const NoRL = (req, res, next) => next(); if (RateLimits.discordOAuth2) { RateFunctions.discordOAuth2 = RL.rateLimit({ windowMs: RateLimits.discordOAuth2.windowMs, max: RateLimits.discordOAuth2.max, message: RateLimits.discordOAuth2.message, store: RateLimits.discordOAuth2.store || new RL.MemoryStore(), }); } router.get("/", (req, res) => { const clientId = req.client.id; const redirectUri = req.redirectUri; let newPage = "/"; if (themeConfig.landingPage?.enabled) newPage = "/dash"; req.session.r = req.query.r || newPage; const authorizeUrl = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${scopes.join("%20")}`; res.redirect(authorizeUrl); }); router.get("/status", async (req, res) => { res.send(req.session?.discordAuthStatus); }); router.get("/callback", RateFunctions.discordOAuth2 ? RateFunctions.discordOAuth2 : NoRL, async (req, res) => { req.session.discordAuthStatus = { loading: true, success: null, state: { error: null, data: null, }, }; const clientId = req.client.id; const clientSecret = req.client.secret; const redirectUri = req.redirectUri; const accessCode = req.query.code; if (!accessCode) return res.redirect("/?error=NoAccessCodeReturnedFromDiscord"); res.redirect("/loading"); let OAuth2Response; let OAuth2UserResponse; let OAuth2GuildsResponse; try { req.session.discordAuthStatus = { loading: true, success: null, state: { error: null, data: "Requesting token...", }, }; req.session.save(function () {}); OAuth2Response = await oauth.tokenRequest({ clientId, clientSecret, code: accessCode, scope: scopes.join(" "), grantType: "authorization_code", redirectUri, }); } catch (err) { console.log("Discord.js Route - OAuth2Response (line 88)\n" + err); req.session.discordAuthStatus = { loading: false, success: false, state: { error: err, data: null, }, }; req.session.save(function () {}); return; } /* Get User from Discord OAuth2 API using gained access_token and update its values with tag and avatarURL */ try { req.session.discordAuthStatus = { loading: true, success: null, state: { error: null, data: "Getting User...", }, }; req.session.save(function () {}); OAuth2UserResponse = await oauth.getUser(OAuth2Response.access_token); } catch (err) { console.log("Discord.js Route - OAuth2UserResponse (line 123)\n" + err); req.session.discordAuthStatus = { loading: false, success: false, state: { error: err, data: null, }, }; req.session.save(function () {}); return; } OAuth2UserResponse.tag = `${OAuth2UserResponse.username}#${OAuth2UserResponse.discriminator}`; OAuth2UserResponse.avatarURL = OAuth2UserResponse.avatar ? `https://cdn.discordapp.com/avatars/${OAuth2UserResponse.id}/${OAuth2UserResponse.avatar}.png?size=1024` : null; /* Save user token in Assistants Secure Storage */ try { storage.SaveUser(OAuth2UserResponse.id, OAuth2Response.access_token); } catch (err) { console.log("Discord.js Route - Assistants Secure Storage (line 147)\n" + err); req.session.discordAuthStatus = { loading: false, success: false, state: { error: err, data: null, }, }; req.session.save(function () {}); return; } /* Save user in session */ req.session.user = OAuth2UserResponse; req.session.loggedInLastTime = true; /* Register user to DBD Stats and emit userLoggedIn event */ try { req.DBDEvents.emit("userLoggedIn", OAuth2UserResponse); } catch (err) { console.log("Discord.js Route - DBDStats register and DBDEvent emit userLoggedIn (line 173)\n" + err); req.session.discordAuthStatus = { loading: false, success: false, state: { error: err, data: null, }, }; req.session.save(function () {}); return; } /* Gain and update session with user guilds */ try { req.session.discordAuthStatus = { loading: true, success: null, state: { error: null, data: "Getting List of User Guilds...", }, }; req.session.save(function () {}); OAuth2GuildsResponse = await oauth.getUserGuilds(OAuth2Response.access_token); } catch (err) { req.config.reportError("Discord.js Route - OAuth2GuildsResponse (line 207)\n" + err); req.session.discordAuthStatus = { loading: false, success: false, state: { error: err, data: null, }, }; req.session.save(function () {}); return; } req.session.guilds = OAuth2GuildsResponse || []; /* Loop and fetch each guild into bots cache */ if (!req.config.disableResolvingGuildCache) { try { req.session.discordAuthStatus = { loading: true, success: null, state: { error: null, data: "Resolving guilds cache...", }, }; req.session.save(function () {}); for (const g of OAuth2GuildsResponse) { try { await req.bot.guilds.fetch(g.id); } catch (e) { /* ... */ } } } catch (err) { console.log("Discord.js Route - OAuth2GuildsResponse Whole Loop (line 244)" + err); req.session.discordAuthStatus = { loading: false, success: false, state: { error: err, data: null, }, }; req.session.save(function () {}); return; } } /* If joining specific guild after authorization is enabled, do it */ if (req.guildAfterAuthorization.use == true) { req.session.discordAuthStatus = { loading: true, success: null, state: { error: null, data: "Authorizing user with guild...", }, }; req.session.save(function () {}); try { await oauth.addMember({ accessToken: OAuth2Response.access_token, botToken: req.botToken, guildId: req.guildAfterAuthorization.guildId, userId: OAuth2UserResponse.id, ...(req.guildAfterAuthorization.options || {}), /* options?: { nickname?: string, roles?: [string], mute?: boolean, deaf?: boolean, } */ }); } catch (err) { req.config.reportError("Discord.js Route - guildAfterAuthorization (line 297)" + err); } } req.session.discordAuthStatus = { loading: false, success: true, state: { error: null, data: null, }, }; req.session.save(function () {}); return; }); router.get("/logout", (req, res) => { const r = req.query.r || "/"; req.session.destroy(); res.redirect(r); }); router.get("/guilds/reload", async (req, res) => { if (!req.session.user) return res.redirect("/discord"); /* Fetch user token */ const access_token = storage.GetUser(req.session.user.id); if (!access_token) return res.send({ error: true, message: "You don't have any access_token saved.", login_again_text: true, }); /* Gain and update session with user guilds */ let OAuth2GuildsResponse; try { OAuth2GuildsResponse = await oauth.getUserGuilds(access_token); } catch (err) { req.config.reportError("Discord.js Route - OAuth2GuildsResponse for ReloadGuilds (line 344)" + err); return res.send({ error: true, message: "An error occured. Access_token is wrong or you're being rate limited.", login_again_text: true, }); } req.session.guilds = OAuth2GuildsResponse || []; /* Loop and fetch each guild into bots cache */ try { const Promises = []; for (const g of OAuth2GuildsResponse) { Promises.push( // eslint-disable-next-line no-unused-vars, no-async-promise-executor new Promise(async (resolve, reject) => { try { await req.bot.guilds.fetch(g.id); } catch (e) { /* ... */ } resolve(1); }), ); try { await Promises.all(Promises); } catch (e) { /* ... */ } } } catch (err) { console.log("Discord.js Route - OAuth2GuildsResponse Whole Loop for ReloadGuilds (line 375)" + err); return res.send({ error: true, message: "An error occured. Access_token is wrong or you're being rate limited.", login_again_text: true, }); } /* Success */ return res.send({ error: false, message: null, login_again_text: false, }); }); return router; };