Skip to content

Commit

Permalink
[686] - create code for refreshing google auth token (#690)
Browse files Browse the repository at this point in the history
* feat: Add refresh_token to googleLogin response

- Added the `refresh_token` property to the `googleLogin` response type in `responses.ts`.
- This change allows the backend to include a refresh token when a user logs in with Google.

Refactor: Remove unused code in useLogin.tsx

- Removed an extra whitespace in the `useLogin` hook in `useLogin.tsx`.
- This change improves code readability and removes unnecessary code.

Refactor: Comment out unused code in useApiClient.tsx

- Commented out the unused `useNavigate` import and `ROUTES` import in `useApiClient.tsx`.
- This change removes unused code and improves code cleanliness.

Refactor: Remove unused parameters in apiCall function

- Removed the `customCookies` parameter from the `apiCall` function in `useApiClient.tsx`.
- This change removes unused code and simplifies the function signature.

Fix: Update getAllConversations request headers

- Updated the request headers in the `getAllConversations` function in `useApiClient.tsx` to include the access token.
- This change ensures that the request is authenticated and authorized.

Fix: Handle token refresh in useApiClient.tsx

- Added a new `handleTokenRefresh` function in `useApiClient.tsx` to handle token refresh logic.
- This change allows the frontend to refresh the access token when it expires.

Fix: Update postGoogleLogin to store refresh token

- Updated the `postGoogleLogin` function in `useApiClient.tsx` to store the refresh token in cookies.
- This change ensures that the refresh token is available for token refresh requests.

Fix: Remove unused code in postRegister function

- Removed an extra line of code in the `postRegister` function in `useApiClient.tsx`.
- This change removes unnecessary code and improves code readability.

* feat: Add authorization header to createConversationInvite and deleteConversation API calls

* refactor: Remove unused code and console logs in useApiClient hook

* refactor: Remove unused code and console logs in useApiClient hook

* refactor: Remove unused code and console logs in useApiClient hook

* cleanup

---------

Co-authored-by: Svenstar74 <sven.firmbach@gmx.de>
  • Loading branch information
epixieme and Svenstar74 authored Sep 25, 2024
1 parent aa83176 commit a24c2ed
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 42 deletions.
1 change: 1 addition & 0 deletions src/api/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type Login = {
export type googleLogin = {
message: string;
access_token: string;
refresh_token: string;
user: {
email: string;
first_name: string;
Expand Down
2 changes: 1 addition & 1 deletion src/features/auth/hooks/useLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function useLogin() {
const dispatch = useAppDispatch();
const quizIdB = useAppSelector((state) => state.auth.userB.quizId);
const quizIdA = useAppSelector((state) => state.auth.userA.quizId);

const apiClient = useApiClient();
const { showSuccessToast, showErrorToast } = useToastMessage();

Expand Down
3 changes: 2 additions & 1 deletion src/pages/UserBPages/UserBSignUpPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function UserBSignUpPage() {
const { sessionId, quizId } = useAppSelector((state) => state.auth.userB);
const { signUp } = useSignUp();
const [isLoading, setIsLoading] = useState(false);
const devMode = localStorage.getItem('devMode') === 'true';

async function signUpHandler(firstName: string, lastName: string, email: string, password: string) {
setIsLoading(true);
Expand Down Expand Up @@ -53,7 +54,7 @@ function UserBSignUpPage() {
<div style={{ display: 'flex', flexDirection: 'column', gap: 19, justifyContent: 'center', alignItems: 'center' }}>
<SignUpForm isLoading={isLoading} onSignUp={signUpHandler} />
<div style={{ borderBottom: '1px solid #0000001A', height: 1, width: 205 }}></div>
<GoogleLogin navigateAfterLogin={navigateAfterLogin} text="Continue With Google" />
{devMode && <GoogleLogin navigateAfterLogin={navigateAfterLogin} text="Continue With Google" />}
</div>
</PageContent>
</Page>
Expand Down
121 changes: 81 additions & 40 deletions src/shared/hooks/useApiClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Cookies from 'js-cookie';
import { jwtDecode } from 'jwt-decode';

import { useAppSelector } from 'store/hooks';
// import { useLogout } from 'features/auth';
import { useToastMessage } from 'shared/hooks';
import * as requests from 'api/requests';
import * as responses from 'api/responses';
Expand All @@ -26,24 +25,42 @@ const validateToken = (token: string): boolean => {

function useApiClient() {
const { showErrorToast } = useToastMessage();

const sessionId = useAppSelector((state) => state.auth.userA.sessionId);
const quizId = useAppSelector((state) => state.auth.userA.quizId);

async function apiCall<T>(method: string, endpoint: string, headers: { [key: string]: string }, data?: any, withCredentials?: boolean) {
async function apiCall<T>(method: string, endpoint: string, headers: { [key: string]: string }, data?: any, withCredentials?: boolean, customCookies?: { [key: string]: string }) {
// Add sessionId to headers
if (sessionId) {
headers['X-Session-Id'] = sessionId;
}

// customCookies is used to set cookies (In this instance it is the refresh token) for the request and remove them after the request is done.
if (customCookies) {
Object.entries(customCookies).forEach(([key, value]) => {
Cookies.set(key, value, { secure: true, sameSite: 'strict' });
});
}

// Get access token from cookies
const accessToken = Cookies.get('accessToken');
let accessToken = Cookies.get('accessToken');
if (accessToken) {
// Check if the token is valid
if (!validateToken(accessToken)) {
Cookies.remove('accessToken');
// const newAccessToken = await postRefresh();
// headers['Authorization'] = 'Bearer ' + newAccessToken;
} else {
// Attempt to refresh the access token
const newAccessToken = await handleTokenRefresh();

// If a new token is received, update it in the cookies and set the Authorization header
if (newAccessToken) {
accessToken = newAccessToken;
Cookies.set('accessToken', accessToken);
} else {
showErrorToast('Your session has expired. Please login again.');
setTimeout(async () => {
window.location.reload();
}, 2000); // Add a delay to allow the toast to show before redirecting
}

// Set the Authorization header with the valid (or refreshed) token
headers['Authorization'] = 'Bearer ' + accessToken;
}
}
Expand All @@ -53,16 +70,25 @@ function useApiClient() {
method,
headers,
data,
withCredentials,
withCredentials, // Always send credentials unless explicitly set to false
});

if (customCookies) {
Object.keys(customCookies).forEach((key) => {
Cookies.remove(key);
});
}

return response;
}

async function postSession() {
try {
const response = await apiCall<responses.PostSession>('post', '/session', {});
return response.data;
if (response) {
return response.data;
}
throw new Error('Response is undefined');
} catch (error) {
console.log(error);
return { sessionId: '' };
Expand Down Expand Up @@ -108,9 +134,18 @@ function useApiClient() {

async function postRegister({ firstName, lastName, email, password, quizId }: requests.PostRegister) {
const response = await apiCall<responses.PostRegister>('post', '/register', {}, { firstName, lastName, email, password, quizId });
const accessToken = response.data.access_token;

// Store the access token for userA in cookies
const accessToken = response.data.access_token;
if (response) {
if (!response) {
throw new Error('Response is undefined');
}

Cookies.set('accessToken', accessToken, { secure: true });
} else {
throw new Error('Response is undefined');
}
Cookies.set('accessToken', accessToken, { secure: true });

return response.data;
Expand All @@ -137,26 +172,25 @@ function useApiClient() {
Cookies.set('accessToken', accessToken, { secure: true });
}

// Store the refresh token in cookies
// const cookieHeader = response.headers['set-cookie'];

// if (cookieHeader) {
// const refreshToken = cookieHeader[0].split(';')[0].split('=')[1];
// Cookies.set('refreshToken', refreshToken, { expires: 365, secure: true });
// }

// Set refresh token from response headers (if backend returns it)
const cookieHeader = response.headers['set-cookie'];
if (cookieHeader) {
const refreshToken = cookieHeader[0].split(';')[0].split('=')[1];
Cookies.set('refreshToken', refreshToken, { expires: 365, secure: true });
}
return response.data;
}

async function postGoogleLogin(credential: string, quizId: string) {
if (quizId) {
const response = await apiCall<responses.googleLogin>('post', '/auth/google', {}, { credential, quizId }, true);
return response.data;
}
const response = await apiCall<responses.googleLogin>('post', '/auth/google', {}, { credential, quizId }, true);
const { access_token, refresh_token } = response.data;
Cookies.set('accessToken', access_token, { secure: true });
Cookies.set('refreshToken', refresh_token, {
secure: true,
sameSite: 'strict',
path: '/',
});

const response = await apiCall<responses.googleLogin>('post', '/auth/google', {}, { credential }, true);
const { access_token } = response.data;
Cookies.set('accessToken', access_token, { secure: true, sameSite: 'strict' });
return response.data;
}

Expand All @@ -167,30 +201,37 @@ function useApiClient() {
await apiCall('post', '/logout', {});
}

async function handleTokenRefresh(): Promise<string | undefined> {
const newToken = await postRefresh();

if (newToken) {
Cookies.set('accessToken', newToken, { secure: true });
return newToken;
} else {
showErrorToast('Your session has expired. Please login again.');
setTimeout(async () => {
window.location.reload();
}, 2000); // Add a delay to allow the toast to show before redirecting
}
}

async function postRefresh(): Promise<string> {
// Get the refresh token from cookies
Cookies.remove('accessToken');
const refreshToken = Cookies.get('refreshToken');

if (!refreshToken) {
showErrorToast('Refresh token not found. Please log in again.');
return '';
}

try {
const response = await apiCall<{ access_token: string }>('post', '/refresh', {
Cookie: 'refreshToken=' + refreshToken,
});
const response = await apiCall<{ access_token: string }>('post', '/refresh', {}, {}, true, { refresh_token: refreshToken });

// Update the access token in cookies
const accessToken = response.data.access_token;
Cookies.set('accessToken', accessToken, { secure: true });

// Update the refresh token in cookies
const cookieHeader = response.headers['set-cookie'];
if (cookieHeader) {
const refreshToken = cookieHeader[0].split(';')[0].split('=')[1];
Cookies.set('refreshToken', refreshToken, { expires: 365, secure: true });
}

return accessToken;
} catch (error) {
if (error instanceof axios.AxiosError) {
Expand Down Expand Up @@ -273,13 +314,13 @@ function useApiClient() {
}

async function createConversationInvite(invitedUserName: string) {
const response = await apiCall<responses.CreateConversation>('post', '/conversation', {}, { invitedUserName });
const response = await apiCall<responses.CreateConversation>('post', '/conversation', { Authorization: `Bearer ${Cookies.get('accessToken')}` }, { invitedUserName });

return response.data;
}

async function getAllConversations() {
const response = await apiCall<{ conversations: responses.GetAllConversations[] }>('get', '/conversations', {});
const response = await apiCall<{ conversations: responses.GetAllConversations[] }>('get', '/conversations', { Authorization: `Bearer ${Cookies.get('accessToken')}` }, {});

return response.data;
}
Expand All @@ -291,7 +332,7 @@ function useApiClient() {
}

async function deleteConversation(conversationId: string) {
await apiCall('delete', '/conversation/' + conversationId, {});
await apiCall('delete', '/conversation/' + conversationId, { Authorization: `Bearer ${Cookies.get('accessToken')}` });
}

async function putSingleConversation(data: requests.PutSingleConversation) {
Expand Down Expand Up @@ -490,7 +531,7 @@ function useApiClient() {
postSharedSolutions,
getAlignmentSummary,
postConversationConsent,

handleTokenRefresh,
postUserBVisit,
};
}
Expand Down

0 comments on commit a24c2ed

Please sign in to comment.