Skip to content

Commit

Permalink
fix: pass getApiToken to createApolloClient function
Browse files Browse the repository at this point in the history
  • Loading branch information
jorilindell committed May 16, 2024
1 parent 6d89bdc commit e5d2889
Show file tree
Hide file tree
Showing 11 changed files with 60 additions and 79 deletions.
39 changes: 7 additions & 32 deletions src/domain/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@

import { ApolloProvider } from '@apollo/client';
import { createInstance, MatomoProvider } from '@datapunt/matomo-tracker-react';
import { LoginProvider, useApiTokensClient, useOidcClient } from 'hds-react';
import React, { PropsWithChildren, useEffect, useMemo, useRef } from 'react';
import { LoginProvider } from 'hds-react';
import React, { PropsWithChildren, useMemo } from 'react';
import { BrowserRouter } from 'react-router-dom';

import theme from '../../assets/theme/theme';
import { AccessibilityNotificationProvider } from '../../common/components/accessibilityNotificationContext/AccessibilityNotificationContext';
import getValue from '../../utils/getValue';
import { loginProviderProps } from '../auth/constants';
import useAuth from '../auth/hooks/useAuth';
import { createApolloClient } from './apollo/apolloClient';
import CookieConsent from './cookieConsent/CookieConsent';
import { useNotificationsContext } from './notificationsContext/hooks/useNotificationsContext';
Expand All @@ -34,38 +35,13 @@ const instance = createInstance({
siteId: Number(import.meta.env.REACT_APP_MATOMO_SITE_ID),
});

const RefreshApiTokenOnLoad: React.FC = () => {
const { getUser } = useOidcClient();
const { fetch: fetchApiTokens } = useApiTokensClient();
const isFetchingStarted = useRef(false);

useEffect(() => {
// Refresh api tokens when page is loaded if the user is already authenticated.
// Reason for this is to make sure that expired api token is not used.
// That might happen if page is refreshed after refreshing access token. In that case
// api token is not refreshed and might be expired already.
// The information about api token expiration time is not stored anywhere.
const fetchNewApiToken = async () => {
const user = getUser();
if (user && !isFetchingStarted.current) {
isFetchingStarted.current = true;
fetchApiTokens(user);
}
};

fetchNewApiToken();
});

return null;
};

const ApolloWrapper: React.FC<PropsWithChildren> = ({ children }) => {
const { addNotification } = useNotificationsContext();
const { getApiToken } = useAuth();

const apolloClient = useMemo(
() => createApolloClient({ addNotification }),
[addNotification]
);
const apolloClient = useMemo(() => {
return createApolloClient({ addNotification, getApiToken });
}, [addNotification, getApiToken]);

return <ApolloProvider client={apolloClient}>{children}</ApolloProvider>;
};
Expand All @@ -76,7 +52,6 @@ const App: React.FC = () => {
<AccessibilityNotificationProvider>
<NotificationsProvider>
<LoginProvider {...loginProviderProps}>
<RefreshApiTokenOnLoad />
<PageSettingsProvider>
<BrowserRouter>
{/* @ts-ignore */}
Expand Down
31 changes: 17 additions & 14 deletions src/domain/app/apollo/apolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import { normalizeKey } from '../../../utils/apolloUtils';
import { featureFlagUtils } from '../../../utils/featureFlags';
import generateAtId from '../../../utils/generateAtId';
import getValue from '../../../utils/getValue';
import { getApiTokenFromStorage } from '../../auth/utils';
import i18n from '../i18n/i18nInit';
import {
addTypenameDataSource,
Expand Down Expand Up @@ -227,17 +226,17 @@ export const createCache = (): InMemoryCache =>
},
});

const authLink = setContext(async (_, { headers }) => {
const token = getApiTokenFromStorage();

return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
'Accept-language': i18n.language,
},
};
});
const createAuthLink = (getApiToken: () => string | null) =>
setContext(async (_, { headers }) => {
const apiToken = getApiToken();
return {
headers: {
...headers,
authorization: apiToken ? `Bearer ${apiToken}` : null,
'Accept-language': i18n.language,
},
};
});

const addNocacheToUrl = (urlStr: string): string => {
const url = new URL(urlStr);
Expand Down Expand Up @@ -538,17 +537,21 @@ const sentryLink = new SentryLink({
),
});

