From b5a40d07cfe01e1eaccd03adbf62cd766b3c9d2a Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 27 Aug 2024 18:37:45 +0200 Subject: [PATCH] feat: Nextauth is working --- actions/auth.ts | 42 +- app/(auth)/layout.tsx | 10 +- app/(prowler)/profile/page.tsx | 24 + app/api/auth/[...nextauth]/route.ts | 3 + app/providers.tsx | 9 +- auth.config.ts | 83 +- components/auth/AuthButton.tsx | 3 +- components/auth/AuthForm.tsx | 53 +- components/ui/custom/CustomInput.tsx | 38 +- components/ui/form/Form.tsx | 2 +- components/ui/sidebar/SidebarWrap.tsx | 17 +- lib/seed.ts | 37 +- middleware.ts | 10 + package-lock.json | 2019 ++++++++++++++++++------- package.json | 4 +- types/auth.ts | 4 +- 16 files changed, 1728 insertions(+), 630 deletions(-) create mode 100644 app/(prowler)/profile/page.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 middleware.ts diff --git a/actions/auth.ts b/actions/auth.ts index 34e7fa36c4..3c93021a12 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -3,25 +3,47 @@ import { AuthError } from "next-auth"; import { signIn, signOut } from "@/auth.config"; +// import { authFormSchema } from "@/types"; -export async function authenticate( - prevState: string | undefined, - formData: FormData, -) { +// const formSchema = authFormSchema("sign-in"); + +const defaultValues = { + email: "", + password: "", +}; + +// Fix TS types. +export async function authenticate(prevState: any, formData: any) { try { - // await new Promise((resolve) => setTimeout(resolve, 2000)); - console.log(formData); - await signIn("credentials", formData); + await new Promise((resolve) => setTimeout(resolve, 2000)); + await signIn("credentials", { + ...formData, + redirect: false, + }); + return { + message: "Success", + }; } catch (error) { if (error instanceof AuthError) { switch (error.type) { case "CredentialsSignin": - return "Invalid credentials."; + return { + message: "Credentials error", + errors: { + ...defaultValues, + credentials: "Incorrect email or password", + }, + }; default: - return "Something went wrong."; + return { + message: "Unknown error", + errors: { + ...defaultValues, + unknown: "Unknown error", + }, + }; } } - throw error; } } diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index f1f003744e..a9fdc8c394 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -1,7 +1,9 @@ import "@/styles/globals.css"; import { Metadata, Viewport } from "next"; +import { redirect } from "next/navigation"; +import { auth } from "@/auth.config"; import { Toaster } from "@/components/ui"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; @@ -27,11 +29,17 @@ export const viewport: Viewport = { ], }; -export default function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode; }) { + const session = await auth(); + + if (session?.user) { + redirect("/"); + } + return ( diff --git a/app/(prowler)/profile/page.tsx b/app/(prowler)/profile/page.tsx new file mode 100644 index 0000000000..76c72a8800 --- /dev/null +++ b/app/(prowler)/profile/page.tsx @@ -0,0 +1,24 @@ +import { Spacer } from "@nextui-org/react"; +import { redirect } from "next/navigation"; +import React from "react"; + +import { auth } from "@/auth.config"; +import { Header } from "@/components/ui"; + +export default async function Profile() { + const session = await auth(); + + if (!session?.user) { + // redirect("/sign-in?returnTo=/profile"); + redirect("/sign-in"); + } + + return ( + <> +
+ + +
{JSON.stringify(session.user, null, 2)}
+ + ); +} diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000000..316cf49c14 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/auth.config"; + +export const { GET, POST } = handlers; diff --git a/app/providers.tsx b/app/providers.tsx index 294f0647a6..942bce43dc 100644 --- a/app/providers.tsx +++ b/app/providers.tsx @@ -2,6 +2,7 @@ import { NextUIProvider } from "@nextui-org/system"; import { useRouter } from "next/navigation"; +import { SessionProvider } from "next-auth/react"; import { ThemeProvider as NextThemesProvider } from "next-themes"; import { ThemeProviderProps } from "next-themes/dist/types"; import * as React from "react"; @@ -15,8 +16,10 @@ export function Providers({ children, themeProps }: ProvidersProps) { const router = useRouter(); return ( - - {children} - + + + {children} + + ); } diff --git a/auth.config.ts b/auth.config.ts index 1171b2ae42..587b64197f 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -3,39 +3,86 @@ import NextAuth, { type NextAuthConfig } from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { z } from "zod"; +import { userMockData } from "./lib"; + +async function getUser(email: string, password: string): Promise { + // Check if the user exists in the userMockData array. + const user = userMockData.find((user) => user.email === email); + if (!user) return null; + + if (!bcryptjs.compareSync(password, user.password)) return null; + + return { + id: user.id, + name: user.name, + companyName: user.companyName, + email: user.email, + role: user.role, + image: user.image, + }; +} + export const authConfig = { + session: { + strategy: "jwt", + }, pages: { signIn: "/sign-in", newUser: "/sign-up", }, + callbacks: { + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + // console.log({ auth }, "llegas o no"); + 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) { + token.data = user; + } + return token; + }, + + session({ session, token }) { + session.user = token.data as any; + return session; + }, + }, 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(6) }) + .object({ + email: z.string().email(), + password: z.string().min(6), + }) .safeParse(credentials); - if (!parsedCredentials.success) return null; - + if (!parsedCredentials.success) { + return null; + } const { email, password } = parsedCredentials.data; - console.log({ email, password }, "from AuthConfig.ts"); - // Check the user using the email - - // const user = await getUser(email); - // if (!user) return null; - - // Compare passwords - - // if (!bcryptjs.compareSync(password, user.password)) return null; - - // Return the user object without the password field - - // const { password: _, ...rest } = user; - // return rest; + const user = await getUser(email, password); + if (!user) return null; + // console.log({ user }); + return user; }, }), ], } satisfies NextAuthConfig; -export const { signIn, signOut, auth } = NextAuth(authConfig); +export const { signIn, signOut, auth, handlers } = NextAuth(authConfig); diff --git a/components/auth/AuthButton.tsx b/components/auth/AuthButton.tsx index 821891f1b2..0eda81782a 100644 --- a/components/auth/AuthButton.tsx +++ b/components/auth/AuthButton.tsx @@ -6,10 +6,11 @@ import { useFormStatus } from "react-dom"; export const AuthButton = ({ type }: { type: string }) => { const { pending } = useFormStatus(); + return ( + ); + return ( - - - ) : null - } /> - + )} /> diff --git a/components/ui/form/Form.tsx b/components/ui/form/Form.tsx index c598b08b8f..0e6296cfd4 100644 --- a/components/ui/form/Form.tsx +++ b/components/ui/form/Form.tsx @@ -14,7 +14,7 @@ import { import { cn } from "@/lib/utils"; -import { Label } from "./label"; +import { Label } from "./Label"; const Form = FormProvider; diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 57243d92de..fe35f5f1c5 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -3,7 +3,9 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; 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 { useMediaQuery } from "usehooks-ts"; @@ -21,6 +23,8 @@ import { UserAvatar } from "./UserAvatar"; export const SidebarWrap = () => { const pathname = usePathname(); + const { data: session } = useSession(); + const [isCollapsed, setIsCollapsed] = useLocalStorage("isCollapsed", false); const isMobile = useMediaQuery("(max-width: 768px)"); @@ -65,11 +69,13 @@ export const SidebarWrap = () => { - + + {" "} + { )} +