Skip to content

Commit

Permalink
refactor(#25): migrated to passport.js
Browse files Browse the repository at this point in the history
  • Loading branch information
sametcodes committed Mar 7, 2023
1 parent c36a771 commit 76581df
Show file tree
Hide file tree
Showing 14 changed files with 1,676 additions and 534 deletions.
1,526 changes: 1,221 additions & 305 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,25 @@
"@vercel/analytics": "^0.1.11",
"next": "13.0.1",
"next-auth": "^4.19.0",
"next-connect": "^0.8.1",
"passport": "^0.6.0",
"passport-github2": "^0.1.12",
"passport-oauth2": "^1.7.0",
"passport-oauth2-refresh": "^2.1.0",
"passport-stack-exchange": "^1.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"simple-oauth2": "^5.0.0"
"react-dom": "18.2.0"
},
"devDependencies": {
"@commitlint/cli": "^17.4.4",
"@commitlint/config-conventional": "^17.4.4",
"@types/node": "18.11.9",
"@types/passport": "^1.0.12",
"@types/passport-github2": "^1.2.5",
"@types/passport-oauth2": "^1.4.12",
"@types/passport-oauth2-refresh": "^1.1.1",
"@types/react": "18.0.24",
"@types/react-dom": "18.0.8",
"@types/simple-oauth2": "^5.0.2",
"chalk": "^4.1.0",
"env-cmd": "^10.1.0",
"eslint": "8.34.0",
"eslint-config-next": "13.2.0",
Expand Down
99 changes: 59 additions & 40 deletions pages/api/oauth/[...route].ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,89 @@
import { NextApiRequest, NextApiResponse } from "next";
import { unstable_getServerSession } from "next-auth";
import { generateRandomString } from "@utils";
import { NextApiRequest, NextApiResponse, NextApiHandler } from "next";
import { getServerSession } from "next-auth";
import { authOptions } from "@pages/api/auth/[...nextauth]";

import { providers } from "@services/oauth";
import actions from "@services/oauth/actions";
import passport from "passport";
import nextConnect from "next-connect";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await unstable_getServerSession(req, res, authOptions);
import StackOverflowProvider from "@services/oauth/providers/stackoverflow";
import GithubProvider from "@services/oauth/providers/github";
import WakatimeProvider from "@services/oauth/providers/wakatime";

async function handler(req: NextApiRequest, res: NextApiResponse, next: any) {
const session = await getServerSession(req, res, authOptions);
if (!session) return res.redirect("/login");

const [action, platform]: string[] = req.query.route as string[];

if (req.method !== "GET") return res.status(405).end();
if (Object.keys(providers).indexOf(platform) === -1)
return res.status(404).send("Not Found");

if (action === "callback") {
const provider = providers[platform];
const params = provider.getTokenParam(provider, req.query);
if (action === "connect") {
return passport.authenticate(platform)(req, res);
}

try {
const accessToken = await provider.authorization.getToken(params);
if (action === "callback") {
return passport.authenticate(
platform,
{
failureRedirect: "/login",
},
async (error: Error | null, data: any) => {
if (error) {
return res.status(500).send(error.message);
}

const profile = await provider.getProfile(accessToken.token);
if (!profile) return res.redirect("/login");
await actions.connect({
token: data.token,
profile: data.profile,
session,
platformCode: platform,
});

await actions.signin({ accessToken, session, provider, profile });
return res.redirect("/login");
} catch (error) {
if (error instanceof Error) {
console.log("Access Token Error", error.message);
return res.send(error.message);
return res.redirect("/");
}
}
}

if (action === "connect") {
const provider = providers[platform];
if (provider.connect_url) {
return res.redirect(provider.connect_url);
}

const redirect_uri = provider.authorization.authorizeURL({
redirect_uri: provider.redirect_uri,
scope: provider.scope,
state: generateRandomString(),
});
return res.redirect(redirect_uri);
)(req, res, next);
}

if (action === "session") {
try {
const connection = await actions.getConnections({ session, platform });
const connection = await actions.getConnections({
session,
platformCode: platform,
});
if (!connection) {
return res.status(403).send("Forbidden");
}
return res.json(connection);
} catch (err) {
if (err instanceof Error) {
return res.status(404).send(err.message);
}
return res.status(500).send("Internal Server Error");
}
}

if (action === "disconnect") {
try {
await actions.disconnect({ session, platformCode: platform });
return res.redirect("/login");
} catch (err) {
if (err instanceof Error) {
return res.status(404).send(err.message);
}
return res.status(500).send("Internal Server Error");
}
}

return res.status(404).send("Not Found");
}

passport.use("stackoverflow", StackOverflowProvider);
passport.use("github", GithubProvider);
passport.use("wakatime", WakatimeProvider);

export default nextConnect()
.use(passport.initialize())
.use((req: NextApiRequest, res: NextApiResponse, next: any) => {
return handler(req, res, next);
});
12 changes: 11 additions & 1 deletion pages/api/platform/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@ import * as services from "@services/platform/github";
import * as templates from "@components/svgs/github";

import handlePlatformAPI from "@services/api/handler";
export default handlePlatformAPI("github", services, templates);

import nextConnect from "next-connect";
import GithubProvider from "@services/oauth/providers/github";
import passport from "passport";
import refresh from "passport-oauth2-refresh";

passport.use("github", GithubProvider);
refresh.use("github", GithubProvider);
export default nextConnect()
.use(passport.initialize())
.get(handlePlatformAPI("github", services, templates));
12 changes: 11 additions & 1 deletion pages/api/platform/stackoverflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@ import * as services from "@services/platform/stackoverflow";
import * as templates from "@components/svgs/stackoverflow";

import handlePlatformAPI from "@services/api/handler";
export default handlePlatformAPI("stackoverflow", services, templates);

import nextConnect from "next-connect";
import StackOverflowProvider from "@services/oauth/providers/stackoverflow";
import passport from "passport";
import refresh from "passport-oauth2-refresh";

passport.use("stackoverflow", StackOverflowProvider);
refresh.use("stackoverflow", StackOverflowProvider);
export default nextConnect()
.use(passport.initialize())
.get(handlePlatformAPI("stackoverflow", services, templates));
12 changes: 11 additions & 1 deletion pages/api/platform/wakatime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@ import * as services from "@services/platform/wakatime";
import * as templates from "@components/svgs/wakatime";

import handlePlatformAPI from "@services/api/handler";
export default handlePlatformAPI("wakatime", services, templates);

import nextConnect from "next-connect";
import WakatimeProvider from "@services/oauth/providers/wakatime";
import passport from "passport";
import refresh from "passport-oauth2-refresh";

passport.use("wakatime", WakatimeProvider);
refresh.use("wakatime", WakatimeProvider);
export default nextConnect()
.use(passport.initialize())
.get(handlePlatformAPI("wakatime", services, templates));
50 changes: 49 additions & 1 deletion services/api/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
import { getPlatformResponse } from "@services/platform/response";
import prisma from "@services/prisma";

import { requestNewAccessToken } from "passport-oauth2-refresh";
import actions from "@services/oauth/actions";
import { Connection } from "@prisma/client";

type PlatformAPIHandler = {
(platformCode: string, services: any, templates: any): NextApiHandler;
};
Expand Down Expand Up @@ -32,21 +36,65 @@ const handlePlatformAPI: PlatformAPIHandler = (
where: { userId: uid, platformId: platform.id },
});

const connection = await prisma.connection.findFirst({
var connection = await prisma.connection.findFirst({
where: { userId: uid, platformId: platform.id },
});

if (!connection)
return res
.status(404)
.json({ message: "User has no connection on this platform" });

// Refreshing access token
// TODO: refactor this
// it should work on the network request level, not on the API layers
if (connection.expires_at && Date.now() > connection.expires_at) {
const updated_connection = await new Promise<Connection | Error>(
(resolve, reject) => {
if (!connection) return reject("No connection found");
requestNewAccessToken(
platformCode,
connection.refresh_token,
async (
err: { statusCode: number; data?: any },
access_token: string,
refresh_token: string,
result: any
) => {
if (err) {
console.log(err);
res.status(err.statusCode).json({ message: err.data });
throw new Error(err.data);
}

if (!connection) return reject("No connection found");

return actions
.updateConnection({
connection,
data: {
access_token,
expires_at: Date.now() + Number(result.expires_in) * 1000,
},
})
.then(resolve);
}
);
}
);

if (updated_connection instanceof Error) return;
connection = updated_connection;
}

const result = await getPlatformResponse(
req.query,
services,
templates,
connection,
userConfig
);

if (result.success === false)
return res.status(result.status).json({ message: result.error });

Expand Down
Loading

0 comments on commit 76581df

Please sign in to comment.