const cache = createCache();

export const createApolloClient = ({
addNotification,
getApiToken,
}: {
addNotification: (props: NotificationProps) => void;
getApiToken: () => string | null;
}) =>
new ApolloClient({
cache: createCache(),
cache,
link: ApolloLink.from([
createErrorLink({ addNotification }),
sentryLink,
authLink,
createAuthLink(getApiToken),
linkedEventsLink,
]),
});
17 changes: 16 additions & 1 deletion src/domain/auth/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useOidcClient } from 'hds-react';
import { useApiTokens, useOidcClient } from 'hds-react';
import { User } from 'oidc-client-ts';
import { useCallback } from 'react';
import { useLocation } from 'react-router';

import useLocale from '../../../hooks/useLocale';
import getValue from '../../../utils/getValue';
import { OidcLoginState } from '../types';

type UseAuthState = {
authenticated: boolean;
getApiToken: () => string | null;
login: (signInPath?: string) => Promise<void>;
logout: (signInPath?: string) => Promise<void>;
user: User | null;
Expand All @@ -15,6 +18,17 @@ type UseAuthState = {
const useAuth = (): UseAuthState => {
const locale = useLocale();
const { isAuthenticated, getUser, login, logout } = useOidcClient();
const { getStoredApiTokens } = useApiTokens();

const getApiToken = useCallback(
() =>
getValue(
getStoredApiTokens()[1]?.[import.meta.env.REACT_APP_OIDC_API_SCOPE],
null
),
[getStoredApiTokens]
);

const location = useLocation();

const handleLogin = async (signInPath?: string) => {
Expand All @@ -30,6 +44,7 @@ const useAuth = (): UseAuthState => {

return {
authenticated: isAuthenticated(),
getApiToken,
login: handleLogin,
logout: handleLogout,
user: getUser(),
Expand Down
20 changes: 0 additions & 20 deletions src/domain/auth/utils.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
import { UserManager, UserManagerSettings } from 'oidc-client-ts';

const apiAccessTokenStorage = sessionStorage;

const storageKey = 'hds_login_api_token_storage_key';

export const getApiTokenFromStorage = (): string | null => {
const apiTokensStr = apiAccessTokenStorage.getItem(storageKey);

if (apiTokensStr) {
return JSON.parse(apiTokensStr)[import.meta.env.REACT_APP_OIDC_API_SCOPE];
}

return null;
};

export const setApiTokenToStorage = (apiToken: string): void =>
apiAccessTokenStorage.setItem(
storageKey,
JSON.stringify({ [import.meta.env.REACT_APP_OIDC_API_SCOPE]: apiToken })
);

/* istanbul ignore next */
export const createUserManager = (config: UserManagerSettings) => {
return new UserManager(config);
Expand Down
5 changes: 4 additions & 1 deletion src/domain/organization/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import {
organizationsPathBuilder,
} from '../utils';

const apolloClient = createApolloClient({ addNotification: vi.fn() });
const apolloClient = createApolloClient({
addNotification: vi.fn(),
getApiToken: () => null,
});

const account = {
active: true,
Expand Down
10 changes: 7 additions & 3 deletions src/domain/registration/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
getMockedSeatsReservationData,
setSessionStorageValues,
} from '../../../utils/mockDataUtils';
import { setApiTokenToStorage } from '../../auth/utils';
import { TEST_SIGNUP_ID } from '../../signup/constants';
import {
REGISTRATION_INITIAL_VALUES,
Expand Down Expand Up @@ -1059,7 +1058,6 @@ describe('exportSignupsAsExcel function', () => {
const registration = fakeRegistration({ id: TEST_REGISTRATION_ID });

it('should download signups as excel', async () => {
setApiTokenToStorage('api-token');
global.fetch = vi.fn(() =>
Promise.resolve({ blob: () => Promise.resolve({}), status: 200 })
) as any;
Expand All @@ -1071,6 +1069,7 @@ describe('exportSignupsAsExcel function', () => {

exportSignupsAsExcel({
addNotification: vi.fn(),
apiToken: 'api-token',
registration,
uiLanguage: 'fi',
});
Expand Down Expand Up @@ -1099,7 +1098,12 @@ describe('exportSignupsAsExcel function', () => {
status: status,
})
) as any;
exportSignupsAsExcel({ addNotification, registration, uiLanguage: 'fi' });
exportSignupsAsExcel({
addNotification,
apiToken: 'api-token',
registration,
uiLanguage: 'fi',
});

await waitFor(() =>
expect(addNotification).toBeCalledWith({ label: error, type: 'error' })
Expand Down
3 changes: 2 additions & 1 deletion src/domain/registration/editButtonPanel/EditButtonPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const EditButtonPanel: React.FC<EditButtonPanelProps> = ({
saving,
}) => {
const { t } = useTranslation();
const { authenticated } = useAuth();
const { authenticated, getApiToken } = useAuth();
const locale = useLocale();
const navigate = useNavigate();
const { id } = getRegistrationFields(registration, locale);
Expand Down Expand Up @@ -135,6 +135,7 @@ const EditButtonPanel: React.FC<EditButtonPanelProps> = ({
onClick: () =>
exportSignupsAsExcel({
addNotification,
apiToken: getApiToken(),
registration,
uiLanguage: locale,
}),
Expand Down
6 changes: 3 additions & 3 deletions src/domain/registration/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import queryBuilder from '../../utils/queryBuilder';
import skipFalsyType from '../../utils/skipFalsyType';
import stripTrailingSlash from '../../utils/stripTrailingSlash';
import i18n from '../app/i18n/i18nInit';
import { getApiTokenFromStorage } from '../auth/utils';
import { getEventFields } from '../event/utils';
import {
getSeatsReservationData,
Expand Down Expand Up @@ -571,20 +570,21 @@ const downloadBlob = (blob: Blob, filename: string) => {

export const exportSignupsAsExcel = ({
addNotification,
apiToken,
registration,
uiLanguage,
}: {
addNotification: (props: NotificationProps) => void;
apiToken: string | null;
registration: RegistrationFieldsFragment;
uiLanguage: Language;
}) => {
const url = getExportSignupsExcelUrl({ registration, uiLanguage });
const token = getApiTokenFromStorage();
const { t } = i18n;

fetch(url, {
headers: {
...(token ? { authorization: `Bearer ${token}` } : undefined),
...(apiToken ? { authorization: `Bearer ${apiToken}` } : undefined),
'Accept-language': i18n.language,
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const RegistrationActionsDropdown: React.FC<
> = ({ className, registration }) => {
const { t } = useTranslation();
const { resetPageParamAndGoToPage } = useResetPageParamAndGoToPage();
const { authenticated } = useAuth();
const { authenticated, getApiToken } = useAuth();
const locale = useLocale();
const navigate = useNavigate();
const { id, registrationUrl } = getRegistrationFields(registration, locale);
Expand Down Expand Up @@ -118,6 +118,7 @@ const RegistrationActionsDropdown: React.FC<
onClick: () =>
exportSignupsAsExcel({
addNotification,
apiToken: getApiToken(),
registration,
uiLanguage: locale,
}),
Expand Down
3 changes: 2 additions & 1 deletion src/domain/signups/SignupsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const SignupsPage: React.FC<SignupsPageProps> = ({ registration }) => {
const navigate = useNavigate();
const locale = useLocale();

const { authenticated } = useAuth();
const { authenticated, getApiToken } = useAuth();
const publisher = getValue(registration.publisher, '');
const { addNotification } = useNotificationsContext();

Expand Down Expand Up @@ -126,6 +126,7 @@ const SignupsPage: React.FC<SignupsPageProps> = ({ registration }) => {
onClick: () =>
exportSignupsAsExcel({
addNotification,
apiToken: getApiToken(),
registration,
uiLanguage: locale,
}),
Expand Down
2 changes: 0 additions & 2 deletions src/domain/signups/__tests__/testUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { RegistrationFieldsFragment } from '../../../generated/graphql';
import { userEvent, waitFor } from '../../../utils/testUtils';
import { setApiTokenToStorage } from '../../auth/utils';

export const shouldExportSignupsAsExcel = async ({
exportAsExcelButton,
Expand All @@ -12,7 +11,6 @@ export const shouldExportSignupsAsExcel = async ({
}) => {
const user = userEvent.setup();

setApiTokenToStorage('api-token');
global.fetch = vi.fn(() =>
Promise.resolve({ blob: () => Promise.resolve({}), status: 200 })
) as any;
Expand Down

0 comments on commit e5d2889

Please sign in to comment.