Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/long-lions-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleek-platform/login-button": minor
---

Extend session dismissal check with cookie mismatching locals storage session details
59 changes: 31 additions & 28 deletions src/providers/DynamicProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { type TriggerLoginModal, type TriggerLogout, useAuthStore } from '../sto
import { cookies } from '../utils/cookies';
import type { LoginProviderChildrenProps } from './LoginProvider';
import { clearStorageByMatchTerm } from '../utils/browser';
import { decodeAccessToken } from '../utils/token';
import { decodeAccessToken, truncateMiddle } from '../utils/token';
import cssOverrides from '../css/index.css';

type DynamicUtilsProps = {
Expand Down Expand Up @@ -47,21 +47,29 @@ const validateUserSession = async ({
graphqlApiUrl,
projectId,
onAuthenticationFailure,
onAuthenticationSuccess,
}: {
accessToken: string;
graphqlApiUrl: string;
projectId: string;
onAuthenticationFailure: () => void;
onAuthenticationSuccess: () => void;
}): Promise<boolean> => {
try {
const { success: meSuccess } = await me(graphqlApiUrl, accessToken);

const { success: projectSuccess } = await project(graphqlApiUrl, accessToken, projectId);

if (!meSuccess || !projectSuccess) {
onAuthenticationFailure();
return false;
}
if (!meSuccess || !projectSuccess) throw Error('Unexpected user session details');

const cookieAccessToken = cookies.get('accessToken');

if (cookieAccessToken !== accessToken)
throw Error(
`Expected ${truncateMiddle(accessToken)} but got ${typeof cookieAccessToken === 'string' ? truncateMiddle(cookieAccessToken) : typeof cookieAccessToken}`,
);

typeof onAuthenticationSuccess === 'function' && onAuthenticationSuccess();

return true;
} catch (error) {
Expand Down Expand Up @@ -90,12 +98,13 @@ export const DynamicProvider: FC<DynamicProviderProps> = ({ children, graphqlApi
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<unknown>();

// TODO: Remove useCallback to inspect re-triggers
const onLogout = useCallback(() => {
cookies.reset();
// TODO: Make sure the reset is not clearing
// the trigger callbacks
resetStore();
// TODO: Dashboard has a concurrent process
// that should also match these requirements
// Clear critical stores
for (const item of ['dynamic', 'wagmi', 'fleek-xyz']) {
clearStorageByMatchTerm(item);
Expand Down Expand Up @@ -141,24 +150,6 @@ export const DynamicProvider: FC<DynamicProviderProps> = ({ children, graphqlApi
[graphqlApiUrl, setAuthToken, setAccessToken, setIsLoggedIn, setUserProfile, setIsNewUser, onAuthenticationSuccess],
);

useEffect(() => {
if (!accessToken) return;

cookies.set('accessToken', accessToken);
}, [accessToken]);

useEffect(() => {
if (!authToken) return;

cookies.set('authToken', authToken);
}, [authToken]);

useEffect(() => {
if (!projectId) return;

cookies.set('projectId', projectId);
}, [projectId]);

useEffect(() => {
const authToken = cookies.get('authToken');
const accessToken = cookies.get('accessToken');
Expand All @@ -184,15 +175,27 @@ export const DynamicProvider: FC<DynamicProviderProps> = ({ children, graphqlApi
useEffect(() => {
if (!accessToken || !graphqlApiUrl) return;

// Validates the user session sometime in the future
// if found faulty, it should clear the user session
// Validates the user session sometime in the future.
// If found faulty, it should clear the user session
// e.g. user session clear/logout by dashboard.
// On the other hand, an existing user session can
// persist (localStorage), but dashboard uses cookies
// e.g. user logins in website and expect cross session.
// This is more of a safe-guard due to Dashboard
// having the requirement to clear localStorage items
// that match prefix `fleek-xyz-login`, meaning
// we're only computing if that fails to happen
validateUserSession({
accessToken,
graphqlApiUrl,
projectId,
onAuthenticationFailure: () => typeof triggerLogout === 'function' && triggerLogout(),
onAuthenticationFailure: () => onLogout(),
onAuthenticationSuccess: () => {
cookies.set('accessToken', accessToken);
cookies.set('projectId', projectId);
},
});
}, [accessToken, graphqlApiUrl, projectId, triggerLogout]);
}, [onLogout]);

const settings = {
environmentId: dynamicEnvironmentId,
Expand Down
20 changes: 18 additions & 2 deletions src/store/authStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useConfigStore } from './configStore';
import { getStoreName } from '../utils/store';
import { decodeAccessToken } from '../utils/token';
import type { UserProfile } from '@dynamic-labs/sdk-react-core';
import { cookies } from '../utils/cookies';

export type TriggerLoginModal = (open: boolean) => void;
export type TriggerLogout = () => void;
Expand Down Expand Up @@ -69,8 +70,15 @@ export const useAuthStore = create<AuthStore>()(
accessToken,
projectId,
});

cookies.set('accessToken', accessToken);
cookies.set('projectId', projectId);
},
setAuthToken: (authToken: string) => {
set({ authToken });

cookies.set('authToken', authToken);
},
setAuthToken: (authToken: string) => set({ authToken }),
setIsLoggingIn: (isLoggingIn: boolean) => set({ isLoggingIn }),
setIsLoggedIn: (isLoggedIn: boolean) => set({ isLoggedIn }),
reset: () => set(initialState),
Expand Down Expand Up @@ -100,11 +108,19 @@ export const useAuthStore = create<AuthStore>()(
throw new Error('Failed to get access token');
}

// TODO: Make a projectId validation against
// the accessToken projectId as it should match.
// On failure, throw error.

const accessToken = res.data;

set({
accessToken: res.data,
accessToken,
projectId,
isLoggingIn: false,
});

cookies.set('accessToken', accessToken);
} catch (err) {
console.error('Failed to update access token:', err);

Expand Down
7 changes: 7 additions & 0 deletions src/utils/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ export const decodeAccessToken = (accessToken: string) => {

return projectId;
};

export const truncateMiddle = (str: string, numOfChars = 3, ellipsis = '...'): string => {
const start = str.substring(0, numOfChars);
const end = str.slice(-numOfChars);

return `${start}${ellipsis}${end}`;
};
Loading