mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat(auth): refresh access token on-demand when receiving 401 error
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { auth } from "@/auth.config";
|
||||
import { parseStringify } from "@/lib";
|
||||
|
||||
export const getProviders = async ({
|
||||
@@ -11,10 +12,12 @@ export const getProviders = async ({
|
||||
sort = "",
|
||||
filters = {},
|
||||
}) => {
|
||||
const session = await auth();
|
||||
|
||||
if (isNaN(Number(page)) || page < 1) redirect("/providers");
|
||||
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
const url = new URL(`${keyServer}/providers?sort=-inserted_at`);
|
||||
const url = new URL(`${keyServer}/providers`);
|
||||
|
||||
if (page) url.searchParams.append("page[number]", page.toString());
|
||||
if (query) url.searchParams.append("filter[search]", query);
|
||||
@@ -31,6 +34,7 @@ export const getProviders = async ({
|
||||
const providers = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Accept: "application/vnd.api+json",
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
});
|
||||
const data = await providers.json();
|
||||
@@ -44,6 +48,7 @@ export const getProviders = async ({
|
||||
};
|
||||
|
||||
export const getProvider = async (formData: FormData) => {
|
||||
// const session = await auth();
|
||||
const providerId = formData.get("id");
|
||||
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
@@ -66,6 +71,7 @@ export const getProvider = async (formData: FormData) => {
|
||||
};
|
||||
|
||||
export const updateProvider = async (formData: FormData) => {
|
||||
// const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
|
||||
const providerId = formData.get("providerId");
|
||||
@@ -102,6 +108,7 @@ export const updateProvider = async (formData: FormData) => {
|
||||
};
|
||||
|
||||
export const addProvider = async (formData: FormData) => {
|
||||
const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
|
||||
const providerType = formData.get("providerType");
|
||||
@@ -116,6 +123,7 @@ export const addProvider = async (formData: FormData) => {
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
Accept: "application/vnd.api+json",
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
@@ -140,6 +148,7 @@ export const addProvider = async (formData: FormData) => {
|
||||
};
|
||||
|
||||
export const checkConnectionProvider = async (formData: FormData) => {
|
||||
// const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
|
||||
const providerId = formData.get("id");
|
||||
@@ -164,6 +173,7 @@ export const checkConnectionProvider = async (formData: FormData) => {
|
||||
};
|
||||
|
||||
export const deleteProvider = async (formData: FormData) => {
|
||||
// const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
|
||||
const providerId = formData.get("id");
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Spacer } from "@nextui-org/react";
|
||||
import { redirect } from "next/navigation";
|
||||
import React from "react";
|
||||
|
||||
// import { getUserByMe } from "@/actions/auth/auth";
|
||||
import { auth } from "@/auth.config";
|
||||
import { Header } from "@/components/ui";
|
||||
|
||||
@@ -13,12 +14,17 @@ export default async function Profile() {
|
||||
redirect("/sign-in");
|
||||
}
|
||||
|
||||
// const user = await getUserByMe();
|
||||
// console.log("user", user);
|
||||
return (
|
||||
<>
|
||||
<Header title="User Profile" icon="ci:users" />
|
||||
<Spacer y={4} />
|
||||
<Spacer y={6} />
|
||||
<pre>{JSON.stringify(session.user, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(session.userId, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(session.tenantId, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { redirect } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getUsers } from "@/actions/users";
|
||||
import { auth } from "@/auth.config";
|
||||
// import { auth } from "@/auth.config";
|
||||
import { Header } from "@/components/ui";
|
||||
import {
|
||||
AddUserModal,
|
||||
@@ -18,11 +18,6 @@ export default async function Users({
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
}) {
|
||||
const session = await auth();
|
||||
if (session?.user?.role !== "admin") {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import data from "../../../dataProviders.json";
|
||||
|
||||
export async function GET() {
|
||||
// Simulate fetching data with a delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
return NextResponse.json({ providers: data });
|
||||
}
|
||||
+71
-53
@@ -1,58 +1,59 @@
|
||||
import { jwtDecode, JwtDecodeOptions, JwtPayload } from "jwt-decode";
|
||||
import { jwtDecode, JwtPayload } from "jwt-decode";
|
||||
import NextAuth, { type NextAuthConfig, User } from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getToken } from "./actions/auth";
|
||||
import { getToken, getUserByMe } from "./actions/auth";
|
||||
|
||||
interface CustomJwtPayload extends JwtPayload {
|
||||
user_id: string;
|
||||
tenant_id: string;
|
||||
}
|
||||
|
||||
const refreshAccessToken = async (token: JwtDecodeOptions) => {
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
const url = new URL(`${keyServer}/tokens/refresh`);
|
||||
// const refreshAccessToken = async (token: JwtPayload) => {
|
||||
// // console.log("tokenDESDE EL REFRESH", token.refreshToken);
|
||||
// const keyServer = process.env.API_BASE_URL;
|
||||
// const url = new URL(`${keyServer}/tokens/refresh`);
|
||||
|
||||
const bodyData = {
|
||||
data: {
|
||||
type: "TokenRefresh",
|
||||
attributes: {
|
||||
refresh: token.refreshToken,
|
||||
},
|
||||
},
|
||||
};
|
||||
// const bodyData = {
|
||||
// data: {
|
||||
// type: "TokenRefresh",
|
||||
// attributes: {
|
||||
// refresh: (token as any).refreshToken,
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
Accept: "application/vnd.api+json",
|
||||
},
|
||||
body: JSON.stringify(bodyData),
|
||||
});
|
||||
// console.log("response", response);
|
||||
const newTokens = await response.json();
|
||||
// try {
|
||||
// const response = await fetch(url, {
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/vnd.api+json",
|
||||
// Accept: "application/vnd.api+json",
|
||||
// },
|
||||
// body: JSON.stringify(bodyData),
|
||||
// });
|
||||
// // console.log("response", response);
|
||||
// const newTokens = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`HTTP error! status: ${response.status}`);
|
||||
// }
|
||||
|
||||
return {
|
||||
...token,
|
||||
accessToken: newTokens.data.attributes.access,
|
||||
refreshToken: newTokens.data.attributes.refresh,
|
||||
};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error refreshing access token:", error);
|
||||
return {
|
||||
...token,
|
||||
error: "RefreshAccessTokenError",
|
||||
};
|
||||
}
|
||||
};
|
||||
// return {
|
||||
// ...token,
|
||||
// accessToken: newTokens.data.attributes.access,
|
||||
// refreshToken: newTokens.data.attributes.refresh,
|
||||
// };
|
||||
// } catch (error) {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.error("Error refreshing access token:", error);
|
||||
// return {
|
||||
// ...token,
|
||||
// error: "RefreshAccessTokenError",
|
||||
// };
|
||||
// }
|
||||
// };
|
||||
|
||||
export const authConfig = {
|
||||
session: {
|
||||
@@ -84,8 +85,21 @@ export const authConfig = {
|
||||
|
||||
const tokenResponse = await getToken(parsedCredentials.data);
|
||||
if (!tokenResponse) return null;
|
||||
// const user = await getUserById(isUserValid.userId);
|
||||
return tokenResponse;
|
||||
|
||||
const userMeResponse = await getUserByMe(tokenResponse.accessToken);
|
||||
|
||||
const user = {
|
||||
name: userMeResponse.name,
|
||||
email: userMeResponse.email,
|
||||
company: userMeResponse?.company,
|
||||
dateJoined: userMeResponse.dateJoined,
|
||||
};
|
||||
|
||||
return {
|
||||
...user,
|
||||
accessToken: tokenResponse.accessToken,
|
||||
refreshToken: tokenResponse.refreshToken,
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -109,23 +123,23 @@ export const authConfig = {
|
||||
|
||||
jwt: async ({ token, account, user }) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`In the jwt token - is ${JSON.stringify(token)}`);
|
||||
// console.log(`In the jwt token - is ${JSON.stringify(token)}`);
|
||||
if (token?.accessToken) {
|
||||
const decodedToken = jwtDecode(
|
||||
token.accessToken as string,
|
||||
) as CustomJwtPayload;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("decodedToken", decodedToken);
|
||||
// console.log("decodedToken", decodedToken);
|
||||
token.accessTokenExpires = (decodedToken.exp as number) * 1000;
|
||||
token.user_id = decodedToken.user_id;
|
||||
token.tenant_id = decodedToken.tenant_id;
|
||||
}
|
||||
|
||||
const userInfo = {
|
||||
name: "Leandro",
|
||||
companyName: "Bitnami",
|
||||
email: "john@doe.com",
|
||||
date_joined: "2024-02-02",
|
||||
name: user?.name,
|
||||
companyName: user?.company,
|
||||
email: user?.email,
|
||||
dateJoined: user?.dateJoined,
|
||||
};
|
||||
|
||||
if (account && user) {
|
||||
@@ -151,11 +165,15 @@ export const authConfig = {
|
||||
if (
|
||||
typeof token.accessTokenExpires === "number" &&
|
||||
Date.now() < token.accessTokenExpires
|
||||
)
|
||||
) {
|
||||
console.log("TOKEN IS VALID");
|
||||
return token;
|
||||
|
||||
} else {
|
||||
console.log("TOKEN IS EXPIRED");
|
||||
}
|
||||
return token;
|
||||
// If the access token is expired, try to refresh it
|
||||
return refreshAccessToken(token as unknown as JwtDecodeOptions);
|
||||
// return refreshAccessToken(token as unknown as JwtDecodeOptions);
|
||||
},
|
||||
|
||||
session: async ({ session, token }) => {
|
||||
@@ -169,7 +187,7 @@ export const authConfig = {
|
||||
session.user = token.user as any;
|
||||
}
|
||||
|
||||
console.log("session", session);
|
||||
// console.log("session", session);
|
||||
return session;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import React, { useCallback } from "react";
|
||||
import React, { Suspense, useCallback } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
import { logOut } from "@/actions/auth";
|
||||
@@ -72,22 +72,20 @@ export const SidebarWrap = () => {
|
||||
<Spacer y={8} />
|
||||
|
||||
<Link href={"/profile"}>
|
||||
<UserAvatar
|
||||
userName={session?.user?.name ?? "Guest"}
|
||||
position={session?.user?.companyName ?? "Company Name"}
|
||||
isCompact={isCompact}
|
||||
/>{" "}
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<UserAvatar
|
||||
userName={session?.user?.name ?? "Guest"}
|
||||
position={session?.user?.companyName ?? "Company Name"}
|
||||
isCompact={isCompact}
|
||||
/>
|
||||
</Suspense>
|
||||
</Link>
|
||||
|
||||
<ScrollShadow hideScrollBar className="-mr-6 h-full max-h-full py-6 pr-6">
|
||||
<Sidebar
|
||||
defaultSelectedKey="overview"
|
||||
isCompact={isCompact}
|
||||
items={
|
||||
session?.user?.role === "admin"
|
||||
? sectionItemsWithTeams
|
||||
: sectionItems
|
||||
}
|
||||
items={sectionItemsWithTeams}
|
||||
selectedKeys={[currentPath]}
|
||||
/>
|
||||
</ScrollShadow>
|
||||
|
||||
Vendored
+1
-1
@@ -12,7 +12,7 @@ declare module "next-auth" {
|
||||
user: {
|
||||
name: string;
|
||||
email: string;
|
||||
company?: string;
|
||||
companyName?: string;
|
||||
dateJoined: string;
|
||||
} & DefaultSession["user"];
|
||||
userId: string;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { JwtPayload } from "jwt-decode";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export type IconSvgProps = SVGProps<SVGSVGElement> & {
|
||||
@@ -31,10 +30,6 @@ export interface SearchParamsProps {
|
||||
[key: string]: string | string[] | undefined;
|
||||
}
|
||||
|
||||
export interface CustomJwtPayload extends JwtPayload {
|
||||
user_id: string;
|
||||
tenant_id: string;
|
||||
}
|
||||
export interface ProviderProps {
|
||||
id: string;
|
||||
type: "providers";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./users";
|
||||
@@ -0,0 +1,52 @@
|
||||
export interface UserAttributes {
|
||||
name: string;
|
||||
email: string;
|
||||
company_name: string;
|
||||
date_joined: string;
|
||||
}
|
||||
|
||||
export interface Membership {
|
||||
type: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MembershipMeta {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface UserRelationships {
|
||||
memberships: {
|
||||
meta: MembershipMeta;
|
||||
data: Membership[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserData {
|
||||
type: string;
|
||||
id: string;
|
||||
attributes: UserAttributes;
|
||||
relationships: UserRelationships;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface UserProps {
|
||||
data: UserData;
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
export interface TokenAttributes {
|
||||
refreshToken: string;
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface TokenData {
|
||||
type: string;
|
||||
attributes: TokenAttributes;
|
||||
}
|
||||
|
||||
export interface SignInResponse {
|
||||
data: TokenData;
|
||||
}
|
||||
Reference in New Issue
Block a user