"use strict"; const RequestHandler = require("./eris/rest/RequestHandler"); /** * Make requests to discord's OAuth2 API * @extends requestHandler */ class OAuth extends RequestHandler { /** * * @arg {Object} opts * @arg {String?} opts.version The version of the Discord API to use. Defaults to v7. * @arg {Number} [opts.requestTimeout=15000] A number of milliseconds before requests are considered timed out * @arg {Number} [opts.latencyThreshold=30000] The average request latency at which the RequestHandler will start emitting latency errors * @arg {Number} [opts.ratelimiterOffset=0] A number of milliseconds to offset the ratelimit timing calculations by * @arg {String?} opts.clientId Your application's client id * @arg {String?} opts.clientSecret Your application's client secret * @arg {String?} opts.redirectUri Your URL redirect uri * @arg {String?} opts.credentials Base64 encoding of the UTF-8 encoded credentials string of your application */ constructor(opts = {}) { super({ version: opts.version || "v7", requestTimeout: opts.requestTimeout || 15000, latencyThreshold: opts.latencyThreshold || 30000, ratelimiterOffset: opts.ratelimiterOffset || 0, }); this.client_id = opts.clientId; this.client_secret = opts.clientSecret; this.redirect_uri = opts.redirectUri; this.credentials = opts.credentials; } _encode(obj) { let string = ""; for (const [key, value] of Object.entries(obj)) { if (!value) continue; string += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`; } return string.substring(1); } /** * Exchange the code returned by discord in the query for the user access token * If specified, can also use refresh_token to get a new valid token * Read discord's OAuth2 documentation for a full example (https://discord.com/developers/docs/topics/oauth2) * @arg {Object} opts The object containing the parameters for the request * @arg {String?} opts.clientId Your application's client id * @arg {String?} opts.clientSecret Your application's client secret * @arg {String} opts.grantType Either authorization_code or refresh_token * @arg {String?} opts.code The code from the querystring * @arg {String?} opts.refreshToken The user's refresh token * @arg {String?} opts.redirectUri Your URL redirect uri * @arg {String} opts.scope The scopes requested in your authorization url, space-delimited * @returns {Promise} */ tokenRequest(opts = {}) { const obj = { client_id: opts.clientId || this.client_id, client_secret: opts.clientSecret || this.client_secret, grant_type: undefined, code: undefined, refresh_token: undefined, redirect_uri: opts.redirectUri || this.redirect_uri, scope: opts.scope instanceof Array ? opts.scope.join(" ") : opts.scope, }; if (opts.grantType === "authorization_code") { obj.code = opts.code; obj.grant_type = opts.grantType; } else if (opts.grantType === "refresh_token") { obj.refresh_token = opts.refreshToken; obj.grant_type = opts.grantType; } else throw new Error("Invalid grant_type provided, it must be either authorization_code or refresh_token"); const encoded_string = this._encode(obj); return this.request("POST", "/oauth2/token", encoded_string, { contentType: "application/x-www-form-urlencoded", }); } /** * Revoke the user access token * @arg {String} access_token The user access token * @arg {String} credentials Base64 encoding of the UTF-8 encoded credentials string of your application * @returns {Promise} */ revokeToken(access_token, credentials) { if (!credentials && !this.credentials) throw new Error("Missing credentials for revokeToken method"); return this.request("POST", "/oauth2/token/revoke", `token=${access_token}`, { auth: { type: "Basic", creds: credentials || this.credentials, }, contentType: "application/x-www-form-urlencoded", }); } /** * Request basic user data * Requires the `identify` scope * @arg {String} access_token The user access token * @returns {Promise} */ getUser(access_token) { return this.request("GET", "/users/@me", undefined, { auth: { type: "Bearer", creds: access_token, }, contentType: "application/json", }); } /** * Request all the guilds the user is in * Requires the `guilds` scope * @arg {String} access_token The user access token * @returns {Promise} */ getUserGuilds(access_token) { return this.request("GET", "/users/@me/guilds", undefined, { auth: { type: "Bearer", creds: access_token, }, contentType: "application/json", }); } /** * Request a user's connections * Requires the `connections` scope * @arg {String} access_token The user access token * @returns {Promise} */ getUserConnections(access_token) { return this.request("GET", "/users/@me/connections", undefined, { auth: { type: "Bearer", creds: access_token, }, contentType: "application/json", }); } /** * Force a user to join a guild * Requires the `guilds.join` scope * @arg {Object} opts * @arg {String} opts.guildId The ID of the guild to join * @arg {String} opts.userId The ID of the user to be added to the guild * @arg {Boolean?} opts.deaf Whether the user is deafened in voice channels * @arg {Boolean?} opts.mute Whether the user is muted in voice channels * @arg {String?} opts.nickname Value to set users nickname to * @arg {String[]?} opts.roles Array of role ids the member is assigned * @arg {String} opts.accessToken The user access token * @arg {String} opts.botToken The token of the bot used to authenticate * @returns {Promise} */ addMember(opts) { return this.request("PUT", `/guilds/${opts.guildId}/members/${opts.userId}`, { deaf: opts.deaf, mute: opts.mute, nick: opts.nickname, roles: opts.roles, access_token: opts.accessToken, }, { auth: { type: "Bot", creds: opts.botToken, }, contentType: "application/json", }); } /** * * @arg {Object} opts * @arg {String} opts.clientId Your application's client id * @arg {String?} opts.prompt Controls how existing authorizations are handled, either consent or none (for passthrough scopes authorization is always required). * @arg {String?} opts.redirectUri Your URL redirect uri * @arg {String?} opts.responseType The response type, either code or token (token is for client-side web applications only). Defaults to code * @arg {String | Array} opts.scope The scopes for your URL * @arg {String?} opts.state A unique cryptographically secure string (https://discord.com/developers/docs/topics/oauth2#state-and-security) * @arg {Number?} opts.permissions The permissions number for the bot invite (only with bot scope) (https://discord.com/developers/docs/topics/permissions) * @arg {String?} opts.guildId The guild id to pre-fill the bot invite (only with bot scope) * @arg {Boolean?} opts.disableGuildSelect Disallows the user from changing the guild for the bot invite, either true or false (only with bot scope) * @returns {String} */ generateAuthUrl(opts = {}) { const obj = { client_id: opts.clientId || this.client_id, prompt: opts.prompt || undefined, redirect_uri: opts.redirectUri || this.redirect_uri, response_type: opts.responseType || "code", scope: opts.scope instanceof Array ? opts.scope.join(" ") : opts.scope, permissions: opts.permissions || undefined, guild_id: opts.guildId || undefined, disable_guild_select: opts.disableGuildSelect || undefined, state: opts.state || undefined, }; const encoded_string = this._encode(obj); return `https://discord.com/api/oauth2/authorize?${encoded_string}`; } } module.exports = OAuth;