From 59bcf7ec9081adee4e4a78044f648bde4c80a53a Mon Sep 17 00:00:00 2001 From: gautamgambhir97 Date: Thu, 11 Jul 2024 14:54:28 +0530 Subject: [PATCH 1/3] feat: revert some files and login button --- .env.local | 5 -- components/account-menu.tsx | 80 +++++++++++++++++ components/api-endpoint.tsx | 31 ++++++- pages/api/api-requests.ts | 103 ++++++++++++++++++++++ theme/fetch-ai-docs/components/navbar.tsx | 25 ++++++ 5 files changed, 238 insertions(+), 6 deletions(-) delete mode 100644 .env.local create mode 100644 components/account-menu.tsx create mode 100644 pages/api/api-requests.ts diff --git a/.env.local b/.env.local deleted file mode 100644 index a496da6f9..000000000 --- a/.env.local +++ /dev/null @@ -1,5 +0,0 @@ -NEXT_PUBLIC_BACKEND_URL=https://profilio-staging.sandbox-london-b.fetch-ai.com -NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID=G-GYC9HYF1LD -NEXT_PUBLIC_ALGOLIA_APP_ID=J27DIPDG4S -NEXT_PUBLIC_ALGOLIA_API_KEY=601cad4cf7041d99c1bdf42f4d4843d6 -NEXT_PUBLIC_ALGOLIA_INDEX=12-6-24-index \ No newline at end of file diff --git a/components/account-menu.tsx b/components/account-menu.tsx new file mode 100644 index 000000000..e2ea57061 --- /dev/null +++ b/components/account-menu.tsx @@ -0,0 +1,80 @@ +import React, { useState, FC } from "react"; +import ProfileIcon from "../src/svgs/profile.svg"; +import Image from "next/image"; + +interface AccountMenuProps { + email: string; + logo: string; + signOut: () => void; +} + +const AccountMenu: FC = ({ email, logo, signOut }) => { + const [open, setOpen] = useState(false); + + const handleClick = () => { + setOpen(!open); + }; + return ( +
+ + {open && ( +
+
+ +

+ You are signed in as +

+

+ {email} +

+
+
+ +
+
+ )} +
+ ); +}; + +export default AccountMenu; diff --git a/components/api-endpoint.tsx b/components/api-endpoint.tsx index db4a494a8..bfe6e1a8f 100644 --- a/components/api-endpoint.tsx +++ b/components/api-endpoint.tsx @@ -349,6 +349,33 @@ export const ApiEndpointRequestResponse: React.FC<{ const context = useUserContext(); + const hitRequestWithoutLogin = async () => { + try { + setLoading(true); + setError(""); + const requestPayloadJSON = JSON.parse(requestPayload || "{}"); + const apiUrlWithParams = (properties.apiUrl + + replacePathParameters(properties.path, pathParameters)) as string; + const response = await fetch(apiUrlWithParams, { + method: properties.method, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${bearerToken}`, + }, + body: properties.method.includes("GET") + ? null + : JSON.stringify(requestPayloadJSON), + }); + const data = await response.json(); + const responseText = JSON.stringify(data, null, 2); + setActualResponse(responseText); + } catch (error) { + setError(`Error: ${error.message}`); + } finally { + setLoading(false); + } + }; + const hitRequest = async () => { try { setLoading(true); @@ -530,7 +557,9 @@ export const ApiEndpointRequestResponse: React.FC<{
diff --git a/pages/api/api-requests.ts b/pages/api/api-requests.ts new file mode 100644 index 000000000..2eaef8c65 --- /dev/null +++ b/pages/api/api-requests.ts @@ -0,0 +1,103 @@ +/* eslint-disable no-useless-catch */ +import axios from "axios"; +import { getCookie, setCookie, deleteCookie } from "cookies-next"; +import { NextApiRequest, NextApiResponse } from "next"; + +class CustomError extends Error { + status: number; + + constructor(message: string, status: number) { + super(message); + this.status = status; + } +} + +async function getNewAccessToken(refreshToken: string) { + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_FETCH_ACCOUNTS_URL}/v1/tokens`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + grant_type: "refresh_token", + refresh_token: refreshToken, + }), + }, + ); + const data = await response.json(); + if (response.ok && response.status === 200) { + return data.access_token; + } + if (response.status === 422) { + throw new CustomError("Refresh token expired", 422); + } + } catch (refreshError) { + throw new CustomError(refreshError.message, 422); + } +} + +async function retryRequest( + req: NextApiRequest, + res: NextApiResponse, + accessToken: string, + url: string, +) { + try { + const response = await axios({ + method: req.method, + url: url, + headers: { Authorization: `Bearer ${accessToken}` }, + data: req.body, + }); + return res.status(response.status).json({ data: response.data }); + } catch (error) { + return res + .status(error.response.status) + .json({ error: error.response.data }); + } +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + const { url } = req.query; + const accessToken = getCookie("fauna", { req, res }); + const refreshToken = getCookie("refresh_token", { req, res }); + try { + const response = await axios({ + method: req.method, + url: url as string, + headers: { Authorization: `Bearer ${accessToken}` }, + data: req.body, + }); + deleteCookie("fauna", { req, res }); + return res.status(response.status).json({ data: response.data }); + } catch (error) { + if (error.response.status === 401) { + try { + const newAccessToken = await getNewAccessToken(refreshToken); + if (newAccessToken) { + setCookie("fauna", newAccessToken, { req, res }); + return await retryRequest(req, res, newAccessToken, url as string); + } else { + return res + .status(422) + .json({ message: "unable to got new access token" }); + } + } catch (Error) { + if (Error.status === 422) { + return res.status(Error.status).json({ message: Error }); + } + return res.status(500).json({ message: Error }); + } + } else { + return res + .status(error.response.status) + .json({ error: error.response.data }); + } + } +} diff --git a/theme/fetch-ai-docs/components/navbar.tsx b/theme/fetch-ai-docs/components/navbar.tsx index b883c5caa..c0e64cf5d 100644 --- a/theme/fetch-ai-docs/components/navbar.tsx +++ b/theme/fetch-ai-docs/components/navbar.tsx @@ -9,6 +9,10 @@ import { renderComponent } from "../utils"; import { Anchor } from "./anchor"; import { useState } from "react"; import React from "react"; +import { handleSignin } from "../helpers"; +import AccountMenu from "components/account-menu"; +import { useUserContext } from "../contexts/context-provider"; +import { useRouter } from "next/router"; export type NavBarProps = { flatDirectories: Item[]; @@ -83,6 +87,12 @@ export function Navbar({ flatDirectories, items }: NavBarProps): ReactElement { const activeRoute = useFSRoute(); const [hoveredLink, setHoveredLink] = useState(null); const { menu, setMenu } = useMenu(); + const context = useUserContext(); + const router = useRouter(); + const handleSignOut = () => { + context.signOut(); + router.push("/"); + }; return (
) : null} + {context.isLoggedIn ? ( + + ) : ( + + )} {renderComponent(config.navbar.extraContent)} - {open && ( -
-
- -

- You are signed in as -

-

- {email} -

-
-
- -
-
- )} -
- ); -}; - -export default AccountMenu; diff --git a/components/api-endpoint.tsx b/components/api-endpoint.tsx index bfe6e1a8f..a961260e2 100644 --- a/components/api-endpoint.tsx +++ b/components/api-endpoint.tsx @@ -9,10 +9,8 @@ import { Tab, DropDownTabs, } from "./mdx"; -import { useUserContext } from "theme/fetch-ai-docs/contexts/context-provider"; import Tooltip from "./tooltip"; import Link from "next/link"; -import fetchJson from "src/lib/fetch-json"; interface PropertyType { name: string; @@ -347,8 +345,6 @@ export const ApiEndpointRequestResponse: React.FC<{ setIsModalOpen(false); }; - const context = useUserContext(); - const hitRequestWithoutLogin = async () => { try { setLoading(true); @@ -375,33 +371,6 @@ export const ApiEndpointRequestResponse: React.FC<{ setLoading(false); } }; - - const hitRequest = async () => { - try { - setLoading(true); - setError(""); - const requestPayloadJSON = JSON.parse(requestPayload || "{}"); - const apiUrlWithParams = (properties.apiUrl + - replacePathParameters(properties.path, pathParameters)) as string; - - const response: { data: unknown } = await fetchJson( - `/docs/api/api-requests?url=${apiUrlWithParams}`, - { - method: properties.method, - body: properties.method.includes("GET") ? null : requestPayloadJSON, - }, - ); - const responseText = JSON.stringify(response.data, null, 2); - setActualResponse(responseText); - } catch (error) { - if (error.response.status === 422) { - context.signOut(); - } - setError(`Error: ${error.message}`); - } finally { - setLoading(false); - } - }; return ( <> @@ -447,7 +416,7 @@ export const ApiEndpointRequestResponse: React.FC<{
- {!context?.isLoggedIn && isBearerTokenRequired && ( + {isBearerTokenRequired && (

@@ -557,9 +526,7 @@ export const ApiEndpointRequestResponse: React.FC<{

diff --git a/k8s/build-img.py b/k8s/build-img.py index c66539649..3893ef236 100755 --- a/k8s/build-img.py +++ b/k8s/build-img.py @@ -23,9 +23,6 @@ 'NEXT_PUBLIC_ALGOLIA_API_KEY', 'NEXT_PUBLIC_ALGOLIA_INDEX', 'NEWSLETTER_BASE_URL', - 'NEXT_PUBLIC_FETCH_ACCOUNTS_URL', - 'NEXT_PUBLIC_CLIENT_ID', - 'NEXT_PUBLIC_COOKIE_PASSWORD' ) def _profile(text: str) -> str: diff --git a/pages/api/api-requests.ts b/pages/api/api-requests.ts deleted file mode 100644 index 2eaef8c65..000000000 --- a/pages/api/api-requests.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-disable no-useless-catch */ -import axios from "axios"; -import { getCookie, setCookie, deleteCookie } from "cookies-next"; -import { NextApiRequest, NextApiResponse } from "next"; - -class CustomError extends Error { - status: number; - - constructor(message: string, status: number) { - super(message); - this.status = status; - } -} - -async function getNewAccessToken(refreshToken: string) { - try { - const response = await fetch( - `${process.env.NEXT_PUBLIC_FETCH_ACCOUNTS_URL}/v1/tokens`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - grant_type: "refresh_token", - refresh_token: refreshToken, - }), - }, - ); - const data = await response.json(); - if (response.ok && response.status === 200) { - return data.access_token; - } - if (response.status === 422) { - throw new CustomError("Refresh token expired", 422); - } - } catch (refreshError) { - throw new CustomError(refreshError.message, 422); - } -} - -async function retryRequest( - req: NextApiRequest, - res: NextApiResponse, - accessToken: string, - url: string, -) { - try { - const response = await axios({ - method: req.method, - url: url, - headers: { Authorization: `Bearer ${accessToken}` }, - data: req.body, - }); - return res.status(response.status).json({ data: response.data }); - } catch (error) { - return res - .status(error.response.status) - .json({ error: error.response.data }); - } -} - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - const { url } = req.query; - const accessToken = getCookie("fauna", { req, res }); - const refreshToken = getCookie("refresh_token", { req, res }); - try { - const response = await axios({ - method: req.method, - url: url as string, - headers: { Authorization: `Bearer ${accessToken}` }, - data: req.body, - }); - deleteCookie("fauna", { req, res }); - return res.status(response.status).json({ data: response.data }); - } catch (error) { - if (error.response.status === 401) { - try { - const newAccessToken = await getNewAccessToken(refreshToken); - if (newAccessToken) { - setCookie("fauna", newAccessToken, { req, res }); - return await retryRequest(req, res, newAccessToken, url as string); - } else { - return res - .status(422) - .json({ message: "unable to got new access token" }); - } - } catch (Error) { - if (Error.status === 422) { - return res.status(Error.status).json({ message: Error }); - } - return res.status(500).json({ message: Error }); - } - } else { - return res - .status(error.response.status) - .json({ error: error.response.data }); - } - } -} diff --git a/pages/api/profile.ts b/pages/api/profile.ts deleted file mode 100644 index fb0b60933..000000000 --- a/pages/api/profile.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "src/core/session"; -import { NextApiRequest, NextApiResponse } from "next"; - -type User = { - email: string; - avatarHref: string; - id: string; - walletAddress?: string; -}; - -type Credentials = { - apiKey: string; - expiresAt: number; - group: string; - sub: string; -}; - -export type CombinedData = { - user?: User; - credentials?: Credentials; - isLoggedIn: boolean; -}; - -async function userRoute( - request: NextApiRequest, - response: NextApiResponse, -) { - response.setHeader("Cache-Control", "no-cache, maxage=0, must-revalidate"); - if (request.session.user || request.session.credentials) { - const user: User = { - email: request.session.user?.email ?? "", - walletAddress: request.session.user?.walletAddress ?? "", - avatarHref: request.session.user?.avatarHref ?? "", - id: request.session.user?.id ?? "", - }; - - const credentials: Credentials = { - apiKey: request.session.credentials?.apiKey ?? "", - expiresAt: request.session.credentials?.expiresAt ?? 0, - group: request.session.credentials?.group ?? "", - sub: request.session.credentials?.sub ?? "", - }; - - const combinedData: CombinedData = { - user, - credentials, - isLoggedIn: true, - }; - - response.json(combinedData); - } else { - response.json({ - isLoggedIn: false, - }); - } -} - -export default withIronSessionApiRoute(userRoute, sessionOptions); diff --git a/pages/api/search.ts b/pages/api/search.ts index bca506776..a2fc5bfc4 100644 --- a/pages/api/search.ts +++ b/pages/api/search.ts @@ -18,8 +18,7 @@ export default async function handler( }); const apiResponse = await response.json(); return res.status(response.status).json({ apiResponse }); - } catch (error) { - console.error(error); + } catch { return res.status(500).json({ error: "Internal Server Error" }); } } diff --git a/pages/api/signout.ts b/pages/api/signout.ts deleted file mode 100644 index ecf6848d6..000000000 --- a/pages/api/signout.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "src/core/session"; -import { NextApiRequest, NextApiResponse } from "next"; -import { CombinedData } from "./profile"; - -async function userRoute( - request: NextApiRequest, - response: NextApiResponse, -) { - response.setHeader("Cache-Control", "no-cache, maxage=0, must-revalidate"); - request.session.destroy(); - response.json({ - isLoggedIn: false, - user: { email: "", avatarHref: "", id: "" }, - credentials: { apiKey: "", expiresAt: 0, group: "", sub: "" }, - }); -} - -export default withIronSessionApiRoute(userRoute, sessionOptions); diff --git a/pages/auth.tsx b/pages/auth.tsx deleted file mode 100644 index 5d1ea5ffc..000000000 --- a/pages/auth.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from "react"; -import { withIronSessionSsr } from "iron-session/next"; -import { parseJwt } from "src/core/jwt"; -import { getTokenFromAuthCode } from "src/core/fauna"; -import { setCookie } from "cookies-next"; -import { sessionOptions } from "src/core/session"; - -export function flatten(normalized = ""): string { - if (Array.isArray(normalized)) { - return normalized.join(" "); - } - return normalized; -} - -const Page: React.FC = () => { - return <>This is the auth page; -}; - -export const getServerSideProps = withIronSessionSsr(async function ({ - req, - res, - query, -}) { - const authFailure = async () => { - req.session.user; - req.session.credentials; - await req.session.save(); - }; - - const authCode = flatten(query.code as string); - if (authCode === "") { - await authFailure(); - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - } - - const token = await getTokenFromAuthCode(authCode); - setCookie("fauna", token.access_token, { req, res }); - setCookie("refresh_token", token.refresh_token, { req, res }); - - const r = await fetch( - `${process.env.NEXT_PUBLIC_FETCH_ACCOUNTS_URL}/v1/profile`, - { - method: "GET", - headers: { - Authorization: `bearer ${token.access_token}`, - }, - }, - ); - if (!r.ok) { - await authFailure(); - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - } - - const profile = await r.json(); - const email: string | undefined = profile.email; - const walletAddress: string | undefined = profile.wallet_address; - const givenName: string | undefined = profile.given_name; - const familyName: string | undefined = profile.family_name; - const avatarHref: string | undefined = profile.image_url; - const id: string | undefined = profile.client_id; - - const isEmailUser = email !== undefined; - const isWalletUser = walletAddress !== undefined; - const isValidUser = isEmailUser || isWalletUser; - - if (!isValidUser) { - await authFailure(); - return { - redirect: { - destination: "/", - permanent: false, - }, - }; - } - - req.session.user = { - email: email ?? "", - walletAddress: walletAddress ?? "", - avatarHref, - familyName, - givenName, - id, - }; - const metadata = parseJwt(token.access_token); - - req.session.credentials = { - apiKey: token.access_token, - expiresAt: metadata.expiry, - group: metadata.group, - sub: metadata.sub, - }; - - await req.session.save(); - - return { - redirect: { - destination: "/", - permanent: false, - }, - }; -}, sessionOptions); - -export default Page; diff --git a/src/core/fauna.tsx b/src/core/fauna.tsx deleted file mode 100644 index 83d67720f..000000000 --- a/src/core/fauna.tsx +++ /dev/null @@ -1,66 +0,0 @@ -export async function getTokenFromAuthCode(authCode: string): Promise<{ - access_token: string; - refresh_token: string; -}> { - const r = await fetch( - `${process.env.NEXT_PUBLIC_FETCH_ACCOUNTS_URL}/v1/tokens`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - grant_type: "authorization_code", - code: authCode, - client_id: process.env.NEXT_PUBLIC_CLIENT_ID, - }), - }, - ); - - if (!r.ok) { - throw new Error("Failed to get token"); - } - - const { access_token, refresh_token } = await r.json(); - return { - access_token: access_token, - refresh_token: refresh_token, - }; -} - -export async function getNewAccessToken(currentToken: string) { - if (!currentToken) { - return; - } - const r = await fetch( - `${process.env.NEXT_PUBLIC_FETCH_ACCOUNTS_URL}/v1/tokens`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - grant_type: "refresh_token", - refresh_token: currentToken, - }), - }, - ); - - if (!r.ok) { - console.error("Could not request new token."); - return; - } - - const refresh_token = await r.json(); - - if (!refresh_token) { - return; - } - - return { - accessToken: refresh_token.access_token, - expiresIn: (refresh_token.expires_in as number) * 1000, - group: refresh_token.grp, - id: "", - }; -} diff --git a/src/core/jwt.tsx b/src/core/jwt.tsx deleted file mode 100644 index 38ca992a6..000000000 --- a/src/core/jwt.tsx +++ /dev/null @@ -1,15 +0,0 @@ -interface TokenInformation { - expiry: number; - group?: "internal"; - sub: string; -} - -export function parseJwt(token: string): TokenInformation { - const payload = JSON.parse( - Buffer.from(token.split(".")[1], "base64").toString(), - ); - - const expiry = (payload.exp as number) * 1000; - - return { expiry: expiry, group: payload.grp, sub: payload.sub }; -} diff --git a/src/core/session.tsx b/src/core/session.tsx deleted file mode 100644 index 7d475b511..000000000 --- a/src/core/session.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { IronSessionOptions } from "iron-session"; -import { - Credentials, - User, -} from "theme/fetch-ai-docs/contexts/context-provider"; - -export const sessionOptions: IronSessionOptions = { - password: process.env.NEXT_PUBLIC_COOKIE_PASSWORD, - cookieName: process.env.NEXT_PUBLIC_CLIENT_ID, - cookieOptions: { - secure: process.env.NODE_ENV === "production", - }, -}; - -declare module "iron-session" { - interface IronSessionData { - user: User | null; - credentials: Credentials | null; - } -} diff --git a/theme/fetch-ai-docs/components/navbar.tsx b/theme/fetch-ai-docs/components/navbar.tsx index c0e64cf5d..b883c5caa 100644 --- a/theme/fetch-ai-docs/components/navbar.tsx +++ b/theme/fetch-ai-docs/components/navbar.tsx @@ -9,10 +9,6 @@ import { renderComponent } from "../utils"; import { Anchor } from "./anchor"; import { useState } from "react"; import React from "react"; -import { handleSignin } from "../helpers"; -import AccountMenu from "components/account-menu"; -import { useUserContext } from "../contexts/context-provider"; -import { useRouter } from "next/router"; export type NavBarProps = { flatDirectories: Item[]; @@ -87,12 +83,6 @@ export function Navbar({ flatDirectories, items }: NavBarProps): ReactElement { const activeRoute = useFSRoute(); const [hoveredLink, setHoveredLink] = useState(null); const { menu, setMenu } = useMenu(); - const context = useUserContext(); - const router = useRouter(); - const handleSignOut = () => { - context.signOut(); - router.push("/"); - }; return (
) : null} - {context.isLoggedIn ? ( - - ) : ( - - )} {renderComponent(config.navbar.extraContent)}
{themeContext.footer && @@ -362,11 +352,9 @@ export default function Layout({ }: NextraThemeLayoutProps): ReactElement { return ( - - - {children} - - + + {children} + ); }