Skip to content

Commit

Permalink
fix(JAQPOT-214): session-checker and refresh-token (#41)
Browse files Browse the repository at this point in the history
* fix(JAQPOT-214/): session-checker and refresh-token

* fix: do not risk the refresh token impl and add todo
  • Loading branch information
alarv authored Jul 29, 2024
1 parent ded99e5 commit 1a0ae47
Show file tree
Hide file tree
Showing 11 changed files with 1,200 additions and 569 deletions.
1,650 changes: 1,095 additions & 555 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"dependencies": {
"@heroicons/react": "^2.1.3",
"@nextui-org/react": "^2.4.2",
"@nextui-org/react": "^2.4.6",
"date-fns": "^3.6.0",
"framer-motion": "^11.2.10",
"jwt-decode": "^4.0.0",
Expand Down
18 changes: 18 additions & 0 deletions src/app/SessionChecker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';

import { useEffect } from 'react';
import { signOut } from 'next-auth/react';

export default function SessionChecker() {
useEffect(() => {
fetch(`/api/auth/validate`)
.then(async (res) => {
if (!res.ok && res.status === 401) {
await signOut({ redirect: false });
}
})
.catch(() => signOut({ redirect: false }));
});

return null;
}
29 changes: 29 additions & 0 deletions src/app/api/auth/validate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { NextRequest, NextResponse } from 'next/server';
import {
ApiResponse,
errorResponse,
handleApiResponse,
} from '@/app/util/response';
import { auth } from '@/auth';
import { isAuthenticated } from '@/app/util/auth';
import { generatePaginationAndSortingSearchParams } from '@/app/util/sort';

export async function GET(
request: NextRequest,
): Promise<NextResponse<ApiResponse>> {
const session = await auth();
if (!isAuthenticated(session)) {
return errorResponse(
'You need to be authenticated to access this endpoint',
401,
);
}
const res = await fetch(`${process.env.API_URL}/v1/auth/validate`, {
headers: {
Authorization: `Bearer ${session!.token}`,
'Content-Type': 'application/json',
},
});

return handleApiResponse(res);
}
2 changes: 1 addition & 1 deletion src/app/dashboard/models/[modelId]/[tabName]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { isAuthenticated } from '@/app/util/auth';
import ModelBreadcrumbs from '@/app/dashboard/models/[modelId]/components/ModelBreadcrumbs';
import { Metadata } from 'next';
import { generateSharedMetadata } from '@/app/shared.metadata';
import JaqpotTimeAgo from '@/app/dashboard/models/[modelId]/components/TimeAgo';
import JaqpotTimeAgo from '@/app/dashboard/models/[modelId]/components/JaqpotTimeAgo';
import { getErrorMessageFromResponse } from '@/app/util/response';
import { Link } from '@nextui-org/link';
import React from 'react';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client';

import { formatDistance } from 'date-fns';
import { getUserFriendlyDate } from '@/app/util/date';

export default function JaqpotTimeAgo({ date }: { date: Date }) {
return <div>{getUserFriendlyDate(date)}</div>;
return <div title={date.toLocaleString()}>{getUserFriendlyDate(date)}</div>;
}
2 changes: 1 addition & 1 deletion src/app/dashboard/models/components/ModelsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { CustomError } from '@/app/types/CustomError';
import { ApiResponse } from '@/app/util/response';
import { SortDescriptor } from '@react-types/shared/src/collections';
import { convertSortDirection, SORT_DELIMITER } from '@/app/util/sort';
import JaqpotTimeAgo from '@/app/dashboard/models/[modelId]/components/TimeAgo';
import JaqpotTimeAgo from '@/app/dashboard/models/[modelId]/components/JaqpotTimeAgo';

const fetcher: Fetcher<ApiResponse<ModelsResponseDto>, string> = async (
url,
Expand Down
2 changes: 1 addition & 1 deletion src/app/dashboard/results/components/ResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import SWRClientFetchError from '@/app/components/SWRClientFetchError';
import { CustomError } from '@/app/types/CustomError';
import { ApiResponse } from '@/app/util/response';
import { Link } from '@nextui-org/link';
import JaqpotTimeAgo from '@/app/dashboard/models/[modelId]/components/TimeAgo';
import JaqpotTimeAgo from '@/app/dashboard/models/[modelId]/components/JaqpotTimeAgo';
import { getDatasetStatusNode } from '@/app/util/dataset';
import { SortDescriptor } from '@react-types/shared/src/collections';
import { convertSortDirection, SORT_DELIMITER } from '@/app/util/sort';
Expand Down
2 changes: 2 additions & 0 deletions src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import { NextUIProvider } from '@nextui-org/react';
import { useRouter } from 'next/navigation';
import { Toaster } from 'react-hot-toast';
import SessionChecker from '@/app/SessionChecker';

export function Providers({ children }: { children: React.ReactNode }) {
const router = useRouter();

return (
<>
<NextUIProvider navigate={router.push}>
<SessionChecker />
<div>
<Toaster toastOptions={{ duration: 7500 }} />
</div>
Expand Down
7 changes: 0 additions & 7 deletions src/app/util/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ function getMessageFromStatusCode(statusCode: number) {
}
}

async function signoutUserIf401(status: number, code: any) {
if (status === 401 && code !== EMAIL_NOT_VERIFIED) {
await signOut();
}
}

async function parseResponse(res: Response) {
const contentType = res.headers.get('Content-Type') || '';
let data;
Expand All @@ -78,7 +72,6 @@ export async function handleApiResponse(
const data = await parseResponse(res);

if (!res.ok) {
await signoutUserIf401(res.status, data?.code);
const message = data?.message ?? getMessageFromStatusCode(res.status);
return errorResponse(message, res.status);
}
Expand Down
52 changes: 51 additions & 1 deletion src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,28 @@ export const { auth, handlers, signIn, signOut } = NextAuth({
providers: [Keycloak],
callbacks: {
jwt: async ({ token, user, account }) => {
// Initial sign in
if (account && account.access_token) {
// set access_token to the token payload
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
if (account.expires_at) {
token.accessTokenExpires = account.expires_at * 1000;
}
}

return token;

// TODO check if refresh token works
// // Return previous token if the access token has not expired yet
// if (
// !token.accessTokenExpires ||
// Date.now() < (token.accessTokenExpires as number)
// ) {
// return token;
// }
//
// // Access token has expired, try to update it
// return refreshAccessToken(token);
},
session: async ({ session, token, user }) => {
// Next Auth shenanigans :(
Expand All @@ -52,3 +68,37 @@ export const { auth, handlers, signIn, signOut } = NextAuth({
},
},
});

async function refreshAccessToken(token: any) {
try {
const url = `${process.env.AUTH_KEYCLOAK_ISSUER}/protocol/openid-connect/token`;
const response = await fetch(url, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST',
body: new URLSearchParams({
client_id: process.env.AUTH_KEYCLOAK_ID!,
client_secret: process.env.AUTH_KEYCLOAK_SECRET!,
grant_type: 'refresh_token',
refresh_token: token.refreshToken,
}),
});

const refreshedTokens = await response.json();

if (!response.ok) {
throw refreshedTokens;
}

return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
};
} catch (error) {
return {
...token,
error: 'RefreshAccessTokenError',
};
}
}

0 comments on commit 1a0ae47

Please sign in to comment.