From dc0e9724194a73a30e0e4ea8748a56ee350c5c08 Mon Sep 17 00:00:00 2001 From: Helder Oliveira Date: Fri, 6 Dec 2024 13:18:48 +0000 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Auth/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Auth/index.tsx b/src/components/Auth/index.tsx index 43976edc..73b2b32d 100644 --- a/src/components/Auth/index.tsx +++ b/src/components/Auth/index.tsx @@ -5,7 +5,6 @@ import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { constants } from '../../constants'; import { matchesPathname } from '../../utils/matchesPathname'; -import { FleekLogo } from '../FleekLogo/FleekLogo'; import type { FC, ReactNode } from 'react'; From d3a10bd3886ea79126bc2b9e140e97e26d87460d Mon Sep 17 00:00:00 2001 From: Helder Oliveira Date: Fri, 6 Dec 2024 13:19:11 +0000 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20logout=20handler?= =?UTF-8?q?=20should=20be=20asynchronous?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAuthProviders.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useAuthProviders.ts b/src/hooks/useAuthProviders.ts index a23c240e..91bd78c0 100644 --- a/src/hooks/useAuthProviders.ts +++ b/src/hooks/useAuthProviders.ts @@ -8,7 +8,7 @@ export type AuthProviders = 'dynamic'; export type AuthWith = { handleLogin: () => void; - handleLogout: () => void; + handleLogout: () => Promise; requestAccessToken: (projectId?: string) => Promise; token: string | undefined; }; @@ -29,7 +29,7 @@ const useAuthWithDynamic = (): AuthWith => { const handleLogin = () => dynamic.setShowAuthFlow(true); - const handleLogout = () => dynamic.handleLogOut(); + const handleLogout = async () => dynamic.handleLogOut(); const requestAccessToken = async (projectId?: string): Promise => { if (!dynamic.authToken) { @@ -61,7 +61,7 @@ const getMockedProvider: () => AuthWith = () => { return { handleLogin: () => {}, - handleLogout: () => {}, + handleLogout: async () => {}, requestAccessToken: async () => 'mocked-token', token: cookies.values.authProviderToken, }; From dcd21a68c87e0e3783c676fff27f954bee04d872 Mon Sep 17 00:00:00 2001 From: Helder Oliveira Date: Fri, 6 Dec 2024 13:20:10 +0000 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20add=20clear=20us?= =?UTF-8?q?er=20session=20utility=20and=20add=20TODOs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/providers/AuthProvider.tsx | 47 ++++++++++++++++++++++++++++------ src/utils/clearUSerSession.ts | 8 ++++++ tests/e2e/homepage.spec.ts | 28 +++----------------- tests/e2e/projects.spec.ts | 12 +++------ 4 files changed, 54 insertions(+), 41 deletions(-) create mode 100644 src/utils/clearUSerSession.ts diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index fde2568a..69c049de 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -13,6 +13,7 @@ import { useRouter } from '@/hooks/useRouter'; import { createContext } from '@/utils/createContext'; import { useCookies } from './CookiesProvider'; +import { clearUserSession } from '@/utils/clearUSerSession'; export type AuthContext = { loading: boolean; @@ -60,9 +61,12 @@ export const AuthProvider: React.FC> = ({ ); const logout = useCallback(async () => { - cookies.remove('authProviderToken'); const invitationHash = router.query.invitation; + // TODO: Why would an invitation hash have to be + // declared on logout? This might related to whitelisting + // approach on persisting session state + // see "clearUserSession" comment if (!constants.PUBLIC_ROUTES.includes(router.pathname.toLowerCase())) { await router.replace({ pathname: routes.home(), @@ -70,15 +74,37 @@ export const AuthProvider: React.FC> = ({ }); } - providersValues.forEach((provider) => { - if (provider.token) { - provider.handleLogout(); + for (const provider of providersValues) { + if (!provider.token) { + // TODO: On "auth provider" initialisation + // a name should be provided to help troubleshoot. + // Include a name for each provider initialised. + console.warn('Provider doesn\'t have a token or loogout callback'); + continue; } - }); - cookies.remove('projectId'); - clearAccessToken(); + if (provider.token && typeof provider.handleLogout !== 'function') { + // TODO: Name provider on initialisation + // see related TODO above. + console.warn('Provider doesn\'t have a logout callback'); + continue; + } + + // TODO: For some reason the original author + // hasn't awaited for the logout request. + // This has to be revised as its possible + // that there are asynchronous calls involved + // which would have to be awaited for? + await provider.handleLogout(); + } + posthog.reset(); + + // TODO: Session has third-party data + // at time of writing there's no way to know which + // values should persist. Use of a whitelist approach + // can help. Until this is revised, all session data wiped + clearUserSession(); }, [cookies, clearAccessToken, router, providersValues]); const requestAccessToken = useCallback( @@ -127,7 +153,12 @@ export const AuthProvider: React.FC> = ({ if (provider?.token) { // if has a provider token, it means that auth provider is authenticated - cookies.set('authProviderToken', provider.token); + // TODO: If this is the token generated by dynamic + // for the call to get the `accessToken` + // if the token is valid, we seem to not have to need + // dynamic for any purpose? Likewise, this doesn't + // seem necessary. + // cookies.set('authProviderToken', provider.token); const projectId = cookies.values.projectId || constants.DEFAULT_PROJECT_ID; diff --git a/src/utils/clearUSerSession.ts b/src/utils/clearUSerSession.ts new file mode 100644 index 00000000..d20a1114 --- /dev/null +++ b/src/utils/clearUSerSession.ts @@ -0,0 +1,8 @@ +export const clearUserSession = () => { + localStorage.clear(); + sessionStorage.clear(); + document.cookie.split(';').forEach((cookie) => { + document.cookie = + cookie.trim() + '; expires=Thu Jan 01 1970 00:00:00 GMT'; + }); +} diff --git a/tests/e2e/homepage.spec.ts b/tests/e2e/homepage.spec.ts index 76bd6b56..9fa86e64 100644 --- a/tests/e2e/homepage.spec.ts +++ b/tests/e2e/homepage.spec.ts @@ -2,6 +2,7 @@ import { test as it, expect } from '@playwright/test'; import { harFilePaths } from '../utils/har'; import { latestBlogPosts } from '../data/fleekWebsiteJsonApi'; import { getDevServerDetails } from '../utils/devServer'; +import { clearUserSession } from '../../src/utils/clearUSerSession'; const { describe, beforeEach, afterEach } = it; @@ -14,14 +15,7 @@ describe('On Home page', () => { }); afterEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.clear(); - sessionStorage.clear(); - document.cookie.split(';').forEach((cookie) => { - document.cookie = - cookie.trim() + '; expires=Thu Jan 01 1970 00:00:00 GMT'; - }); - }); + await page.evaluate(() => clearUserSession()); }); it('Should get the title page "Home"', async ({ page }) => { @@ -73,14 +67,7 @@ describe('On Home page', () => { }); afterEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.clear(); - sessionStorage.clear(); - document.cookie.split(';').forEach((cookie) => { - document.cookie = - cookie.trim() + '; expires=Thu Jan 01 1970 00:00:00 GMT'; - }); - }); + await page.evaluate(() => clearUserSession()); }); it('Should redirect to the homepage url', async ({ page }) => { @@ -180,14 +167,7 @@ describe('On Home page', () => { }); afterEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.clear(); - sessionStorage.clear(); - document.cookie.split(';').forEach((cookie) => { - document.cookie = - cookie.trim() + '; expires=Thu Jan 01 1970 00:00:00 GMT'; - }); - }); + await page.evaluate(() => clearUserSession()); }); it('Should redirect to projects page', async ({ page }) => { diff --git a/tests/e2e/projects.spec.ts b/tests/e2e/projects.spec.ts index e28443bc..661cd45c 100644 --- a/tests/e2e/projects.spec.ts +++ b/tests/e2e/projects.spec.ts @@ -3,12 +3,13 @@ import { harFilePaths } from '../utils/har'; import { latestBlogPosts } from '../data/fleekWebsiteJsonApi'; import { getDevServerDetails } from '../utils/devServer'; const { describe, beforeEach, afterEach, beforeAll, afterAll } = it; +import { clearUserSession } from '../../src/utils/clearUSerSession'; const { hostname, port } = getDevServerDetails(); describe('On Project settings page', () => { describe('Valid cookie token user', () => { - const projectId = 'cls4v91mt0001l708wu51eozd'; + const projectId = 's4v91mt0001l708wu51eozd'; beforeEach(async ({ page }) => { const validMockToken = @@ -80,14 +81,7 @@ describe('On Project settings page', () => { }); afterEach(async ({ page }) => { - await page.evaluate(() => { - localStorage.clear(); - sessionStorage.clear(); - document.cookie.split(';').forEach((cookie) => { - document.cookie = - cookie.trim() + '; expires=Thu Jan 01 1970 00:00:00 GMT'; - }); - }); + await page.evaluate(() => clearUserSession()); }); it('Should redirect to projects page', async ({ page }) => { From 9e7536c234758eedfae5f8497a9004176e6a9edc Mon Sep 17 00:00:00 2001 From: Helder Oliveira Date: Fri, 6 Dec 2024 19:07:25 +0000 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20splitting=20conc?= =?UTF-8?q?erns=20from=20AuthProvider,=20to=20ProjectProvider,=20rename=20?= =?UTF-8?q?token=20as=20accessToken,=20etc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fragments/App/Navbar/NavbarCombined.tsx | 4 +- src/fragments/IpfsPropagation/Layout.tsx | 10 +- .../Sections/CreateGatewayButton.tsx | 6 +- src/fragments/Migration/Layout.tsx | 8 +- src/fragments/Migration/Steps/Step1.tsx | 4 +- src/fragments/Template/List/Hero/Hero.tsx | 6 +- src/hooks/useAuthCookie.ts | 2 +- src/hooks/useAuthProviders.ts | 8 +- src/pages/index.tsx | 8 +- src/pages/login/[verificationSessionId].tsx | 12 +- src/pages/migration/index.tsx | 4 +- src/pages/templates/[templateId]/index.tsx | 8 +- src/providers/AuthProvider.tsx | 216 +++++++++--------- src/providers/DynamicProvider.tsx | 9 +- src/providers/ProjectProvider.tsx | 61 ++++- src/providers/SessionProvider.tsx | 2 +- 16 files changed, 207 insertions(+), 161 deletions(-) diff --git a/src/fragments/App/Navbar/NavbarCombined.tsx b/src/fragments/App/Navbar/NavbarCombined.tsx index 5b32f26c..96d685cb 100644 --- a/src/fragments/App/Navbar/NavbarCombined.tsx +++ b/src/fragments/App/Navbar/NavbarCombined.tsx @@ -4,9 +4,9 @@ import { NavbarProject } from './NavbarProject'; import { NavbarUnauthenticated } from './NavbarUnauthenticated'; export const NavbarCombined: React.FC = () => { - const session = useSessionContext(true); + const { loading, auth: { accessToken } } = useSessionContext(true); - if (session.loading || session.auth.token) { + if (loading || accessToken) { return ; } diff --git a/src/fragments/IpfsPropagation/Layout.tsx b/src/fragments/IpfsPropagation/Layout.tsx index 9d74e69c..be6c3f86 100644 --- a/src/fragments/IpfsPropagation/Layout.tsx +++ b/src/fragments/IpfsPropagation/Layout.tsx @@ -15,10 +15,10 @@ import { App } from '../App/App'; export const IpfsPropagationLayout: React.FC = ({ children, }) => { - const session = useSessionContext(); + const { loading, auth: { accessToken }} = useSessionContext(); const navItems = useMainNavigationItems(); - if (!session.auth.token) { + if (!accessToken) { return ( <> @@ -42,7 +42,7 @@ export const IpfsPropagationLayout: React.FC = ({ } - isNavigationLoading={session.loading} + isNavigationLoading={loading} navigation={navItems} breadcrumbs={breadcrumbs} > @@ -53,9 +53,9 @@ export const IpfsPropagationLayout: React.FC = ({ }; const Content: React.FC = ({ children }) => { - const session = useSessionContext(); + const { auth: { accessToken } } = useSessionContext(); - if (session.auth.token) { + if (accessToken) { return {children}; } diff --git a/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx b/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx index 0c3a2218..81f4f7c7 100644 --- a/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx +++ b/src/fragments/IpfsPropagation/Sections/CreateGatewayButton.tsx @@ -5,13 +5,13 @@ import { ActionBox } from '@/components'; import { useSessionContext } from '@/providers/SessionProvider'; export const CreateGatewayButton: React.FC = () => { - const session = useSessionContext(); + const { auth: { accessToken, login } } = useSessionContext(); - const hasToken = Boolean(session.auth.token); + const hasToken = Boolean(accessToken); const handleClick = () => { if (!hasToken) { - session.auth.login( + login( 'dynamic', routes.project.settings.privateGateways({ projectId: 'project' }), ); diff --git a/src/fragments/Migration/Layout.tsx b/src/fragments/Migration/Layout.tsx index ab23e93a..d88d433d 100644 --- a/src/fragments/Migration/Layout.tsx +++ b/src/fragments/Migration/Layout.tsx @@ -13,11 +13,11 @@ export type Layout = React.PropsWithChildren<{ }>; export const Layout: React.FC = ({ children }) => { - const session = useSessionContext(); + const { error, auth: { login, accessToken }, loading } = useSessionContext(); const handleLogIn = () => { - if (!session.error && !session.loading && !session.auth.token) { - session.auth.login('dynamic', routes.migration()); + if (!error && !loading && !accessToken) { + login('dynamic', routes.migration()); } }; @@ -26,7 +26,7 @@ export const Layout: React.FC = ({ children }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - if (session.auth.token) { + if (accessToken) { return ( <> diff --git a/src/fragments/Migration/Steps/Step1.tsx b/src/fragments/Migration/Steps/Step1.tsx index 216605a4..20c65818 100644 --- a/src/fragments/Migration/Steps/Step1.tsx +++ b/src/fragments/Migration/Steps/Step1.tsx @@ -6,7 +6,7 @@ import { useMigrationContext } from '../Migration.context'; import { MigrationStyles as S } from '../Migration.styles'; export const Step1: React.FC = () => { - const session = useSessionContext(); + const { auth: { accessToken } } = useSessionContext(); const { isStep1Loading: isLoading, handleBeginMigration, @@ -14,7 +14,7 @@ export const Step1: React.FC = () => { migrationToken, } = useMigrationContext(); - const isDisabled = !session.auth.token || isLoading || !migrationToken; + const isDisabled = !accessToken || isLoading || !migrationToken; return ( diff --git a/src/fragments/Template/List/Hero/Hero.tsx b/src/fragments/Template/List/Hero/Hero.tsx index d5977643..2f341fb5 100644 --- a/src/fragments/Template/List/Hero/Hero.tsx +++ b/src/fragments/Template/List/Hero/Hero.tsx @@ -36,12 +36,12 @@ export const Hero: React.FC = () => { }; const SubmitTemplateButton: React.FC = () => { - const session = useSessionContext(); + const { auth: { accessToken, login } } = useSessionContext(); const router = useRouter(); const handleSubmitTemplate = () => { - if (!session.auth.token) { - return session.auth.login('dynamic', routes.profile.settings.templates()); + if (!accessToken) { + return login('dynamic', routes.profile.settings.templates()); } return router.push(routes.profile.settings.templates()); diff --git a/src/hooks/useAuthCookie.ts b/src/hooks/useAuthCookie.ts index 07bd412e..32867b4c 100644 --- a/src/hooks/useAuthCookie.ts +++ b/src/hooks/useAuthCookie.ts @@ -16,7 +16,7 @@ export const useAuthCookie = (): [ const set = (jwt: string) => { try { - const parsed = decodeAccessToken({ token: jwt }); + const parsed = decodeAccessToken({ accessToken: jwt }); cookies.set(key, jwt, { expires: DateTime.fromSeconds(parsed.exp).toJSDate(), diff --git a/src/hooks/useAuthProviders.ts b/src/hooks/useAuthProviders.ts index 91bd78c0..89469bbc 100644 --- a/src/hooks/useAuthProviders.ts +++ b/src/hooks/useAuthProviders.ts @@ -10,7 +10,7 @@ export type AuthWith = { handleLogin: () => void; handleLogout: () => Promise; requestAccessToken: (projectId?: string) => Promise; - token: string | undefined; + accessToken?: string; }; export const useAuthProviders = (): Record => { @@ -32,6 +32,7 @@ const useAuthWithDynamic = (): AuthWith => { const handleLogout = async () => dynamic.handleLogOut(); const requestAccessToken = async (projectId?: string): Promise => { + console.log('[debug] useAuthProvidders: requestAccessToken: 1') if (!dynamic.authToken) { return ''; } @@ -39,6 +40,7 @@ const useAuthWithDynamic = (): AuthWith => { const { data, error } = await loginWithDynamic({ data: { authToken: dynamic.authToken, projectId }, }); + console.log(`[debug] useAuthProvidders: requestAccessToken: loginWithDynamic: response: ${JSON.stringify(data)}`) if (data && data.loginWithDynamic) { return data.loginWithDynamic; @@ -51,7 +53,7 @@ const useAuthWithDynamic = (): AuthWith => { handleLogin, handleLogout, requestAccessToken, - token: dynamic.authToken, + accessToken: dynamic.authToken, }; }; @@ -63,6 +65,6 @@ const getMockedProvider: () => AuthWith = () => { handleLogin: () => {}, handleLogout: async () => {}, requestAccessToken: async () => 'mocked-token', - token: cookies.values.authProviderToken, + accessToken: cookies.values.authProviderToken, }; }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 262bf578..d19cbafd 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -5,18 +5,18 @@ import { useSessionContext } from '@/providers/SessionProvider'; import { Page } from '@/types/App'; const HomePage: Page = () => { - const session = useSessionContext(); + const { error, loading, auth: { accessToken, login } } = useSessionContext(); const handleLogIn = () => { - if (!session.error && !session.loading && !session.auth.token) { - session.auth.login('dynamic'); + if (!error && !loading && !accessToken) { + login('dynamic'); } }; useEffect(() => { handleLogIn(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [session.loading]); + }, [loading]); return ( <> diff --git a/src/pages/login/[verificationSessionId].tsx b/src/pages/login/[verificationSessionId].tsx index 0564b4ad..886f469f 100644 --- a/src/pages/login/[verificationSessionId].tsx +++ b/src/pages/login/[verificationSessionId].tsx @@ -13,7 +13,7 @@ import { Log } from '@/utils/log'; const LoginWithSessionPage: Page = () => { const router = useRouter(); - const session = useSessionContext(); + const { auth: { accessToken, login }, project, loading } = useSessionContext(); const [ createLoginVerificationSessionMutation, @@ -26,9 +26,9 @@ const LoginWithSessionPage: Page = () => { const handleRedirect = useCallback(() => { return router.replace( - routes.project.home({ projectId: session.project.id }), + routes.project.home({ projectId: project.id }), ); - }, [router, session.project.id]); + }, [router, project.id]); const handleCreateVerificationSession = useCallback(async () => { try { @@ -76,7 +76,7 @@ const LoginWithSessionPage: Page = () => { } if ( - session.auth.token && + accessToken && !createLoginVerificationSessionMutation?.data ?.createLoginVerificationSession ) { @@ -106,8 +106,8 @@ const LoginWithSessionPage: Page = () => { withExternalLink >