Reorganize frontend and install Craco to alias modules (#190)
This commit is contained in:
20
front/src/modules/auth/components/RequireAuth.tsx
Normal file
20
front/src/modules/auth/components/RequireAuth.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { hasAccessToken } from '../services/AuthService';
|
||||
|
||||
export function RequireAuth({
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
}): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasAccessToken()) {
|
||||
navigate('/auth/login');
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
return children;
|
||||
}
|
||||
52
front/src/modules/auth/services/AuthService.ts
Normal file
52
front/src/modules/auth/services/AuthService.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import jwt from 'jwt-decode';
|
||||
|
||||
export const hasAccessToken = () => {
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
|
||||
return accessToken ? true : false;
|
||||
};
|
||||
|
||||
export const getUserIdFromToken: () => string | null = () => {
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
if (!accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return jwt<{ userId: string }>(accessToken).userId;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const hasRefreshToken = () => {
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
|
||||
return refreshToken ? true : false;
|
||||
};
|
||||
|
||||
export const refreshAccessToken = async () => {
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
if (!refreshToken) {
|
||||
localStorage.removeItem('accessToken');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
process.env.REACT_APP_AUTH_URL + '/token' || '',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const { accessToken } = await response.json();
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
} else {
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('accessToken');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,90 @@
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
import {
|
||||
getUserIdFromToken,
|
||||
hasAccessToken,
|
||||
hasRefreshToken,
|
||||
refreshAccessToken,
|
||||
} from '../AuthService';
|
||||
|
||||
const mockFetch = async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
): Promise<Response> => {
|
||||
const refreshToken = init?.body
|
||||
? JSON.parse(init.body.toString()).refreshToken
|
||||
: null;
|
||||
return new Promise((resolve) => {
|
||||
resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
accessToken:
|
||||
refreshToken === 'xxx-valid-refresh' ? 'xxx-valid-access' : null,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
global.fetch = mockFetch;
|
||||
|
||||
it('hasAccessToken is true when token is present', () => {
|
||||
localStorage.setItem('accessToken', 'xxx');
|
||||
expect(hasAccessToken()).toBe(true);
|
||||
});
|
||||
|
||||
it('hasAccessToken is false when token is not', () => {
|
||||
expect(hasAccessToken()).toBe(false);
|
||||
});
|
||||
|
||||
it('hasRefreshToken is true when token is present', () => {
|
||||
localStorage.setItem('refreshToken', 'xxx');
|
||||
expect(hasRefreshToken()).toBe(true);
|
||||
});
|
||||
|
||||
it('hasRefreshToken is true when token is not', () => {
|
||||
expect(hasRefreshToken()).toBe(false);
|
||||
});
|
||||
|
||||
it('refreshToken does not refresh the token if refresh token is missing', () => {
|
||||
refreshAccessToken();
|
||||
expect(localStorage.getItem('accessToken')).toBeNull();
|
||||
});
|
||||
|
||||
it('refreshToken does not refreh the token if refresh token is invalid', () => {
|
||||
localStorage.setItem('refreshToken', 'xxx-invalid-refresh');
|
||||
refreshAccessToken();
|
||||
expect(localStorage.getItem('accessToken')).toBeNull();
|
||||
});
|
||||
|
||||
it('refreshToken refreshes the token if refresh token is valid', async () => {
|
||||
localStorage.setItem('refreshToken', 'xxx-valid-refresh');
|
||||
refreshAccessToken();
|
||||
await waitFor(() => {
|
||||
expect(localStorage.getItem('accessToken')).toBe('xxx-valid-access');
|
||||
});
|
||||
});
|
||||
|
||||
it('getUserIdFromToken returns null when the token is not present', async () => {
|
||||
const userId = getUserIdFromToken();
|
||||
expect(userId).toBeNull();
|
||||
});
|
||||
|
||||
it('getUserIdFromToken returns null when the token is not valid', async () => {
|
||||
localStorage.setItem('accessToken', 'xxx-invalid-access');
|
||||
const userId = getUserIdFromToken();
|
||||
expect(userId).toBeNull();
|
||||
});
|
||||
|
||||
it('getUserIdFromToken returns the right userId when the token is valid', async () => {
|
||||
localStorage.setItem(
|
||||
'accessToken',
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJiNzU5MGRiOS1hYzdkLTQyNzUtOWM2Yy0zMjM5NzkxOTI3OTUiLCJ3b3Jrc3BhY2VJZCI6IjdlZDlkMjEyLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsImlhdCI6MTY4NTA5MzE3MiwiZXhwIjoxNjg1MDkzNDcyfQ.0g-z2vKBbGGcs0EmZ3Q7HpZ9Yno_SOrprhcQMm1Zb6Y',
|
||||
);
|
||||
const userId = getUserIdFromToken();
|
||||
expect(userId).toBe('b7590db9-ac7d-4275-9c6c-323979192795');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
Reference in New Issue
Block a user