feat: refactoring auth & add email password login (#318)

* feat: wip

* fix: issues

* feat: clean controllers and services

* fix: test

* Fix auth

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2023-06-17 13:42:02 +02:00
committed by GitHub
parent d13ceb98fa
commit 299ca293a8
215 changed files with 1668 additions and 680 deletions

View File

@ -13,7 +13,7 @@ export const getUserIdFromToken: () => string | null = () => {
}
try {
return jwt<{ userId: string }>(accessToken).userId;
return jwt<{ sub: string }>(accessToken).sub;
} catch (error) {
return null;
}
@ -25,10 +25,41 @@ export const hasRefreshToken = () => {
return refreshToken ? true : false;
};
export const refreshAccessToken = async () => {
export const getTokensFromLoginToken = async (loginToken: string) => {
if (!loginToken) {
return;
}
const response = await fetch(
process.env.REACT_APP_AUTH_URL + '/verify' || '',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ loginToken }),
},
);
if (response.ok) {
const { tokens } = await response.json();
if (!tokens) {
return;
}
localStorage.setItem('accessToken', tokens.accessToken.token);
localStorage.setItem('refreshToken', tokens.refreshToken.token);
} else {
localStorage.removeItem('refreshToken');
localStorage.removeItem('accessToken');
}
};
export const getTokensFromRefreshToken = async () => {
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
localStorage.removeItem('accessToken');
return;
}
const response = await fetch(
@ -43,8 +74,13 @@ export const refreshAccessToken = async () => {
);
if (response.ok) {
const { accessToken } = await response.json();
localStorage.setItem('accessToken', accessToken);
const { tokens } = await response.json();
if (!tokens) {
return;
}
localStorage.setItem('accessToken', tokens.accessToken.token);
localStorage.setItem('refreshToken', tokens.refreshToken.token);
} else {
localStorage.removeItem('refreshToken');
localStorage.removeItem('accessToken');

View File

@ -1,29 +1,62 @@
import { waitFor } from '@testing-library/react';
import {
getTokensFromLoginToken,
getTokensFromRefreshToken,
getUserIdFromToken,
hasAccessToken,
hasRefreshToken,
refreshAccessToken,
} from '../AuthService';
const validTokensPayload = {
accessToken: {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzRmZTNhNS1kZjFlLTQxMTktYWZlMC0yYTYyYTJiYTQ4MWUiLCJ3b3Jrc3BhY2VJZCI6InR3ZW50eS03ZWQ5ZDIxMi0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJpYXQiOjE2ODY5OTMxODIsImV4cCI6MTY4Njk5MzQ4Mn0.F_FD6nJ5fssR_47v2XFhtzqjr-wrEQpqaWVq8iIlLJw',
expiresAt: '2023-06-17T09:18:02.942Z',
},
refreshToken: {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzRmZTNhNS1kZjFlLTQxMTktYWZlMC0yYTYyYTJiYTQ4MWUiLCJpYXQiOjE2ODY5OTMxODIsImV4cCI6OTQ2Mjk5MzE4MiwianRpIjoiNzBmMWNhMjctOTYxYi00ZGZlLWEwOTUtMTY2OWEwOGViMTVjIn0.xEdX9dOGzrPHrPsivQYB9ipYGJH-mJ7GSIVPacmIzfY',
expiresAt: '2023-09-15T09:13:02.952Z',
},
};
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,
}),
),
);
});
if (input.toString().match(/\/auth\/token$/g)) {
const refreshToken = init?.body
? JSON.parse(init.body.toString()).refreshToken
: null;
return new Promise((resolve) => {
resolve(
new Response(
JSON.stringify({
tokens:
refreshToken === 'xxx-valid-refresh' ? validTokensPayload : null,
}),
),
);
});
}
if (input.toString().match(/\/auth\/verify$/g)) {
const loginToken = init?.body
? JSON.parse(init.body.toString()).loginToken
: null;
return new Promise((resolve) => {
resolve(
new Response(
JSON.stringify({
tokens:
loginToken === 'xxx-valid-login' ? validTokensPayload : null,
}),
),
);
});
}
return new Promise(() => new Response());
};
global.fetch = mockFetch;
@ -47,21 +80,28 @@ it('hasRefreshToken is true when token is not', () => {
});
it('refreshToken does not refresh the token if refresh token is missing', () => {
refreshAccessToken();
getTokensFromRefreshToken();
expect(localStorage.getItem('accessToken')).toBeNull();
});
it('refreshToken does not refreh the token if refresh token is invalid', () => {
localStorage.setItem('refreshToken', 'xxx-invalid-refresh');
refreshAccessToken();
getTokensFromRefreshToken();
expect(localStorage.getItem('accessToken')).toBeNull();
});
it('refreshToken does not refreh the token if refresh token is empty', () => {
getTokensFromRefreshToken();
expect(localStorage.getItem('accessToken')).toBeNull();
});
it('refreshToken refreshes the token if refresh token is valid', async () => {
localStorage.setItem('refreshToken', 'xxx-valid-refresh');
refreshAccessToken();
getTokensFromRefreshToken();
await waitFor(() => {
expect(localStorage.getItem('accessToken')).toBe('xxx-valid-access');
expect(localStorage.getItem('accessToken')).toBe(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzRmZTNhNS1kZjFlLTQxMTktYWZlMC0yYTYyYTJiYTQ4MWUiLCJ3b3Jrc3BhY2VJZCI6InR3ZW50eS03ZWQ5ZDIxMi0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJpYXQiOjE2ODY5OTMxODIsImV4cCI6MTY4Njk5MzQ4Mn0.F_FD6nJ5fssR_47v2XFhtzqjr-wrEQpqaWVq8iIlLJw',
);
});
});
@ -79,10 +119,32 @@ it('getUserIdFromToken returns null when the token is not valid', async () => {
it('getUserIdFromToken returns the right userId when the token is valid', async () => {
localStorage.setItem(
'accessToken',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJiNzU5MGRiOS1hYzdkLTQyNzUtOWM2Yy0zMjM5NzkxOTI3OTUiLCJ3b3Jrc3BhY2VJZCI6IjdlZDlkMjEyLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsImlhdCI6MTY4NTA5MzE3MiwiZXhwIjoxNjg1MDkzNDcyfQ.0g-z2vKBbGGcs0EmZ3Q7HpZ9Yno_SOrprhcQMm1Zb6Y',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzRmZTNhNS1kZjFlLTQxMTktYWZlMC0yYTYyYTJiYTQ4MWUiLCJ3b3Jrc3BhY2VJZCI6InR3ZW50eS03ZWQ5ZDIxMi0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJpYXQiOjE2ODY5OTI0ODgsImV4cCI6MTY4Njk5Mjc4OH0.IO7U5G14IrrQriw3JjrKVxmZgd6XKL6yUIwuNe_R55E',
);
const userId = getUserIdFromToken();
expect(userId).toBe('b7590db9-ac7d-4275-9c6c-323979192795');
expect(userId).toBe('374fe3a5-df1e-4119-afe0-2a62a2ba481e');
});
it('getTokensFromLoginToken does nothing if loginToken is empty', async () => {
await getTokensFromLoginToken('');
expect(localStorage.getItem('accessToken')).toBeNull();
expect(localStorage.getItem('refreshToken')).toBeNull();
});
it('getTokensFromLoginToken does nothing if loginToken is not valid', async () => {
await getTokensFromLoginToken('xxx-invalid-login');
expect(localStorage.getItem('accessToken')).toBeNull();
expect(localStorage.getItem('refreshToken')).toBeNull();
});
it('getTokensFromLoginToken does nothing if loginToken is not valid', async () => {
await getTokensFromLoginToken('xxx-valid-login');
expect(localStorage.getItem('accessToken')).toBe(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzRmZTNhNS1kZjFlLTQxMTktYWZlMC0yYTYyYTJiYTQ4MWUiLCJ3b3Jrc3BhY2VJZCI6InR3ZW50eS03ZWQ5ZDIxMi0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJpYXQiOjE2ODY5OTMxODIsImV4cCI6MTY4Njk5MzQ4Mn0.F_FD6nJ5fssR_47v2XFhtzqjr-wrEQpqaWVq8iIlLJw',
);
expect(localStorage.getItem('refreshToken')).toBe(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNzRmZTNhNS1kZjFlLTQxMTktYWZlMC0yYTYyYTJiYTQ4MWUiLCJpYXQiOjE2ODY5OTMxODIsImV4cCI6OTQ2Mjk5MzE4MiwianRpIjoiNzBmMWNhMjctOTYxYi00ZGZlLWEwOTUtMTY2OWEwOGViMTVjIn0.xEdX9dOGzrPHrPsivQYB9ipYGJH-mJ7GSIVPacmIzfY',
);
});
afterEach(() => {