From 00ebc426d3d0909614fe43afe943814e742ca318 Mon Sep 17 00:00:00 2001 From: delivan Date: Mon, 5 Dec 2022 20:01:24 +0900 Subject: [PATCH] Refactor api route path and types --- pages/api/auth/index.ts | 49 ----------------- .../access-token.ts => twitter-auth-info.ts} | 39 +++++++------- pages/api/twitter-auth-url.ts | 54 +++++++++++++++++++ pages/index.tsx | 9 ++-- pages/verification/index.tsx | 7 +-- types/api-response.ts | 7 +++ 6 files changed, 87 insertions(+), 78 deletions(-) delete mode 100644 pages/api/auth/index.ts rename pages/api/{auth/access-token.ts => twitter-auth-info.ts} (70%) create mode 100644 pages/api/twitter-auth-url.ts create mode 100644 types/api-response.ts diff --git a/pages/api/auth/index.ts b/pages/api/auth/index.ts deleted file mode 100644 index 0162529..0000000 --- a/pages/api/auth/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import crypto from "crypto"; -import { withIronSessionApiRoute } from "iron-session/next"; -import { base64URLEncode } from "../../../utils/encoding"; -import { buildQueryString } from "../../../utils/url"; -import { ironOptions } from "../../../iron.config"; -import { - twitterOAuthBaseUrl, - twitterOAuthScopes, -} from "../../../constants/twitter"; - -export default withIronSessionApiRoute(async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - if ( - !process.env.TWITTER_AUTH_STATE || - !process.env.TWITTER_AUTH_CODE_CHALLENGE - ) { - return res.status(500).send("No state or code_challenge"); - } - - const codeVerifier = base64URLEncode(crypto.randomBytes(32)); - req.session.code_verifier = codeVerifier; - await req.session.save(); - const codeChallenge = base64URLEncode( - crypto.createHash("sha256").update(codeVerifier).digest(), - ); - const authUrlObj = new URL(twitterOAuthBaseUrl); - authUrlObj.search = buildQueryString({ - client_id: process.env.TWITTER_CLIENT_ID, - redirect_uri: process.env.TWITTER_AUTH_CALLBACK_URI, - state: process.env.TWITTER_AUTH_STATE, - scope: twitterOAuthScopes.join(" "), - response_type: "code", - code_challenge_method: "s256", - code_challenge: codeChallenge, - }); - const authUrl = authUrlObj.toString(); - - res.status(200).json({ authUrl }); -}, -ironOptions); - -declare module "iron-session" { - interface IronSessionData { - code_verifier?: string; - } -} diff --git a/pages/api/auth/access-token.ts b/pages/api/twitter-auth-info.ts similarity index 70% rename from pages/api/auth/access-token.ts rename to pages/api/twitter-auth-info.ts index 04dff3d..0782c60 100644 --- a/pages/api/auth/access-token.ts +++ b/pages/api/twitter-auth-info.ts @@ -1,8 +1,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { withIronSessionApiRoute } from "iron-session/next"; -import { request } from "../../../utils/url"; -import { ironOptions } from "../../../iron.config"; -import { twitterApiBaseUrl } from "../../../constants/twitter"; +import { request } from "../../utils/url"; +import { ironOptions } from "../../iron.config"; +import { twitterApiBaseUrl } from "../../constants/twitter"; interface TwitterOAuth2TokenData { token_type: string; @@ -16,23 +16,23 @@ export default withIronSessionApiRoute(async function handler( req: NextApiRequest, res: NextApiResponse, ) { + if ( + !process.env.TWITTER_CLIENT_ID || + !process.env.TWITTER_CLIENT_SECRET || + !process.env.TWITTER_AUTH_CALLBACK_URI + ) { + return res + .status(500) + .send( + "Twitter app client id or client secret or callback URI is not set", + ); + } + + if (!req.session.code_verifier) { + return res.status(401).send("No OAuth2.0 code verifier"); + } + try { - if ( - !process.env.TWITTER_CLIENT_ID || - !process.env.TWITTER_CLIENT_SECRET || - !process.env.TWITTER_AUTH_CALLBACK_URI - ) { - return res - .status(500) - .send( - "Twitter app client id or client secret or callback URI is not set", - ); - } - - if (!req.session.code_verifier) { - return res.status(401).send("No OAuth2.0 code verifier"); - } - const { code, state } = req.query; if (state !== process.env.TWITTER_AUTH_STATE) { return res.status(401).send("State isn't matching"); @@ -61,6 +61,7 @@ export default withIronSessionApiRoute(async function handler( }); } catch (error) { console.log(error); + res.status(500).send("Internal server error "); } }, ironOptions); diff --git a/pages/api/twitter-auth-url.ts b/pages/api/twitter-auth-url.ts new file mode 100644 index 0000000..556185e --- /dev/null +++ b/pages/api/twitter-auth-url.ts @@ -0,0 +1,54 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import crypto from "crypto"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { base64URLEncode } from "../../utils/encoding"; +import { buildQueryString } from "../../utils/url"; +import { ironOptions } from "../../iron.config"; +import { + twitterOAuthBaseUrl, + twitterOAuthScopes, +} from "../../constants/twitter"; + +export default withIronSessionApiRoute(async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + if ( + !process.env.TWITTER_AUTH_STATE || + !process.env.TWITTER_AUTH_CODE_CHALLENGE + ) { + return res.status(500).send("No state or code_challenge"); + } + + try { + const codeVerifier = base64URLEncode(crypto.randomBytes(32)); + req.session.code_verifier = codeVerifier; + await req.session.save(); + const codeChallenge = base64URLEncode( + crypto.createHash("sha256").update(codeVerifier).digest(), + ); + const authUrlObj = new URL(twitterOAuthBaseUrl); + authUrlObj.search = buildQueryString({ + client_id: process.env.TWITTER_CLIENT_ID, + redirect_uri: process.env.TWITTER_AUTH_CALLBACK_URI, + state: process.env.TWITTER_AUTH_STATE, + scope: twitterOAuthScopes.join(" "), + response_type: "code", + code_challenge_method: "s256", + code_challenge: codeChallenge, + }); + const authUrl = authUrlObj.toString(); + + res.status(200).json({ authUrl }); + } catch (error) { + console.error(error); + res.status(500).send("Internal server error"); + } +}, +ironOptions); + +declare module "iron-session" { + interface IronSessionData { + code_verifier?: string; + } +} diff --git a/pages/index.tsx b/pages/index.tsx index d42f8a6..ed552a8 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,12 +1,11 @@ import styles from "../styles/Home.module.css"; - -interface AuthResponse { - authUrl: string; -} +import { TwitterAuthUrlResponse } from "../types/api-response"; export default function Home() { const handleSignInWithTwitter = async () => { - const { authUrl }: AuthResponse = await (await fetch("/api/auth")).json(); + const { authUrl }: TwitterAuthUrlResponse = await ( + await fetch("/api/twitter-auth-url") + ).json(); window.location.href = authUrl; }; diff --git a/pages/verification/index.tsx b/pages/verification/index.tsx index 370323c..8b2c21f 100644 --- a/pages/verification/index.tsx +++ b/pages/verification/index.tsx @@ -1,14 +1,11 @@ import { useEffect, useState } from "react"; - -interface AccessTokenResponse { - accessToken: string; -} +import { TwitterAuthInfoResponse } from "../../types/api-response"; export default function VerificationPage() { const [accessToken, setAccessToken] = useState(); const fetchAccessToken = async (state: string, code: string) => { - const { accessToken }: AccessTokenResponse = await ( + const { accessToken }: TwitterAuthInfoResponse = await ( await fetch(`/api/auth/access-token?state=${state}&code=${code}`) ).json(); diff --git a/types/api-response.ts b/types/api-response.ts new file mode 100644 index 0000000..fa44b55 --- /dev/null +++ b/types/api-response.ts @@ -0,0 +1,7 @@ +export interface TwitterAuthUrlResponse { + authUrl: string; +} + +export interface TwitterAuthInfoResponse { + accessToken: string; +}