diff --git a/front/package-lock.json b/front/package-lock.json
index 9c0f857e9..ad5d1f6de 100644
--- a/front/package-lock.json
+++ b/front/package-lock.json
@@ -19,6 +19,7 @@
"@types/node": "^16.18.4",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
+ "apollo-link-rest": "^0.9.0",
"graphql": "^16.6.0",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.26",
@@ -8666,6 +8667,16 @@
"node": ">= 8"
}
},
+ "node_modules/apollo-link-rest": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/apollo-link-rest/-/apollo-link-rest-0.9.0.tgz",
+ "integrity": "sha512-kuXjR56Y12w0TZcqwVaONKlipB6g3Ya1dAy4NMCaylPpNXq6tO+qzQFPUyDJC7B0JoJPIFjxPV2rAet4uGM4UQ==",
+ "peerDependencies": {
+ "@apollo/client": ">=3",
+ "graphql": ">=0.11",
+ "qs": ">=6"
+ }
+ },
"node_modules/app-root-dir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz",
@@ -9966,7 +9977,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
- "dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -13973,7 +13983,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
- "dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -14406,7 +14415,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
- "dev": true,
"engines": {
"node": ">= 0.4"
},
@@ -19997,7 +20005,6 @@
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
- "dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -22226,7 +22233,6 @@
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
- "dev": true,
"dependencies": {
"side-channel": "^1.0.4"
},
@@ -24020,7 +24026,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
- "dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
diff --git a/front/package.json b/front/package.json
index 5e4943a07..f239d1e53 100644
--- a/front/package.json
+++ b/front/package.json
@@ -14,6 +14,7 @@
"@types/node": "^16.18.4",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
+ "apollo-link-rest": "^0.9.0",
"graphql": "^16.6.0",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.26",
diff --git a/front/src/apollo.tsx b/front/src/apollo.tsx
new file mode 100644
index 000000000..3c3c73684
--- /dev/null
+++ b/front/src/apollo.tsx
@@ -0,0 +1,32 @@
+import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
+import { setContext } from '@apollo/client/link/context';
+import { RestLink } from 'apollo-link-rest';
+
+const apiLink = createHttpLink({
+ uri: `${process.env.REACT_APP_API_URL}/v1/graphql`,
+});
+
+const withAuthHeadersLink = setContext((_, { headers }) => {
+ const token = localStorage.getItem('accessToken');
+ return {
+ headers: {
+ ...headers,
+ authorization: token ? `Bearer ${token}` : '',
+ },
+ };
+});
+
+export const apiClient = new ApolloClient({
+ link: withAuthHeadersLink.concat(apiLink),
+ cache: new InMemoryCache(),
+});
+
+const authLink = new RestLink({
+ uri: `${process.env.REACT_APP_AUTH_URL}`,
+ credentials: 'same-origin',
+});
+
+export const authClient = new ApolloClient({
+ link: authLink,
+ cache: new InMemoryCache(),
+});
diff --git a/front/src/hooks/__tests__/useOutsideAlerter.test.tsx b/front/src/hooks/__tests__/useOutsideAlerter.test.tsx
index 9f970b56a..830fb4f25 100644
--- a/front/src/hooks/__tests__/useOutsideAlerter.test.tsx
+++ b/front/src/hooks/__tests__/useOutsideAlerter.test.tsx
@@ -1,5 +1,4 @@
import { useRef } from 'react';
-import TableHeader from '../../components/table/table-header/TableHeader';
import { render, fireEvent } from '@testing-library/react';
import { useOutsideAlerter } from '../useOutsideAlerter';
import { act } from 'react-dom/test-utils';
@@ -17,9 +16,7 @@ function TestComponent() {
);
}
-export default TableHeader;
-
-test('clicking the button toggles an answer on/off', async () => {
+test('useOutsideAlerter hook works properly', async () => {
const { getByText } = render();
const inside = getByText('Inside');
const outside = getByText('Outside');
diff --git a/front/src/hooks/auth/__tests__/useRefreshToken.test.tsx b/front/src/hooks/auth/__tests__/useRefreshToken.test.tsx
new file mode 100644
index 000000000..b89081773
--- /dev/null
+++ b/front/src/hooks/auth/__tests__/useRefreshToken.test.tsx
@@ -0,0 +1,56 @@
+import { render, waitFor } from '@testing-library/react';
+import { useRefreshToken } from '../useRefreshToken';
+
+const localStorageMock = (function () {
+ let store: { [key: string]: string } = {};
+ return {
+ getItem: function (key: string) {
+ return store[key];
+ },
+ setItem: function (key: string, value: string) {
+ store[key] = value.toString();
+ },
+ clear: function () {
+ store = {};
+ },
+ removeItem: function (key: string) {
+ delete store[key];
+ },
+ };
+})();
+Object.defineProperty(window, 'localStorage', { value: localStorageMock });
+
+function TestComponent() {
+ const { loading } = useRefreshToken();
+
+ return
;
+}
+
+jest.mock('@apollo/client', () => {
+ return {
+ __esModule: true,
+ ...jest.requireActual('@apollo/client'),
+ useQuery: () => ({
+ data: {
+ token: {
+ accessToken: 'test-access-token',
+ },
+ },
+ isLoading: false,
+ error: {},
+ }),
+ };
+});
+
+test('useRefreshToken works properly', async () => {
+ localStorage.setItem('refreshToken', 'test-refresh-token');
+ render();
+
+ await waitFor(() => {
+ expect(localStorageMock.getItem('accessToken')).toBe('test-access-token');
+ });
+});
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
diff --git a/front/src/hooks/auth/useHasAccessToken.tsx b/front/src/hooks/auth/useHasAccessToken.tsx
new file mode 100644
index 000000000..91ee21b71
--- /dev/null
+++ b/front/src/hooks/auth/useHasAccessToken.tsx
@@ -0,0 +1,3 @@
+export const useHasAccessToken = () => {
+ return false;
+};
diff --git a/front/src/hooks/auth/useRedirectToSignIn.tsx b/front/src/hooks/auth/useRedirectToSignIn.tsx
new file mode 100644
index 000000000..5aa69d04a
--- /dev/null
+++ b/front/src/hooks/auth/useRedirectToSignIn.tsx
@@ -0,0 +1,3 @@
+export const redirectToSignIn = () => {
+ return false;
+};
diff --git a/front/src/hooks/auth/useRefreshToken.tsx b/front/src/hooks/auth/useRefreshToken.tsx
new file mode 100644
index 000000000..c4a0b3d60
--- /dev/null
+++ b/front/src/hooks/auth/useRefreshToken.tsx
@@ -0,0 +1,32 @@
+import { gql, useQuery } from '@apollo/client';
+import { useEffect } from 'react';
+import { authClient } from '../../apollo';
+
+export const GET_TOKEN = gql`
+ fragment Payload on REST {
+ refreshToken: String
+ }
+ query jwt($input: Payload) {
+ token(input: $input) @rest(type: "string", path: "/token", method: "POST") {
+ accessToken
+ }
+ }
+`;
+
+export const useRefreshToken = () => {
+ const refreshToken = localStorage.getItem('refreshToken');
+ const { data, loading } = useQuery(GET_TOKEN, {
+ client: authClient,
+ variables: { input: { refreshToken } },
+ });
+ useEffect(() => {
+ if (!loading) {
+ const accessToken = data.token.accessToken;
+ if (refreshToken && accessToken) {
+ localStorage.setItem('accessToken', accessToken || '');
+ }
+ }
+ }, [data, refreshToken, loading]);
+
+ return { loading };
+};
diff --git a/front/src/index.tsx b/front/src/index.tsx
index 2a3c1c11a..ec84e70d6 100644
--- a/front/src/index.tsx
+++ b/front/src/index.tsx
@@ -3,36 +3,16 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
-import {
- ApolloClient,
- InMemoryCache,
- ApolloProvider,
- createHttpLink,
-} from '@apollo/client';
-import { setContext } from '@apollo/client/link/context';
+import { ApolloProvider } from '@apollo/client';
import '@emotion/react';
import { ThemeType } from './layout/styles/themes';
-
-const httpLink = createHttpLink({
- uri: `${process.env.REACT_APP_API_URL}/v1/graphql`,
-});
-
-const authLink = setContext((_, { headers }) => {
- return {
- headers: { ...headers, 'x-hasura-admin-secret': 'secret' },
- };
-});
-
-const client = new ApolloClient({
- link: authLink.concat(httpLink),
- cache: new InMemoryCache(),
-});
+import { apiClient } from './apollo';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
root.render(
-
+
diff --git a/front/src/pages/auth/Callback.tsx b/front/src/pages/auth/Callback.tsx
index f30c8e4e8..16d63ac0f 100644
--- a/front/src/pages/auth/Callback.tsx
+++ b/front/src/pages/auth/Callback.tsx
@@ -1,15 +1,18 @@
-import { useEffect } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
+import { useRefreshToken } from '../../hooks/auth/useRefreshToken';
+import { useEffect } from 'react';
function Callback() {
const [searchParams] = useSearchParams();
const refreshToken = searchParams.get('refreshToken');
localStorage.setItem('refreshToken', refreshToken || '');
-
+ const { loading } = useRefreshToken();
const navigate = useNavigate();
useEffect(() => {
- navigate('/');
- }, [navigate]);
+ if (!loading) {
+ navigate('/');
+ }
+ }, [navigate, loading]);
return <>>;
}
diff --git a/front/src/pages/auth/Login.tsx b/front/src/pages/auth/Login.tsx
index b57c12efb..fb98fe083 100644
--- a/front/src/pages/auth/Login.tsx
+++ b/front/src/pages/auth/Login.tsx
@@ -6,7 +6,8 @@ function Login() {
const navigate = useNavigate();
useEffect(() => {
if (!refreshToken) {
- window.location.href = process.env.REACT_APP_LOGIN_PROVIDER_URL || '';
+ window.location.href =
+ process.env.REACT_APP_AUTH_URL + '/signin/provider/google' || '';
}
navigate('/');
}, [refreshToken, navigate]);
diff --git a/front/src/pages/auth/__tests__/Callback.test.tsx b/front/src/pages/auth/__tests__/Callback.test.tsx
index 3b22994e1..ab5f92494 100644
--- a/front/src/pages/auth/__tests__/Callback.test.tsx
+++ b/front/src/pages/auth/__tests__/Callback.test.tsx
@@ -2,6 +2,14 @@ import { render } from '@testing-library/react';
import { CallbackDefault } from '../__stories__/Callback.stories';
+jest.mock('../../../hooks/auth/useRefreshToken', () => ({
+ useRefreshToken: () => ({ loading: false }),
+}));
+
it('Checks the Callback page render', () => {
render();
});
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
diff --git a/front/src/pages/people/People.tsx b/front/src/pages/people/People.tsx
index 04e36c52b..9de38894a 100644
--- a/front/src/pages/people/People.tsx
+++ b/front/src/pages/people/People.tsx
@@ -14,8 +14,8 @@ const StyledPeopleContainer = styled.div`
`;
export const GET_PEOPLE = gql`
- query GetPeople($orderBy: [person_order_by!]) {
- person(order_by: $orderBy) {
+ query GetPeople($orderBy: [persons_order_by!]) {
+ persons(order_by: $orderBy) {
id
phone
email
@@ -57,7 +57,7 @@ function People() {
setOrderBy(sorts.length ? reduceSortsToOrderBy(sorts) : defaultOrderBy);
};
- const { data } = useQuery<{ person: GraphqlPerson[] }>(GET_PEOPLE, {
+ const { data } = useQuery<{ persons: GraphqlPerson[] }>(GET_PEOPLE, {
variables: { orderBy: orderBy },
});
@@ -66,7 +66,7 @@ function People() {
{