import { createClient } from "redis"; import * as passport from "passport"; import * as session from "express-session"; import RedisStore from "connect-redis"; import * as OAuth2Strategy from "passport-oauth2"; import { Profile, Strategy } from "passport-github2"; import * as express from "express"; import config from "../../config"; import UserModel from "../../core/model/users/users.model"; import { IUserDocument } from "../../core/model/users/users.types"; import AnonymousError from "../../core/AnonymousError"; import AnonymizedPullRequestModel from "../../core/model/anonymizedPullRequests/anonymizedPullRequests.model"; export function ensureAuthenticated( req: express.Request, res: express.Response, next: express.NextFunction ) { if (req.isAuthenticated()) { return next(); } res.status(401).json({ error: "not_connected" }); } const verify = async ( accessToken: string, refreshToken: string, profile: Profile, done: OAuth2Strategy.VerifyCallback ): Promise => { let user: IUserDocument | null; try { const now = new Date(); user = await UserModel.findOne({ "externalIDs.github": profile.id }); if (user) { await UserModel.updateOne( { _id: user._id }, { $set: { "accessTokens.github": accessToken, "accessTokenDates.github": now, }, } ); await AnonymizedPullRequestModel.updateMany( { owner: user._id }, { "source.accessToken": accessToken } ); user = await UserModel.findById(user._id); } else { // Check if a user with this username already exists (e.g. created // manually without externalIDs.github). Link the GitHub ID to the // existing account instead of creating a duplicate that would lose // the isAdmin flag. user = await UserModel.findOne({ username: profile.username }); if (user) { await UserModel.updateOne( { _id: user._id }, { $set: { "externalIDs.github": profile.id, "accessTokens.github": accessToken, "accessTokenDates.github": now, }, } ); user = await UserModel.findById(user._id); } else { const photo = profile.photos ? profile.photos[0]?.value : null; user = new UserModel({ username: profile.username, accessTokens: { github: accessToken, }, accessTokenDates: { github: now, }, externalIDs: { github: profile.id, }, emails: profile.emails?.map((email) => { return { email: email.value, default: false }; }), photo, }); if (user.emails?.length) user.emails[0].default = true; await user.save(); } } done(null, { username: profile.username, accessToken, refreshToken, profile, user, }); } catch (error) { console.error(error); done( new AnonymousError("unable_to_connect_user", { httpStatus: 500, object: profile, cause: error as Error, }) ); } }; passport.use( new Strategy( { clientID: config.CLIENT_ID, clientSecret: config.CLIENT_SECRET, callbackURL: config.AUTH_CALLBACK, }, verify ) ); passport.serializeUser((user: Express.User, done) => { done(null, user); }); passport.deserializeUser((user: Express.User, done) => { done(null, user); }); export function initSession() { const redisClient = createClient({ legacyMode: false, socket: { port: config.REDIS_PORT, host: config.REDIS_HOSTNAME, }, }); redisClient.on("error", (err) => console.log("Redis Client Error", err)); redisClient.connect(); const redisStore = new RedisStore({ client: redisClient, prefix: "anoGH_session:", }); return session({ secret: config.SESSION_SECRET, store: redisStore, saveUninitialized: false, resave: false, }); } export const router = express.Router(); router.get( "/login", passport.authenticate("github", { scope: ["repo"] }), // Note the scope here function (req: express.Request, res: express.Response) { res.redirect("/"); } ); router.get( "/auth", passport.authenticate("github", { failureRedirect: "/" }), function (req: express.Request, res: express.Response) { res.redirect("/"); } );