sendou.ink/server/auth.ts
2022-02-19 22:56:43 +02:00

149 lines
4.4 KiB
TypeScript

import type { Express } from "express";
import invariant from "tiny-invariant";
import session from "express-session";
import cookieParser from "cookie-parser";
import passport from "passport";
import { Strategy as DiscordStrategy } from "passport-discord";
import { db } from "../app/utils/db.server";
declare module "express-session" {
export interface SessionData {
returnTo?: string;
}
}
export function setUpAuth(app: Express): void {
invariant(
process.env.DISCORD_CLIENT_ID,
"env var DISCORD_CLIENT_ID undefined"
);
invariant(
process.env.DISCORD_CLIENT_SECRET,
"env var DISCORD_CLIENT_SECRET undefined"
);
invariant(process.env.COOKIE_SECRET, "env var COOKIE_SECRET undefined");
passport.use(
new DiscordStrategy(
{
clientID: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
callbackURL: `${process.env.FRONT_PAGE_URL}auth/discord/callback`,
scope: ["identify", "connections"],
},
function (_accessToken, refreshToken, loggedInUser, cb) {
db.user
.upsert({
create: {
discordId: loggedInUser.id,
discordName: loggedInUser.username,
discordDiscriminator: loggedInUser.discriminator,
discordAvatar: loggedInUser.avatar,
discordRefreshToken: refreshToken,
...parseConnections(),
},
update: {
discordName: loggedInUser.username,
discordDiscriminator: loggedInUser.discriminator,
discordAvatar: loggedInUser.avatar,
discordRefreshToken: refreshToken,
...parseConnections(),
},
where: {
discordId: loggedInUser.id,
},
})
.then((user) => {
return cb(null, {
id: user.id,
discordId: user.discordId,
discordAvatar: user.discordAvatar,
});
})
.catch((err: unknown) => {
if (err instanceof Error || err === undefined || err === null) {
cb(err);
}
});
function parseConnections() {
if (!loggedInUser.connections) return null;
const result: {
twitch?: string;
twitter?: string;
youtubeId?: string;
youtubeName?: string;
} = {};
for (const connection of loggedInUser.connections) {
if (connection.visibility !== 1 || !connection.verified) continue;
switch (connection.type) {
case "twitch":
result.twitch = connection.name;
break;
case "twitter":
result.twitter = connection.name;
break;
case "youtube":
result.youtubeId = connection.id;
result.youtubeName = connection.name;
}
}
return result;
}
}
)
);
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
// @ts-expect-error it is guaranteed it's of a certain shape without an extra check
done(null, user);
});
app.use(cookieParser());
app.use(
session({
secret: process.env.COOKIE_SECRET,
resave: true,
saveUninitialized: true,
})
);
app.use(passport.initialize());
app.use(passport.session());
app.post("/auth/discord", (req, res) => {
const returnTo = req.query.origin ?? req.header("Referer");
if (returnTo) {
invariant(typeof returnTo === "string", "returnTo is not string");
req.session.returnTo = returnTo;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return passport.authenticate("discord")(req, res);
});
app.get("/auth/discord/callback", (req, res) => {
const returnTo = req.session.returnTo ?? process.env.FRONT_PAGE_URL;
if (req.session.returnTo) {
delete req.session.returnTo;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return passport.authenticate("discord", {
failureRedirect: "/login",
successRedirect: returnTo,
})(req, res);
});
app.use(function (req, _res, next) {
if (req.session.returnTo) {
delete req.session.returnTo;
}
next();
});
}