npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

discord-strategy

v1.2.0

Published

Passport strategy for Discord OAuth2

Downloads

17

Readme

Discord OAuth2 Strategy for Passport.js

Overview

This repository contains a custom OAuth2 strategy for authenticating with Discord using Passport.js. It facilitates user authentication via Discord and enables the retrieval of user data, including profile information, guilds, and connections.

Installation

To use this strategy, first install Passport.js and then the custom strategy:

npm install passport discord-strategy

Usage

Integrate the strategy into your Express application as follows:

Example Setup

const express = require("express");
const passport = require("passport");
const Strategy = require("discord-strategy");

const app = express();

// Define options for the Strategy
const options = {
  clientID: "YOUR_CLIENT_ID",
  clientSecret: "YOUR_CLIENT_SECRET",
  callbackURL: "http://localhost:3000/auth/discord/callback",
  scope: ["identify", "email", "guilds", "connections"], // Example scopes
};

// Create a new instance of the Strategy
passport.use(new Strategy(options, verify));

// Define the verify function
function verify(accessToken, refreshToken, profile, done) {
  profile.connection((err) => {
    if (err) return done(err, false);
    profile.guilds((err) => {
      if (err) return done(err, false);
      console.log("Authentication successful!", profile);
      // Call clean before saving to the database.
      // Since we're not using any other functionalities,
      // there's no need to call clean here.
      done(null, profile);
    });
  });
}

// Initialize Passport
app.use(passport.initialize());

// Define routes
app.get("/auth/discord", passport.authenticate("discord"));

app.get(
  "/auth/discord/callback",
  passport.authenticate("discord", { session: false }),
  (req, res) => {
    res.send(`
      <h1>Authentication successful!</h1>
      <h2>User Profile:</h2>
      <pre>${JSON.stringify(req.user, null, 2)}</pre>
    `);
  }
);

app.listen(3000, () => {
  console.log("Login via http://localhost:3000/auth/discord");
});

Strategy Options

  • clientID: Your Discord application's Client ID.
  • clientSecret: Your Discord application's Client Secret.
  • callbackURL: The URL to which Discord will redirect after authorization.
  • scope: An array of scopes specifying the level of access (default: ["identify", "email"]).

Consumable Functions

You might ask why functions are attached to the profile instead of just fetching data directly and then putting it on the profile object. The reason lies in the structure of the Discord API and the flow of Passport.js. Since Passport.js is callback-based, if I want to add new functions in the future, I need to check scopes, invloke each function one by one, and then send it, which adds a little complexity. This approach avoids unnecessary complexity, increases the readability of your code, and simplifies logic.

Fancy reasons aside, I’m just lazy, and this way feels good.

These functions are available on the profile object to fetch additional data:

  • profile.connection(callback): Fetches the user's connections. Requires the connections scope.
  • profile.guilds(callback): Fetches the guilds the user is part of. Requires the guilds scope.
  • profile.clean(callback): Cleans the profile object by removing functions.
  • profile.guildJoiner(botToken, serverId, nickname, roles, callback) : Adds the user to a guild with optional parameters for setting a nickname and assigning roles.
  • profile.resolver(key, api, callback): Fetches data from a specified API endpoint and stores it under the given key in the profile.
  • profile.complexResolver(method, api, body, callback): Allows customization of data fetching with more complex API interactions. The access token is sent as a query parameter btw.

Example Profile Object

Before Consuming Connections:

{
  "id": "123456789",
  "username": "exampleUser",
  "connections": [function: "getConnections"],
  "resolver" : [function: "resolver"],
  "complexResolver" : [function: "complexResolver"],
  "clean": [function: "clean"]
}

After Consuming Connections and Applying Clean:

{
  "id": "123456789",
  "username": "exampleUser",
  "connections": [
    { "connectionName": "Steam", "id": "steam_12345" },
    { "connectionName": "Twitch", "id": "twitch_12345" }
  ]
}

Cleaner Function

The clean function removes any functions from the profile object, leaving only the essential data.

clean(profile, done) {
  Object.keys(profile).forEach((key) => {
    if (typeof profile[key] === "function") {
      delete profile[key];
    }
  });
  return done();
}

Example Usage

function verify(accessToken, refreshToken, profile, done) {
  profile.resolver("guilds", "users/@me/guilds", (err) => {
    if (err) return done(err);
    profile.clean(() => {
      // Perform operations like saving to the database here
      done(null, profile);
    });
  });
}

guildJoiner()

function verify(accessToken, refreshToken, profile, done) {
  profile.guildJoiner(
    "bot_token",
    "server_id",
    null, // value to set user's nickname to (string)
    null, // array of role IDs to assign to the user (string[])
    (err) => {
      // Response status codes:
      // 201 => Joined
      // 204 => Already in the guild
      if (err) return done(err, false, "Failed to join guild");
      done(null, profile);
      console.log("Authentication successful!", profile);
    }
  );
}

Resolver Functions

Basic Get Resolver

The resolver function allows for the customization of data fetching for basic GET requests:

profile.resolver = (key, api, done) => {
  this.resolveApi(api, (err, data) => {
    if (err) return done(err);
    profile[key] = data; // Store the resolved data in the profile under the specified key
    return done(null, profile);
  });
};

Example Usage

Fetching Guilds Data with Resolver:

function verify(accessToken, refreshToken, profile, done) {
  profile.resolver("guilds", "users/@me/guilds", (err) => {
    if (err) return done(err);
    done(null, profile); // Stored in profile[guilds]
  });
}

Complex Resolver

The complexResolver function allows for more complex data interactions:

profile.complexResolver(
  "PUT",
  `guilds/${serverId}/members/${profile.id}`,
  {
    Authorization: `Bot ${botToken}`,
    "content-type": "application/json",
  },
  JSON.stringify({
    nick: nickname,
    roles: ["role_id_1", "role_id_2", "role_id_3", "role_id_4"],
  }),
  (err, result, res) => {
    if (err) return done(err, false);
    if (res.statusCode === 201 || res.statusCode === 204)
      return done(null, profile);
  }
);

Basic Information Only

For scenarios where only basic user information is needed:

function verify(accessToken, refreshToken, profile, done) {
  console.log("Fetched", profile);
  return done(null, profile);
}

Before invoking the done function, the cleaner method will be called to remove any unused consumable functions from the profile object. This ensures that only essential data is passed forward.

Refresh Tokens and Additional Handling

If you need to store the refreshToken, manage sessions, or handle other processes unrelated to Discord OAuth, please refer to the Passport.js documentation for more information on managing these tasks or explore other strategies that might be necessary for additional handling.

Changelog

v1.1 Patch

  • No longer required to pass the access token to the consumable functions.
  • Added two new consumable functions: complexResolver() and guildJoiner().

v1.0.1 Patch

  • Bound the cleaner function to the clean property of the profile (profile.clean()).