mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-11 14:41:53 +00:00
222 lines
6.1 KiB
TypeScript
222 lines
6.1 KiB
TypeScript
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, getUserByMe } from "./actions/auth";
|
|
import { apiBaseUrl } from "./lib";
|
|
|
|
interface CustomJwtPayload extends JwtPayload {
|
|
user_id: string;
|
|
tenant_id: string;
|
|
}
|
|
|
|
const refreshAccessToken = async (token: JwtPayload) => {
|
|
const url = new URL(`${apiBaseUrl}/tokens/refresh`);
|
|
|
|
const bodyData = {
|
|
data: {
|
|
type: "tokens-refresh",
|
|
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),
|
|
});
|
|
const newTokens = await response.json();
|
|
|
|
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 {
|
|
error: "RefreshAccessTokenError",
|
|
};
|
|
}
|
|
};
|
|
|
|
export const authConfig = {
|
|
session: {
|
|
strategy: "jwt",
|
|
// The session will be valid for 24 hours
|
|
maxAge: 24 * 60 * 60,
|
|
},
|
|
pages: {
|
|
signIn: "/sign-in",
|
|
newUser: "/sign-up",
|
|
},
|
|
|
|
providers: [
|
|
Credentials({
|
|
name: "credentials",
|
|
credentials: {
|
|
email: { label: "email", type: "text" },
|
|
password: { label: "password", type: "password" },
|
|
},
|
|
async authorize(credentials) {
|
|
const parsedCredentials = z
|
|
.object({
|
|
email: z.string().email(),
|
|
password: z.string().min(12),
|
|
})
|
|
.safeParse(credentials);
|
|
|
|
if (!parsedCredentials.success) return null;
|
|
|
|
const tokenResponse = await getToken(parsedCredentials.data);
|
|
if (!tokenResponse) return null;
|
|
|
|
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,
|
|
};
|
|
},
|
|
}),
|
|
Credentials({
|
|
id: "social-oauth",
|
|
name: "social-oauth",
|
|
credentials: {
|
|
accessToken: { label: "Access Token", type: "text" },
|
|
refreshToken: { label: "Refresh Token", type: "text" },
|
|
},
|
|
async authorize(credentials) {
|
|
const accessToken = credentials?.accessToken;
|
|
|
|
if (!accessToken) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const userMeResponse = await getUserByMe(accessToken as string);
|
|
|
|
const user = {
|
|
name: userMeResponse.name,
|
|
email: userMeResponse.email,
|
|
company: userMeResponse?.company,
|
|
dateJoined: userMeResponse.dateJoined,
|
|
};
|
|
|
|
return {
|
|
...user,
|
|
accessToken: credentials.accessToken,
|
|
refreshToken: credentials.refreshToken,
|
|
};
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-console
|
|
console.error("Error in authorize:", error);
|
|
return null;
|
|
}
|
|
},
|
|
}),
|
|
],
|
|
callbacks: {
|
|
authorized({ auth, request: { nextUrl } }) {
|
|
const isLoggedIn = !!auth?.user;
|
|
const isOnDashboard = nextUrl.pathname.startsWith("/");
|
|
const isSignUpPage = nextUrl.pathname === "/sign-up";
|
|
|
|
// Allow access to sign-up page
|
|
if (isSignUpPage) return true;
|
|
|
|
if (isOnDashboard) {
|
|
if (isLoggedIn) return true;
|
|
return false; // Redirect users who are not logged in to the login page
|
|
} else if (isLoggedIn) {
|
|
return Response.redirect(new URL("/", nextUrl));
|
|
}
|
|
return true;
|
|
},
|
|
|
|
jwt: async ({ token, account, user }) => {
|
|
if (token?.accessToken) {
|
|
const decodedToken = jwtDecode(
|
|
token.accessToken as string,
|
|
) as CustomJwtPayload;
|
|
// eslint-disable-next-line no-console
|
|
// 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: user?.name,
|
|
companyName: user?.company,
|
|
email: user?.email,
|
|
dateJoined: user?.dateJoined,
|
|
};
|
|
|
|
if (account && user) {
|
|
return {
|
|
...token,
|
|
userId: token.user_id,
|
|
tenantId: token.tenant_id,
|
|
accessToken: (user as User & { accessToken: JwtPayload }).accessToken,
|
|
refreshToken: (user as User & { refreshToken: JwtPayload })
|
|
.refreshToken,
|
|
user: userInfo,
|
|
};
|
|
}
|
|
|
|
// eslint-disable-next-line no-console
|
|
// console.log(
|
|
// "Access token expires",
|
|
// token.accessTokenExpires,
|
|
// new Date(Number(token.accessTokenExpires)),
|
|
// );
|
|
|
|
// If the access token is not expired, return the token
|
|
if (
|
|
typeof token.accessTokenExpires === "number" &&
|
|
Date.now() < token.accessTokenExpires
|
|
)
|
|
return token;
|
|
|
|
// If the access token is expired, try to refresh it
|
|
return refreshAccessToken(token as JwtPayload);
|
|
},
|
|
|
|
session: async ({ session, token }) => {
|
|
if (token) {
|
|
session.userId = token?.user_id as string;
|
|
session.tenantId = token?.tenant_id as string;
|
|
session.accessToken = token?.accessToken as string;
|
|
session.refreshToken = token?.refreshToken as string;
|
|
session.user = token.user as any;
|
|
}
|
|
|
|
// console.log("session", session);
|
|
return session;
|
|
},
|
|
},
|
|
} satisfies NextAuthConfig;
|
|
|
|
export const { signIn, signOut, auth, handlers } = NextAuth(authConfig);
|