From 2e443db3621dc9d97fffecb77b2d633174c857d3 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Tue, 1 Oct 2024 14:13:49 +0200
Subject: [PATCH 01/26] chore: comanyName is now optional and added
confirmPassword field
---
auth.config.ts | 22 +++++++++++-----------
types/authFormSchema.ts | 5 ++++-
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/auth.config.ts b/auth.config.ts
index 55a81edcca..105a6934c7 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -32,17 +32,17 @@ export const authConfig = {
newUser: "/sign-up",
},
callbacks: {
- authorized({ auth, request: { nextUrl } }) {
- const isLoggedIn = !!auth?.user;
- const isOnDashboard = nextUrl.pathname.startsWith("/");
- if (isOnDashboard) {
- if (isLoggedIn) return true;
- return false; // Redirect unauthenticated users to login page
- } else if (isLoggedIn) {
- return Response.redirect(new URL("/", nextUrl));
- }
- return true;
- },
+ // authorized({ auth, request: { nextUrl } }) {
+ // const isLoggedIn = !!auth?.user;
+ // const isOnDashboard = nextUrl.pathname.startsWith("/");
+ // if (isOnDashboard) {
+ // if (isLoggedIn) return true;
+ // return false; // Redirect unauthenticated users to login page
+ // } else if (isLoggedIn) {
+ // return Response.redirect(new URL("/", nextUrl));
+ // }
+ // return true;
+ // },
jwt({ token, user }) {
if (user) {
diff --git a/types/authFormSchema.ts b/types/authFormSchema.ts
index 3cd577de69..89c8904f56 100644
--- a/types/authFormSchema.ts
+++ b/types/authFormSchema.ts
@@ -3,7 +3,8 @@ import { z } from "zod";
export const authFormSchema = (type: string) =>
z.object({
// Sign Up
- companyName: type === "sign-in" ? z.string().optional() : z.string().min(3),
+ companyName:
+ type === "sign-in" ? z.string().optional() : z.string().min(3).optional(),
firstName:
type === "sign-in"
? z.string().optional()
@@ -13,6 +14,8 @@ export const authFormSchema = (type: string) =>
message: "The name must be at least 3 characters.",
})
.max(20),
+ confirmPassword:
+ type === "sign-in" ? z.string().optional() : z.string().min(6),
termsAndConditions:
type === "sign-in"
? z.enum(["true"]).optional()
From ceebfc9aca4e88e02d45c60e923490d24f33332f Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Tue, 1 Oct 2024 14:14:26 +0200
Subject: [PATCH 02/26] chore: remove unused dependency
---
app/(auth)/sign-in/page.tsx | 2 --
app/(auth)/sign-up/page.tsx | 2 --
2 files changed, 4 deletions(-)
diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx
index f012c3f358..0f8ebffd55 100644
--- a/app/(auth)/sign-in/page.tsx
+++ b/app/(auth)/sign-in/page.tsx
@@ -1,5 +1,3 @@
-import React from "react";
-
import { AuthForm } from "@/components/auth";
const SignIn = () => {
diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx
index dfe04cadeb..8f7954ba34 100644
--- a/app/(auth)/sign-up/page.tsx
+++ b/app/(auth)/sign-up/page.tsx
@@ -1,5 +1,3 @@
-import React from "react";
-
import { AuthForm } from "@/components/auth";
const SignUp = () => {
From 650b95c4f1f3400c691104bb82e2eda5589906a6 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Tue, 1 Oct 2024 14:15:05 +0200
Subject: [PATCH 03/26] chore: add confirmPassword input in sign-up page
---
components/auth/AuthForm.tsx | 30 ++++--------------
components/ui/custom/CustomInput.tsx | 46 +++++++++++++++++++++++-----
2 files changed, 44 insertions(+), 32 deletions(-)
diff --git a/components/auth/AuthForm.tsx b/components/auth/AuthForm.tsx
index b1bbcb866a..c0316dcd4a 100644
--- a/components/auth/AuthForm.tsx
+++ b/components/auth/AuthForm.tsx
@@ -97,7 +97,6 @@ export const AuthForm = ({ type }: { type: string }) => {
diff --git a/components/auth/index.ts b/components/auth/index.ts
index 26161c473c..82fc6feefa 100644
--- a/components/auth/index.ts
+++ b/components/auth/index.ts
@@ -1,2 +1 @@
-export * from "./AuthButton";
-export * from "./AuthForm";
+export * from "./auth-form";
diff --git a/components/ui/custom/custom-button.tsx b/components/ui/custom/custom-button.tsx
index 32287a76c6..ca266275b2 100644
--- a/components/ui/custom/custom-button.tsx
+++ b/components/ui/custom/custom-button.tsx
@@ -8,7 +8,7 @@ export const buttonClasses = {
base: "px-4 inline-flex items-center justify-center relative z-0 text-center whitespace-nowrap",
primary: "bg-default-100 hover:bg-default-200 text-default-800",
secondary: "bg-prowler-grey-light dark:bg-prowler-grey-medium text-white",
- action: "text-white bg-prowler-blue-smoky dark:bg-prowler-grey-medium",
+ action: "text-white bg-prowler-grey-medium dark:bg-prowler-grey-medium",
dashed:
"border border-default border-dashed bg-transparent justify-center whitespace-nowrap font-medium shadow-sm hover:border-solid hover:bg-default-100 active:bg-default-200 active:border-solid",
transparent: "border-0 border-transparent bg-transparent",
@@ -19,6 +19,7 @@ export const buttonClasses = {
interface ButtonProps {
type?: "button" | "submit" | "reset";
ariaLabel: string;
+ ariaDisabled?: boolean;
className?: string;
variant?:
| "solid"
@@ -52,6 +53,7 @@ interface ButtonProps {
export const CustomButton = ({
type = "button",
ariaLabel,
+ ariaDisabled,
className,
variant = "solid",
color = "primary",
@@ -69,6 +71,7 @@ export const CustomButton = ({
}
+ spinner={
+
+ }
isLoading={isLoading}
isIconOnly={isIconOnly}
{...props}
diff --git a/tailwind.config.js b/tailwind.config.js
index f1a4b38db2..608551bb96 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -85,6 +85,7 @@ module.exports = {
},
},
danger: "#E11D48",
+ action: "#353a4d",
},
fontFamily: {
sans: ["var(--font-sans)"],
From 8da95c7102ff8e057fee94ef1015e68747a54680 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Fri, 4 Oct 2024 18:48:51 +0200
Subject: [PATCH 15/26] chore: The session will expire in 24 hours as the
refreshToken coming from the API
---
auth.config.ts | 2 ++
components/auth/auth-form.tsx | 3 ---
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/auth.config.ts b/auth.config.ts
index b5d36fc8b3..20a7c3f137 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -54,6 +54,8 @@ const refreshAccessToken = async (token: CustomJwtPayload) => {
export const authConfig = {
session: {
strategy: "jwt",
+ // The session will be valid for 24 hours
+ maxAge: 24 * 60 * 60,
},
pages: {
signIn: "/sign-in",
diff --git a/components/auth/auth-form.tsx b/components/auth/auth-form.tsx
index 18391d44d4..de53e66f8b 100644
--- a/components/auth/auth-form.tsx
+++ b/components/auth/auth-form.tsx
@@ -21,7 +21,6 @@ import { authFormSchema } from "@/types";
import { NotificationIcon, ProwlerExtended } from "../icons";
import { ThemeSwitch } from "../ThemeSwitch";
import { CustomButton, CustomInput } from "../ui/custom";
-// import { AuthButton } from "./AuthButton";
export const AuthForm = ({ type }: { type: string }) => {
const formSchema = authFormSchema(type);
@@ -43,8 +42,6 @@ export const AuthForm = ({ type }: { type: string }) => {
const [state, dispatch] = useFormState(authenticate, undefined);
- console.log(isLoading, state);
-
useEffect(() => {
if (state?.message === "Success") {
router.push("/");
From abcf37ea92ff73957f4b99aaa7d71caa873eaf6f Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Sat, 5 Oct 2024 06:51:05 +0200
Subject: [PATCH 16/26] feat: Reduce session cookie size drastically
---
actions/auth/auth.ts | 28 +++++++--------------------
auth.config.ts | 45 ++++++++++++++++++++++++++++++++------------
2 files changed, 40 insertions(+), 33 deletions(-)
diff --git a/actions/auth/auth.ts b/actions/auth/auth.ts
index 658d711402..6f3cae0376 100644
--- a/actions/auth/auth.ts
+++ b/actions/auth/auth.ts
@@ -1,12 +1,10 @@
"use server";
-import { jwtDecode } from "jwt-decode";
import { AuthError } from "next-auth";
import { z } from "zod";
import { signIn, signOut } from "@/auth.config";
-import { parseStringify } from "@/lib";
-import { authFormSchema, CustomJwtPayload } from "@/types";
+import { authFormSchema } from "@/types";
const formSchemaSignIn = authFormSchema("sign-in");
// const formSchemaSignUp = authFormSchema("sign-up");
@@ -76,30 +74,18 @@ export const getToken = async (formData: z.infer) => {
body: JSON.stringify(bodyData),
});
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const data = await response.json();
- const parsedData = parseStringify(data);
-
- const accessToken = parsedData.data.attributes.access;
- const refreshToken = parsedData.data.attributes.refresh;
-
- const decodedToken = jwtDecode(accessToken);
- const userId = decodedToken.user_id;
-
- // Verify if the response contains the expected data
+ if (!response.ok) return null;
+ const parsedResponse = await response.json();
+ // return parsedResponse;
+ const accessToken = parsedResponse.data.attributes.access;
+ const refreshToken = parsedResponse.data.attributes.refresh;
return {
- email: formData.email,
accessToken,
refreshToken,
- userId,
- // Add here other user fields we need in the session
};
} catch (error) {
- // eslint-disable-next-line no-console
- console.error("Error en trying to get token:", error);
+ throw new Error("Error in trying to get token");
}
};
diff --git a/auth.config.ts b/auth.config.ts
index 20a7c3f137..d62704e913 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -1,10 +1,20 @@
import { jwtDecode } from "jwt-decode";
import NextAuth, { type NextAuthConfig } from "next-auth";
+import { DefaultSession } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { z } from "zod";
import { getToken } from "./actions/auth";
import { CustomJwtPayload } from "./types";
+import { UserAttributes } from "./types/users";
+
+declare module "next-auth" {
+ interface Session extends DefaultSession {
+ accessToken: string;
+ refreshToken: string;
+ user: UserAttributes;
+ }
+}
const refreshAccessToken = async (token: CustomJwtPayload) => {
const keyServer = process.env.API_BASE_URL;
@@ -79,11 +89,10 @@ export const authConfig = {
if (!parsedCredentials.success) return null;
- const user = await getToken(parsedCredentials.data);
-
- if (!user) return null;
-
- return user;
+ const tokenResponse = await getToken(parsedCredentials.data);
+ if (!tokenResponse) return null;
+ // const user = await getUserById(isUserValid.userId);
+ return tokenResponse;
},
}),
],
@@ -105,20 +114,29 @@ export const authConfig = {
return true;
},
- jwt: async ({ token, user, account }) => {
+ jwt: async ({ token, account, user }) => {
+ // console.log(`In the jwt token - is ${JSON.stringify(token)}`);
if (token?.accessToken) {
- const decodedToken = jwtDecode(token.accessToken);
+ const decodedToken = jwtDecode(token.accessToken);
console.log("decodedToken", decodedToken);
token.accessTokenExpires = decodedToken?.exp * 1000;
}
- if (user && account) {
+
+ if (account && user) {
// token.data = user;
return {
...token,
accessToken: user.accessToken,
refreshToken: user.refreshToken,
- user,
+ user: {
+ id: "123",
+ tenantId: "123",
+ name: "John",
+ companyName: "Doe",
+ email: "john@doe.com",
+ date_joined: "2024-02-02",
+ },
};
}
@@ -135,11 +153,14 @@ export const authConfig = {
},
session: async ({ session, token }) => {
- // session.user = token.data as any;
+ // session.user = token.data;
+
if (token) {
- session.accessToken = token.accessToken;
- session.refreshToken = token.refreshToken;
+ session.accessToken = token?.accessToken as string;
+ session.refreshToken = token?.refreshToken as string;
+ session.user = token.user;
}
+
// console.log("session", session);
return session;
},
From 735f830251d6571f40e675a9cd08c51648aeff0e Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Sat, 5 Oct 2024 14:02:22 +0200
Subject: [PATCH 17/26] feat: add function getUserByMe
---
actions/auth/auth.ts | 34 +++++++++++++++++++-
auth.config.ts | 74 ++++++++++++++++++++++++--------------------
nextauth.d.ts | 22 ++++++++-----
3 files changed, 89 insertions(+), 41 deletions(-)
diff --git a/actions/auth/auth.ts b/actions/auth/auth.ts
index 6f3cae0376..28504d2c0c 100644
--- a/actions/auth/auth.ts
+++ b/actions/auth/auth.ts
@@ -77,7 +77,7 @@ export const getToken = async (formData: z.infer) => {
if (!response.ok) return null;
const parsedResponse = await response.json();
- // return parsedResponse;
+
const accessToken = parsedResponse.data.attributes.access;
const refreshToken = parsedResponse.data.attributes.refresh;
return {
@@ -89,6 +89,38 @@ export const getToken = async (formData: z.infer) => {
}
};
+export const getUserByMe = async (accessToken: string) => {
+ const keyServer = process.env.API_BASE_URL;
+ const url = new URL(`${keyServer}/users/me`);
+
+ try {
+ const response = await fetch(url.toString(), {
+ method: "GET",
+ headers: {
+ Accept: "application/vnd.api+json",
+ Authorization: `Bearer ${accessToken}`,
+ },
+ });
+
+ if (!response.ok) throw new Error("Error in trying to get user by me");
+
+ const parsedResponse = await response.json();
+
+ const name = parsedResponse.data.attributes.name;
+ const email = parsedResponse.data.attributes.email;
+ const company = parsedResponse.data.attributes.company_name;
+ const dateJoined = parsedResponse.data.attributes.date_joined;
+ return {
+ name,
+ email,
+ company,
+ dateJoined,
+ };
+ } catch (error) {
+ throw new Error("Error in trying to get user by me");
+ }
+};
+
export async function logOut() {
await signOut();
}
diff --git a/auth.config.ts b/auth.config.ts
index d62704e913..2370fe02bb 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -1,22 +1,16 @@
-import { jwtDecode } from "jwt-decode";
-import NextAuth, { type NextAuthConfig } from "next-auth";
-import { DefaultSession } from "next-auth";
+import { jwtDecode, JwtDecodeOptions, 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 { CustomJwtPayload } from "./types";
-import { UserAttributes } from "./types/users";
-declare module "next-auth" {
- interface Session extends DefaultSession {
- accessToken: string;
- refreshToken: string;
- user: UserAttributes;
- }
+interface CustomJwtPayload extends JwtPayload {
+ user_id: string;
+ tenant_id: string;
}
-const refreshAccessToken = async (token: CustomJwtPayload) => {
+const refreshAccessToken = async (token: JwtDecodeOptions) => {
const keyServer = process.env.API_BASE_URL;
const url = new URL(`${keyServer}/tokens/refresh`);
@@ -42,7 +36,6 @@ const refreshAccessToken = async (token: CustomJwtPayload) => {
const newTokens = await response.json();
if (!response.ok) {
- // TODO: handle error
throw new Error(`HTTP error! status: ${response.status}`);
}
@@ -115,53 +108,68 @@ export const authConfig = {
},
jwt: async ({ token, account, user }) => {
- // console.log(`In the jwt token - is ${JSON.stringify(token)}`);
+ // eslint-disable-next-line no-console
+ console.log(`In the jwt token - is ${JSON.stringify(token)}`);
if (token?.accessToken) {
- const decodedToken = jwtDecode(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 * 1000;
+ 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",
+ };
+
if (account && user) {
- // token.data = user;
return {
...token,
- accessToken: user.accessToken,
- refreshToken: user.refreshToken,
- user: {
- id: "123",
- tenantId: "123",
- name: "John",
- companyName: "Doe",
- email: "john@doe.com",
- date_joined: "2024-02-02",
- },
+ 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 (Date.now() < token.accessTokenExpires) return token;
+ // If the access token is not expired, return the token
+ if (
+ typeof token.accessTokenExpires === "number" &&
+ Date.now() < token.accessTokenExpires
+ )
+ return token;
- // Access token is expired, we need to refresh it
- return refreshAccessToken(token);
+ // If the access token is expired, try to refresh it
+ return refreshAccessToken(token as unknown as JwtDecodeOptions);
},
session: async ({ session, token }) => {
// session.user = token.data;
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;
+ session.user = token.user as any;
}
- // console.log("session", session);
+ console.log("session", session);
return session;
},
},
diff --git a/nextauth.d.ts b/nextauth.d.ts
index 2fe2480ce5..e4eaa51f8a 100644
--- a/nextauth.d.ts
+++ b/nextauth.d.ts
@@ -1,15 +1,23 @@
import { DefaultSession } from "next-auth";
declare module "next-auth" {
- interface Session {
+ interface User extends NextAuthUser {
+ name: string;
+ email: string;
+ company?: string;
+ dateJoined: string;
+ }
+
+ interface Session extends DefaultSession {
user: {
- id: string;
- tenantId: string;
- firstName: string;
- companyName: string;
+ name: string;
email: string;
- role: string;
- image?: string;
+ company?: string;
+ dateJoined: string;
} & DefaultSession["user"];
+ userId: string;
+ tenantId: string;
+ accessToken: string;
+ refreshToken: string;
}
}
From ff74edcc048a40ec06f59e35c402ba90a86d38e9 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Sat, 5 Oct 2024 14:29:41 +0200
Subject: [PATCH 18/26] feat(auth): refresh access token on-demand when
receiving 401 error
---
actions/providers/providers.ts | 12 ++-
app/(prowler)/profile/page.tsx | 6 ++
app/(prowler)/users/page.tsx | 7 +-
app/api/providers/route.ts | 10 ---
auth.config.ts | 124 +++++++++++++++-----------
components/ui/sidebar/SidebarWrap.tsx | 20 ++---
nextauth.d.ts | 2 +-
types/components.ts | 5 --
types/users/index.ts | 1 +
types/users/users.ts | 52 +++++++++++
10 files changed, 152 insertions(+), 87 deletions(-)
delete mode 100644 app/api/providers/route.ts
create mode 100644 types/users/index.ts
create mode 100644 types/users/users.ts
diff --git a/actions/providers/providers.ts b/actions/providers/providers.ts
index ea37ca8986..8c67196f92 100644
--- a/actions/providers/providers.ts
+++ b/actions/providers/providers.ts
@@ -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");
diff --git a/app/(prowler)/profile/page.tsx b/app/(prowler)/profile/page.tsx
index 76c72a8800..91fa4907f0 100644
--- a/app/(prowler)/profile/page.tsx
+++ b/app/(prowler)/profile/page.tsx
@@ -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 (
<>
{JSON.stringify(session.user, null, 2)}
+ {JSON.stringify(session.userId, null, 2)}
+ {JSON.stringify(session.tenantId, null, 2)}
+ {JSON.stringify(session, null, 2)}
>
);
}
diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx
index 0a2f240f57..34a887589f 100644
--- a/app/(prowler)/users/page.tsx
+++ b/app/(prowler)/users/page.tsx
@@ -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 (
diff --git a/app/api/providers/route.ts b/app/api/providers/route.ts
deleted file mode 100644
index ef9b5995e2..0000000000
--- a/app/api/providers/route.ts
+++ /dev/null
@@ -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 });
-}
diff --git a/auth.config.ts b/auth.config.ts
index 2370fe02bb..a249c85aaa 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -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;
},
},
diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx
index 59b29bb458..e4e0043eac 100644
--- a/components/ui/sidebar/SidebarWrap.tsx
+++ b/components/ui/sidebar/SidebarWrap.tsx
@@ -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 = () => {
- {" "}
+ Loading...
}>
+
+
diff --git a/nextauth.d.ts b/nextauth.d.ts
index e4eaa51f8a..d5b6f472f9 100644
--- a/nextauth.d.ts
+++ b/nextauth.d.ts
@@ -12,7 +12,7 @@ declare module "next-auth" {
user: {
name: string;
email: string;
- company?: string;
+ companyName?: string;
dateJoined: string;
} & DefaultSession["user"];
userId: string;
diff --git a/types/components.ts b/types/components.ts
index 9ad3507108..6bac7ce1d2 100644
--- a/types/components.ts
+++ b/types/components.ts
@@ -1,4 +1,3 @@
-import { JwtPayload } from "jwt-decode";
import { SVGProps } from "react";
export type IconSvgProps = SVGProps & {
@@ -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";
diff --git a/types/users/index.ts b/types/users/index.ts
new file mode 100644
index 0000000000..ddf77b4624
--- /dev/null
+++ b/types/users/index.ts
@@ -0,0 +1 @@
+export * from "./users";
diff --git a/types/users/users.ts b/types/users/users.ts
new file mode 100644
index 0000000000..61a92ae70e
--- /dev/null
+++ b/types/users/users.ts
@@ -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;
+}
From e2261af59f07f611e4e6e69f9658f485a801da2e Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Sat, 5 Oct 2024 14:43:02 +0200
Subject: [PATCH 19/26] feat(auth): refresh access token on-demand when
receiving 401 error
---
auth.config.ts | 93 ++++++++++++++++++++++----------------------------
1 file changed, 41 insertions(+), 52 deletions(-)
diff --git a/auth.config.ts b/auth.config.ts
index a249c85aaa..ffcfaad06d 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -10,50 +10,47 @@ interface CustomJwtPayload extends JwtPayload {
tenant_id: string;
}
-// 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 refreshAccessToken = async (token: JwtPayload) => {
+ const keyServer = process.env.API_BASE_URL;
+ const url = new URL(`${keyServer}/tokens/refresh`);
-// const bodyData = {
-// data: {
-// type: "TokenRefresh",
-// attributes: {
-// refresh: (token as any).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),
+ });
+ 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 {
+ error: "RefreshAccessTokenError",
+ };
+ }
+};
export const authConfig = {
session: {
@@ -122,14 +119,12 @@ export const authConfig = {
},
jwt: async ({ token, account, user }) => {
- // eslint-disable-next-line no-console
- // 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;
@@ -165,20 +160,14 @@ 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 JwtPayload);
},
session: async ({ session, token }) => {
- // session.user = token.data;
-
if (token) {
session.userId = token?.user_id as string;
session.tenantId = token?.tenant_id as string;
From f0f4e85f0644759533635d6f32326026544ced57 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Sat, 5 Oct 2024 19:08:28 +0200
Subject: [PATCH 20/26] feat(sign-up): integrate sign-up functionality in the
application
---
actions/auth/auth.ts | 42 ++++++-
auth.config.ts | 2 +-
components/auth/auth-form.tsx | 157 ++++++++++++++++----------
components/ui/sidebar/SidebarWrap.tsx | 6 +-
types/authFormSchema.ts | 21 ++--
types/components.ts | 9 ++
6 files changed, 163 insertions(+), 74 deletions(-)
diff --git a/actions/auth/auth.ts b/actions/auth/auth.ts
index 28504d2c0c..394bdf4f3e 100644
--- a/actions/auth/auth.ts
+++ b/actions/auth/auth.ts
@@ -7,7 +7,7 @@ import { signIn, signOut } from "@/auth.config";
import { authFormSchema } from "@/types";
const formSchemaSignIn = authFormSchema("sign-in");
-// const formSchemaSignUp = authFormSchema("sign-up");
+const formSchemaSignUp = authFormSchema("sign-up");
const defaultValues: z.infer = {
email: "",
@@ -50,6 +50,46 @@ export async function authenticate(
}
}
+export const createNewUser = async (
+ formData: z.infer,
+) => {
+ const keyServer = process.env.API_BASE_URL;
+ const url = new URL(`${keyServer}/users`);
+
+ const bodyData = {
+ data: {
+ type: "User",
+ attributes: {
+ name: formData.name,
+ company_name: formData.company,
+ email: formData.email,
+ password: formData.password,
+ },
+ },
+ };
+
+ try {
+ const response = await fetch(url.toString(), {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/vnd.api+json",
+ Accept: "application/vnd.api+json",
+ },
+ body: JSON.stringify(bodyData),
+ });
+
+ const parsedResponse = await response.json();
+
+ if (!response.ok) {
+ return parsedResponse;
+ }
+
+ return parsedResponse;
+ } catch (error) {
+ return { errors: [{ detail: "Network error or server is unreachable" }] };
+ }
+};
+
export const getToken = async (formData: z.infer) => {
const keyServer = process.env.API_BASE_URL;
const url = new URL(`${keyServer}/tokens`);
diff --git a/auth.config.ts b/auth.config.ts
index ffcfaad06d..556c7d40c0 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -74,7 +74,7 @@ export const authConfig = {
const parsedCredentials = z
.object({
email: z.string().email(),
- password: z.string().min(6),
+ password: z.string().min(12),
})
.safeParse(credentials);
diff --git a/components/auth/auth-form.tsx b/components/auth/auth-form.tsx
index de53e66f8b..1a9dbd9777 100644
--- a/components/auth/auth-form.tsx
+++ b/components/auth/auth-form.tsx
@@ -9,18 +9,19 @@ import { useFormState } from "react-dom";
import { useForm } from "react-hook-form";
import { z } from "zod";
-import { authenticate } from "@/actions/auth";
+import { authenticate, createNewUser } from "@/actions/auth";
import {
Form,
FormControl,
FormField,
FormMessage,
} from "@/components/ui/form";
-import { authFormSchema } from "@/types";
+import { ApiError, authFormSchema } from "@/types";
import { NotificationIcon, ProwlerExtended } from "../icons";
import { ThemeSwitch } from "../ThemeSwitch";
import { CustomButton, CustomInput } from "../ui/custom";
+import { useToast } from "../ui/toast";
export const AuthForm = ({ type }: { type: string }) => {
const formSchema = authFormSchema(type);
@@ -31,17 +32,20 @@ export const AuthForm = ({ type }: { type: string }) => {
email: "",
password: "",
...(type === "sign-up" && {
- firstName: "",
- companyName: "",
+ name: "",
+ company: "",
+ termsAndConditions: false,
confirmPassword: "",
}),
},
});
- const isLoading = form.formState.isSubmitting;
-
const [state, dispatch] = useFormState(authenticate, undefined);
+ const { toast } = useToast();
+
+ const isLoading = form.formState.isSubmitting;
+
useEffect(() => {
if (state?.message === "Success") {
router.push("/");
@@ -49,20 +53,53 @@ export const AuthForm = ({ type }: { type: string }) => {
}, [state]);
const onSubmit = async (data: z.infer) => {
- try {
- // Sign-up logic will be here.
- if (type === "sign-in") {
- dispatch({
- email: data.email.toLowerCase(),
- password: data.password,
+ if (type === "sign-in") {
+ dispatch({
+ email: data.email.toLowerCase(),
+ password: data.password,
+ });
+ }
+ if (type === "sign-up") {
+ const newUser = await createNewUser(data);
+
+ if (newUser?.errors && newUser.errors.length > 0) {
+ newUser.errors.forEach((error: ApiError) => {
+ const errorMessage = error.detail;
+
+ switch (error.source.pointer) {
+ case "/data/attributes/name":
+ form.setError("name", { type: "server", message: errorMessage });
+ break;
+ case "/data/attributes/email":
+ form.setError("email", { type: "server", message: errorMessage });
+ break;
+ case "/data/attributes/password":
+ form.setError("password", {
+ type: "server",
+ message: errorMessage,
+ });
+ break;
+ default:
+ toast({
+ variant: "destructive",
+ title: "Oops! Something went wrong",
+ description: errorMessage,
+ });
+ }
+ });
+ } else {
+ toast({
+ title: "Success!",
+ description: "The user was registered successfully.",
+ });
+ form.reset({
+ name: "",
+ company: "",
+ email: "",
+ password: "",
+ termsAndConditions: false,
});
}
- if (type === "sign-up") {
- // const newUser = await signUp(data);
- }
- } catch (error) {
- // eslint-disable-next-line no-console
- console.error(error);
}
};
@@ -107,18 +144,20 @@ export const AuthForm = ({ type }: { type: string }) => {
<>
>
)}
@@ -128,9 +167,15 @@ export const AuthForm = ({ type }: { type: string }) => {
type="email"
label="Email"
placeholder="Enter your email"
+ isInvalid={!!form.formState.errors.email}
/>
-
+
{type === "sign-in" && (
@@ -143,40 +188,40 @@ export const AuthForm = ({ type }: { type: string }) => {
)}
{type === "sign-up" && (
- (
- <>
-
-
-
- field.onChange(e.target.checked ? "true" : "false")
- }
- >
- I agree with the
-
- Terms
-
- and
-
- Privacy Policy
-
-
-
-
- >
- )}
- />
+ <>
+
+ (
+ <>
+
+ field.onChange(e.target.checked)}
+ >
+ I agree with the
+
+ Terms
+
+ and
+
+ Privacy Policy
+
+
+
+
+ >
+ )}
+ />
+ >
)}
{state?.message === "Credentials error" && (
@@ -186,8 +231,6 @@ export const AuthForm = ({ type }: { type: string }) => {
)}
- {isLoading && Loading...
}
-
{
@@ -74,8 +74,8 @@ export const SidebarWrap = () => {
Loading...}>
diff --git a/types/authFormSchema.ts b/types/authFormSchema.ts
index 7705b2e19f..6f3ab5a087 100644
--- a/types/authFormSchema.ts
+++ b/types/authFormSchema.ts
@@ -4,11 +4,9 @@ export const authFormSchema = (type: string) =>
z
.object({
// Sign Up
- companyName:
- type === "sign-in"
- ? z.string().optional()
- : z.string().min(3).optional(),
- firstName:
+ company:
+ type === "sign-in" ? z.string().optional() : z.string().optional(),
+ name:
type === "sign-in"
? z.string().optional()
: z
@@ -20,20 +18,19 @@ export const authFormSchema = (type: string) =>
confirmPassword:
type === "sign-in"
? z.string().optional()
- : z.string().min(3, {
+ : z.string().min(12, {
message: "It must contain at least 12 characters.",
}),
termsAndConditions:
type === "sign-in"
- ? z.enum(["true"]).optional()
- : z.enum(["true"], {
- errorMap: () => ({
- message: "You must accept the terms and conditions.",
- }),
+ ? z.boolean().optional()
+ : z.boolean().refine((value) => value === true, {
+ message: "You must accept the terms and conditions.",
}),
+
// Fields for Sign In and Sign Up
email: z.string().email(),
- password: z.string().min(3, {
+ password: z.string().min(12, {
message: "It must contain at least 12 characters.",
}),
})
diff --git a/types/components.ts b/types/components.ts
index 6bac7ce1d2..02018469dc 100644
--- a/types/components.ts
+++ b/types/components.ts
@@ -30,6 +30,15 @@ export interface SearchParamsProps {
[key: string]: string | string[] | undefined;
}
+export interface ApiError {
+ detail: string;
+ status: string;
+ source: {
+ pointer: string;
+ };
+ code: string;
+}
+
export interface ProviderProps {
id: string;
type: "providers";
From d138c4eeb8a84e235170bee02f7e81dd0c8a1289 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Sun, 6 Oct 2024 13:20:45 +0200
Subject: [PATCH 21/26] feat(sign-up/sign-in): styling the the auth page
---
components/auth/auth-form.tsx | 377 +++++++++++++++++-----------------
1 file changed, 190 insertions(+), 187 deletions(-)
diff --git a/components/auth/auth-form.tsx b/components/auth/auth-form.tsx
index 1a9dbd9777..728afb930a 100644
--- a/components/auth/auth-form.tsx
+++ b/components/auth/auth-form.tsx
@@ -2,7 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Icon } from "@iconify/react";
-import { Button, Checkbox, Divider, Link } from "@nextui-org/react";
+import { Button, Checkbox, Divider, Link, User } from "@nextui-org/react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useFormState } from "react-dom";
@@ -26,6 +26,7 @@ import { useToast } from "../ui/toast";
export const AuthForm = ({ type }: { type: string }) => {
const formSchema = authFormSchema(type);
const router = useRouter();
+
const form = useForm>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -104,196 +105,198 @@ export const AuthForm = ({ type }: { type: string }) => {
};
return (
-
- {/* Brand Logo and ThemeSwitch */}
-
-
-
-
+
+ {/* Auth Form */}
+
+ {/* Prowler Logo */}
+
+
+
+
+
+ {type === "sign-in" ? "Sign In" : "Sign Up"}
+
+
+
+
+
+
+
+ {type === "sign-in" && (
+ <>
+
+
+
+ }
+ variant="bordered"
+ >
+ Continue with Google
+
+
+ }
+ variant="bordered"
+ >
+ Continue with Github
+
+
+ >
+ )}
+ {type === "sign-in" ? (
+
+ Need to create an account?
+ Sign Up
+
+ ) : (
+
+ Already have an account?
+ Log In
+
+ )}
- {/* Testimonial */}
-
-
- “
- Open Cloud Security
- ”
-
-
-
-
-
- {type === "sign-in" ? "Sign In" : "Sign Up"}
-
-
-
-
-
- {type === "sign-in" && (
- <>
-
-
-
- }
- variant="bordered"
- >
- Continue with Google
-
-
- }
- variant="bordered"
- >
- Continue with Github
-
-
- >
- )}
- {type === "sign-in" ? (
-
- Need to create an account?
- Sign Up
+
+
+
+ Open Source Security Platform
- ) : (
-
- Already have an account?
- Log In
-
- )}
+
);
From 22bacfdcb3e419b65052901603fd9bab0b6f775f Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Sun, 6 Oct 2024 13:23:59 +0200
Subject: [PATCH 22/26] feat(sign-up/sign-in): remove unused component
---
components/auth/auth-form.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/components/auth/auth-form.tsx b/components/auth/auth-form.tsx
index 728afb930a..fe4e62f9d1 100644
--- a/components/auth/auth-form.tsx
+++ b/components/auth/auth-form.tsx
@@ -2,7 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Icon } from "@iconify/react";
-import { Button, Checkbox, Divider, Link, User } from "@nextui-org/react";
+import { Button, Checkbox, Divider, Link } from "@nextui-org/react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useFormState } from "react-dom";
From b8b05b923fcdf589fe1ec0035cae458449719628 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Mon, 7 Oct 2024 06:41:13 +0200
Subject: [PATCH 23/26] chore: tweak styles for Prowler logo in signIn page
---
components/auth/auth-form.tsx | 8 +++--
components/icons/prowler/ProwlerIcons.tsx | 41 ++++++++++++-----------
2 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/components/auth/auth-form.tsx b/components/auth/auth-form.tsx
index fe4e62f9d1..038a34dd6d 100644
--- a/components/auth/auth-form.tsx
+++ b/components/auth/auth-form.tsx
@@ -63,6 +63,10 @@ export const AuthForm = ({ type }: { type: string }) => {
if (type === "sign-up") {
const newUser = await createNewUser(data);
+ if (!newUser.errors) {
+ router.push("/sign-in");
+ }
+
if (newUser?.errors && newUser.errors.length > 0) {
newUser.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
@@ -109,8 +113,8 @@ export const AuthForm = ({ type }: { type: string }) => {
{/* Auth Form */}
{/* Prowler Logo */}
-
-
+
diff --git a/components/icons/prowler/ProwlerIcons.tsx b/components/icons/prowler/ProwlerIcons.tsx
index 0229c6e0b5..425788e88b 100644
--- a/components/icons/prowler/ProwlerIcons.tsx
+++ b/components/icons/prowler/ProwlerIcons.tsx
@@ -7,26 +7,27 @@ export const ProwlerExtended: React.FC
= ({
width = 164,
height,
...props
-}) => (
-
-);
+}) => {
+ return (
+
+ );
+};
export const ProwlerShort: React.FC = ({
size,
From f5e53e814b45b090b9b3c37776162ac82194d763 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Mon, 7 Oct 2024 07:07:26 +0200
Subject: [PATCH 24/26] chore: tweak styles auth pages
---
app/(auth)/sign-in/page.tsx | 2 +-
app/(auth)/sign-up/page.tsx | 2 +-
components/auth/{ => oss}/auth-form.tsx | 21 +++++++++++----------
components/auth/{ => oss}/index.ts | 0
4 files changed, 13 insertions(+), 12 deletions(-)
rename components/auth/{ => oss}/auth-form.tsx (93%)
rename components/auth/{ => oss}/index.ts (100%)
diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx
index 0f8ebffd55..b5bd978029 100644
--- a/app/(auth)/sign-in/page.tsx
+++ b/app/(auth)/sign-in/page.tsx
@@ -1,4 +1,4 @@
-import { AuthForm } from "@/components/auth";
+import { AuthForm } from "@/components/auth/oss";
const SignIn = () => {
return ;
diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx
index 8f7954ba34..8e525507b8 100644
--- a/app/(auth)/sign-up/page.tsx
+++ b/app/(auth)/sign-up/page.tsx
@@ -1,4 +1,4 @@
-import { AuthForm } from "@/components/auth";
+import { AuthForm } from "@/components/auth/oss";
const SignUp = () => {
return ;
diff --git a/components/auth/auth-form.tsx b/components/auth/oss/auth-form.tsx
similarity index 93%
rename from components/auth/auth-form.tsx
rename to components/auth/oss/auth-form.tsx
index 038a34dd6d..1807fcce19 100644
--- a/components/auth/auth-form.tsx
+++ b/components/auth/oss/auth-form.tsx
@@ -4,12 +4,16 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { Icon } from "@iconify/react";
import { Button, Checkbox, Divider, Link } from "@nextui-org/react";
import { useRouter } from "next/navigation";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
import { useFormState } from "react-dom";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { authenticate, createNewUser } from "@/actions/auth";
+import { NotificationIcon, ProwlerExtended } from "@/components/icons";
+import { ThemeSwitch } from "@/components/ThemeSwitch";
+import { useToast } from "@/components/ui";
+import { CustomButton, CustomInput } from "@/components/ui/custom";
import {
Form,
FormControl,
@@ -18,11 +22,6 @@ import {
} from "@/components/ui/form";
import { ApiError, authFormSchema } from "@/types";
-import { NotificationIcon, ProwlerExtended } from "../icons";
-import { ThemeSwitch } from "../ThemeSwitch";
-import { CustomButton, CustomInput } from "../ui/custom";
-import { useToast } from "../ui/toast";
-
export const AuthForm = ({ type }: { type: string }) => {
const formSchema = authFormSchema(type);
const router = useRouter();
@@ -42,11 +41,9 @@ export const AuthForm = ({ type }: { type: string }) => {
});
const [state, dispatch] = useFormState(authenticate, undefined);
-
+ const [isLoading, setIsLoading] = useState(false);
const { toast } = useToast();
- const isLoading = form.formState.isSubmitting;
-
useEffect(() => {
if (state?.message === "Success") {
router.push("/");
@@ -55,13 +52,17 @@ export const AuthForm = ({ type }: { type: string }) => {
const onSubmit = async (data: z.infer) => {
if (type === "sign-in") {
+ setIsLoading(true);
dispatch({
email: data.email.toLowerCase(),
password: data.password,
});
+ setIsLoading(false);
}
if (type === "sign-up") {
+ setIsLoading(true);
const newUser = await createNewUser(data);
+ setIsLoading(false);
if (!newUser.errors) {
router.push("/sign-in");
@@ -291,7 +292,7 @@ export const AuthForm = ({ type }: { type: string }) => {
className="relative hidden w-1/2 flex-col-reverse rounded-medium p-10 shadow-small lg:flex"
style={{
backgroundImage:
- "url(https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/white-building.jpg)",
+ "url(https://media.licdn.com/dms/image/v2/D5622AQFnGdly6BE-Qw/feedshare-shrink_1280/feedshare-shrink_1280/0/1725548764361?e=1730937600&v=beta&t=A2VLwDFbjWqOgzCtsF58GkasH-eUC9uqP9rY2UI9B9A)",
backgroundSize: "cover",
backgroundPosition: "center",
}}
diff --git a/components/auth/index.ts b/components/auth/oss/index.ts
similarity index 100%
rename from components/auth/index.ts
rename to components/auth/oss/index.ts
From 6b7fe81cf84c3e0cb561609e695c05eafe5f6873 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Mon, 7 Oct 2024 17:30:31 +0200
Subject: [PATCH 25/26] chore: tweak styles auth pages
---
actions/providers/providers.ts | 11 +++++++----
components/auth/oss/auth-form.tsx | 4 ++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/actions/providers/providers.ts b/actions/providers/providers.ts
index 8c67196f92..200bc79bd6 100644
--- a/actions/providers/providers.ts
+++ b/actions/providers/providers.ts
@@ -48,7 +48,7 @@ export const getProviders = async ({
};
export const getProvider = async (formData: FormData) => {
- // const session = await auth();
+ const session = await auth();
const providerId = formData.get("id");
const keyServer = process.env.API_BASE_URL;
@@ -58,6 +58,7 @@ export const getProvider = async (formData: FormData) => {
const providers = await fetch(url.toString(), {
headers: {
Accept: "application/vnd.api+json",
+ Authorization: `Bearer ${session?.accessToken}`,
},
});
const data = await providers.json();
@@ -71,7 +72,7 @@ export const getProvider = async (formData: FormData) => {
};
export const updateProvider = async (formData: FormData) => {
- // const session = await auth();
+ const session = await auth();
const keyServer = process.env.API_BASE_URL;
const providerId = formData.get("providerId");
@@ -85,6 +86,7 @@ export const updateProvider = async (formData: FormData) => {
headers: {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
+ Authorization: `Bearer ${session?.accessToken}`,
},
body: JSON.stringify({
data: {
@@ -173,7 +175,7 @@ export const checkConnectionProvider = async (formData: FormData) => {
};
export const deleteProvider = async (formData: FormData) => {
- // const session = await auth();
+ const session = await auth();
const keyServer = process.env.API_BASE_URL;
const providerId = formData.get("id");
@@ -184,6 +186,7 @@ export const deleteProvider = async (formData: FormData) => {
method: "DELETE",
headers: {
Accept: "application/vnd.api+json",
+ Authorization: `Bearer ${session?.accessToken}`,
},
});
const data = await response.json();
@@ -196,7 +199,7 @@ export const deleteProvider = async (formData: FormData) => {
}
};
-export const getErrorMessage = (error: unknown): string => {
+export const getErrorMessage = async (error: unknown): Promise => {
let message: string;
if (error instanceof Error) {
diff --git a/components/auth/oss/auth-form.tsx b/components/auth/oss/auth-form.tsx
index 1807fcce19..3b70a5a040 100644
--- a/components/auth/oss/auth-form.tsx
+++ b/components/auth/oss/auth-form.tsx
@@ -292,14 +292,14 @@ export const AuthForm = ({ type }: { type: string }) => {
className="relative hidden w-1/2 flex-col-reverse rounded-medium p-10 shadow-small lg:flex"
style={{
backgroundImage:
- "url(https://media.licdn.com/dms/image/v2/D5622AQFnGdly6BE-Qw/feedshare-shrink_1280/feedshare-shrink_1280/0/1725548764361?e=1730937600&v=beta&t=A2VLwDFbjWqOgzCtsF58GkasH-eUC9uqP9rY2UI9B9A)",
+ "url(https://nextuipro.nyc3.cdn.digitaloceanspaces.com/components-images/white-building.jpg)",
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
- Open Source Security Platform
+ Open Cloud Security Platform
From 7572136cc8a2cbdba107d8f526a4213889e85c46 Mon Sep 17 00:00:00 2001
From: Pablo Lara
Date: Tue, 8 Oct 2024 08:46:35 +0200
Subject: [PATCH 26/26] feat: sign-up and sign-in pages are styled and ready to
be merged
---
app/(prowler)/profile/page.tsx | 2 +-
app/(prowler)/providers/page.tsx | 2 +-
auth.config.ts | 12 ++++-----
components/auth/oss/auth-form.tsx | 30 ++++++----------------
components/filters/data-filters.ts | 5 ----
tailwind.config.js | 40 ++++++++++++++----------------
6 files changed, 35 insertions(+), 56 deletions(-)
diff --git a/app/(prowler)/profile/page.tsx b/app/(prowler)/profile/page.tsx
index 91fa4907f0..0cfb4f8ab2 100644
--- a/app/(prowler)/profile/page.tsx
+++ b/app/(prowler)/profile/page.tsx
@@ -15,7 +15,7 @@ export default async function Profile() {
}
// const user = await getUserByMe();
- // console.log("user", user);
+
return (
<>
diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx
index a5cb092664..b999671859 100644
--- a/app/(prowler)/providers/page.tsx
+++ b/app/(prowler)/providers/page.tsx
@@ -24,7 +24,7 @@ export default async function Providers({
-
+
diff --git a/auth.config.ts b/auth.config.ts
index 556c7d40c0..4639f8bf37 100644
--- a/auth.config.ts
+++ b/auth.config.ts
@@ -124,7 +124,7 @@ export const authConfig = {
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;
@@ -150,11 +150,11 @@ export const authConfig = {
}
// eslint-disable-next-line no-console
- console.log(
- "Access token expires",
- token.accessTokenExpires,
- new Date(Number(token.accessTokenExpires)),
- );
+ // console.log(
+ // "Access token expires",
+ // token.accessTokenExpires,
+ // new Date(Number(token.accessTokenExpires)),
+ // );
// If the access token is not expired, return the token
if (
diff --git a/components/auth/oss/auth-form.tsx b/components/auth/oss/auth-form.tsx
index 3b70a5a040..451e043c0b 100644
--- a/components/auth/oss/auth-form.tsx
+++ b/components/auth/oss/auth-form.tsx
@@ -112,13 +112,15 @@ export const AuthForm = ({ type }: { type: string }) => {
return (
{/* Auth Form */}
-
- {/* Prowler Logo */}
-
+
+ {/* Background Pattern */}
+
-
+
+ {/* Prowler Logo */}
+
{type === "sign-in" ? "Sign In" : "Sign Up"}
@@ -287,22 +289,6 @@ export const AuthForm = ({ type }: { type: string }) => {
)}
-
-
-
-
- Open Cloud Security Platform
-
-
-
);
};
diff --git a/components/filters/data-filters.ts b/components/filters/data-filters.ts
index 9aea8e9dd3..df1e01cea0 100644
--- a/components/filters/data-filters.ts
+++ b/components/filters/data-filters.ts
@@ -1,9 +1,4 @@
export const filtersProviders = [
- {
- key: "provider__in",
- labelCheckboxGroup: "Select a Provider",
- values: ["aws", "gcp", "azure", "kubernetes"],
- },
{
key: "connected",
labelCheckboxGroup: "Connection",
diff --git a/tailwind.config.js b/tailwind.config.js
index 608551bb96..31bdbfd39e 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -20,36 +20,34 @@ module.exports = {
extend: {
colors: {
prowler: {
- blue: {
+ theme: {
midnight: "#030921",
pale: "#f3fcff",
- smoky: "#7b8390",
- },
- grey: {
- medium: "#353a4d",
- light: "#868994",
- },
- green: {
- DEFAULT: "#9FD655",
- medium: "#09BF3D",
- },
- theme: {
green: "#6af400",
purple: "#5001d0",
coral: "#ff5356",
orange: "#f69000",
yellow: "#ffdf16",
},
- dark: {
- DEFAULT: "#0E1117",
- 700: "#151B23",
- 400: "#262C36",
- title: "#E2E8F0",
- text: "#94a3b8" /* primary default for dark mode */,
+ blue: {
+ 800: "#1e293bff",
},
- light: {
- title: "#1e293bff",
- text: "#64748b" /* primary default for light mode */,
+ grey: {
+ medium: "#353a4d",
+ light: "#868994",
+ 600: "#64748b",
+ },
+ green: {
+ DEFAULT: "#9FD655",
+ medium: "#09BF3D",
+ },
+ black: {
+ DEFAULT: "#000",
+ 900: "#18181A",
+ },
+ white: {
+ DEFAULT: "#FFF",
+ 900: "#18181A",
},
},
system: {