diff --git a/domains/User.js b/domains/User.js
index 11438ad7..8d659f39 100644
--- a/domains/User.js
+++ b/domains/User.js
@@ -167,6 +167,15 @@ class User extends EventEmitter {
return !!this.twitch_token;
}
+ setDiscordToken(token) {
+ // in memory only, not in DB
+ this.discord_token = token;
+ }
+
+ hasDiscordToken() {
+ return !!this.discord_token;
+ }
+
setGoogleToken(token) {
// in memory only, not in DB
this.google_token = token;
diff --git a/public/images/identity_providers/discord.png b/public/images/identity_providers/discord.png
new file mode 100644
index 00000000..d8abaaf7
Binary files /dev/null and b/public/images/identity_providers/discord.png differ
diff --git a/routes/auth.js b/routes/auth.js
index 3362ac27..2ef13fe2 100644
--- a/routes/auth.js
+++ b/routes/auth.js
@@ -20,6 +20,14 @@ const TWITCH_LOGIN_QS = new URLSearchParams({
force_verify: true,
});
+const DISCORD_LOGIN_BASE_URI = 'https://discord.com/oauth2/authorize?';
+const DISCORD_LOGIN_QS = new URLSearchParams({
+ client_id: process.env.DISCORD_CLIENT_ID,
+ scope: 'identify email',
+ response_type: 'code',
+ force_verify: true,
+});
+
import UserDAO from '../daos/UserDAO.js';
const router = express.Router();
@@ -44,6 +52,16 @@ function getGoogleAuthUrl() {
});
}
+function getDiscordAuthUrl(req) {
+ DISCORD_LOGIN_QS.set(
+ 'redirect_uri',
+ `${req.protocol}://${req.get('host')}/auth/discord/callback`
+ );
+
+ const qs = DISCORD_LOGIN_QS.toString();
+
+ return `${DISCORD_LOGIN_BASE_URI}${qs}`;
+}
if (process.env.IS_PUBLIC_SERVER) {
router.get('/', (_req, res) => {
res.render('login');
@@ -60,6 +78,9 @@ if (process.env.IS_PUBLIC_SERVER) {
router.get('/google', (req, res) => {
res.redirect(getGoogleAuthUrl(req));
});
+ router.get('/discord', (req, res) => {
+ res.redirect(getDiscordAuthUrl(req));
+ });
} else {
router.get('/', (_req, res) => {
res.render('local_login');
@@ -275,6 +296,105 @@ router.get('/google/callback', async (req, res) => {
}
});
+router.get('/discord/callback', async (req, res) => {
+ console.log(`Discord callback received with code [${req.query.code}]`);
+
+ if (!req.query.code) {
+ res
+ .status(400)
+ .send(
+ `Unable to authenticate [${req.query.error}]: ${req.query.error_description}`
+ );
+ return;
+ }
+
+ console.log(`Discord get access token`);
+
+ try {
+ const { body: token } = await got.post(
+ 'https://discord.com/api/oauth2/token',
+ {
+ username: process.env.DISCORD_CLIENT_ID,
+ password: process.env.DISCORD_CLIENT_SECRET,
+ form: {
+ code: req.query.code,
+ grant_type: 'authorization_code',
+ redirect_uri: `${req.protocol}://${req.get(
+ 'host'
+ )}/auth/discord/callback`,
+ },
+ responseType: 'json',
+ }
+ );
+
+ console.log(`Retrieved oauth token`, token);
+
+ // finally can get user data from user id
+ const { body: user_object } = await got.get(
+ 'https://discord.com/api/users/@me',
+ {
+ headers: {
+ authorization: `${token.token_type} ${token.access_token}`,
+ },
+ responseType: 'json',
+ }
+ );
+
+ console.log({ data: user_object });
+
+ // transform discord response into twitch-like user object
+ user_object.profile_image_url = `https://cdn.discordapp.com/avatars/${user_object.id}/${user_object.avatar}?size=512`;
+ user_object.login = user_object.username;
+ user_object.secret = ULID.ulid();
+ user_object.type = '';
+ user_object.display_name = user_object.global_name;
+
+ // NEED more logic here to check BOTh the users and oauth users table sigh...
+ const user = await UserDAO.createUser(user_object, {
+ provider: 'discord',
+ current_user: req.session?.user,
+ pending_linkage: req.session?.pending_linkage_expiry > Date.now(),
+ });
+
+ // pending linkage is single use
+ if (req.session?.pending_linkage_expiry) {
+ req.session.pending_linkage_expiry = null;
+ delete req.session.pending_linkage_expiry;
+ }
+
+ console.log(
+ `Retrieved user object from DB for ${user_object.id} (${user_object.login})`
+ );
+
+ user.setDiscordToken(token);
+
+ // TODO: modify when adding google auth
+ req.session.token = {
+ discord: token,
+ };
+
+ req.session.user = {
+ id: user.id,
+ login: user.login,
+ secret: user.secret,
+ profile_image_url: user.profile_image_url,
+ };
+
+ req.session.save(() => {
+ console.log('Stored session user as', req.session.user);
+ res.redirect(req.session.auth_success_redirect || '/');
+ });
+ } catch (err) {
+ console.error(`Error when processing Discord callback`);
+ console.error(err);
+ res
+ .status(500)
+ .send(
+ `An unexpected error occured with your Twich login: ${err.message}. Please try again later`
+ );
+ }
+});
+
router.get('/link/twitch', middlewares.assertSession, async (req, res) => {
req.session.auth_success_redirect = '/auth/link';
req.session.pending_linkage_expiry = Date.now() + 120000; // 2 minute max to complete linkage
@@ -287,6 +407,12 @@ router.get('/link/google', middlewares.assertSession, async (req, res) => {
res.redirect(getGoogleAuthUrl(req));
});
+router.get('/link/discord', middlewares.assertSession, async (req, res) => {
+ req.session.auth_success_redirect = '/auth/discord';
+ req.session.pending_linkage_expiry = Date.now() + 120000; // 2 minute max to complete linkage
+ res.redirect(getDiscordAuthUrl(req));
+});
+
router.get('/link', middlewares.assertSession, async (req, res) => {
const identities = await UserDAO.getIdentities(req.session.user.id);
const providers = identities.reduce((acc, identity) => {
@@ -310,7 +436,7 @@ router.get(
identity => (identity.id = req.params.identity_id)
);
- // we obnly accept to remove an identity if it's not the last one
+ // we only accept to remove an identity if it's not the last one
if (identity && identities.length > 1) {
const res = await UserDAO.removeIdentity(
req.session.user.id,
diff --git a/views/header.ejs b/views/header.ejs
index a385122b..a472d698 100644
--- a/views/header.ejs
+++ b/views/header.ejs
@@ -128,6 +128,9 @@
Log in with Twitch
+
+ Log in with Discord
+
Log in with Google
diff --git a/views/link.ejs b/views/link.ejs
index 6f0d08bf..1633dad9 100644
--- a/views/link.ejs
+++ b/views/link.ejs
@@ -71,7 +71,7 @@
<% } %>
- <% }) %>
+ <% }) %>
@@ -86,6 +86,11 @@
Link Twitch account
<% } %>
+ <% if (!providers.discord) { %>
+
+ Link Discord account
+
+ <% } %>
diff --git a/views/login.ejs b/views/login.ejs
index 8a5cc4f1..70f1384c 100644
--- a/views/login.ejs
+++ b/views/login.ejs
@@ -28,9 +28,14 @@
Log in with Twitch
+
+ Log in with Discord
+
+
Log in with Google
+