diff --git a/.env.example b/.env.example index 6b50f4a4..347b12cb 100644 --- a/.env.example +++ b/.env.example @@ -22,4 +22,4 @@ AWS_SESSION_TOKEN= NX_PLAID_SECRET= NX_FINICITY_APP_KEY= NX_FINICITY_PARTNER_SECRET= -NX_CONVERTKIT_SECRET= \ No newline at end of file +NX_CONVERTKIT_SECRET= diff --git a/apps/client/pages/_app.tsx b/apps/client/pages/_app.tsx index 4fde4633..bdac22af 100644 --- a/apps/client/pages/_app.tsx +++ b/apps/client/pages/_app.tsx @@ -1,4 +1,4 @@ -import type { PropsWithChildren, ReactElement } from 'react' +import { useEffect, type PropsWithChildren, type ReactElement } from 'react' import type { AppProps } from 'next/app' import { ErrorBoundary } from 'react-error-boundary' import { Analytics } from '@vercel/analytics/react' @@ -16,10 +16,11 @@ import * as Sentry from '@sentry/react' import { BrowserTracing } from '@sentry/tracing' import env from '../env' import '../styles.css' -import { withAuthenticationRequired } from '@auth0/auth0-react' +import { SessionProvider, useSession } from 'next-auth/react' import ModalManager from '../components/ModalManager' import Meta from '../components/Meta' import APM from '../components/APM' +import { useRouter } from 'next/router' Sentry.init({ dsn: env.NEXT_PUBLIC_SENTRY_DSN, @@ -33,20 +34,32 @@ Sentry.init({ }) // Providers and components only relevant to a logged-in user -const WithAuth = withAuthenticationRequired(function ({ children }: PropsWithChildren) { - return ( - - - - {children} +const WithAuth = function ({ children }: PropsWithChildren) { + const { data: session } = useSession() + const router = useRouter() - {/* Add, edit, delete connections and manual accounts */} - - - - - ) -}) + useEffect(() => { + if (!session) { + router.push('/login') + } + }, [session, router]) + + if (session) { + return ( + + + + {children} + + {/* Add, edit, delete connections and manual accounts */} + + + + + ) + } + return null +} export default function App({ Component: Page, @@ -72,16 +85,18 @@ export default function App({ - - <> - - {Page.isPublic === true ? ( - getLayout() - ) : ( - {getLayout()} - )} - - + + + <> + + {Page.isPublic === true ? ( + getLayout() + ) : ( + {getLayout()} + )} + + + diff --git a/apps/client/pages/api/auth/[...nextauth].ts b/apps/client/pages/api/auth/[...nextauth].ts new file mode 100644 index 00000000..339eab48 --- /dev/null +++ b/apps/client/pages/api/auth/[...nextauth].ts @@ -0,0 +1,83 @@ +import NextAuth from 'next-auth' +import CredentialsProvider from 'next-auth/providers/credentials' +import { z } from 'zod' +import type { SharedType } from '@maybe-finance/shared' +import { PrismaClient } from '@prisma/client' +import { PrismaAdapter } from '@auth/prisma-adapter' +import axios from 'axios' +import bcrypt from 'bcrypt' + +const prisma = new PrismaClient() +axios.defaults.baseURL = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3333'}/v1` + +const authPrisma = { + account: prisma.authAccount, + user: prisma.authUser, + session: prisma.authSession, + verificationToken: prisma.authVerificationToken, +} as unknown as PrismaClient + +export const authOptions = { + adapter: PrismaAdapter(authPrisma), + secret: process.env.AUTH_SECRET || 'CHANGE_ME', + pages: { + signIn: '/login', + }, + providers: [ + CredentialsProvider({ + name: 'Credentials', + type: 'credentials', + credentials: { + email: { label: 'Email', type: 'email', placeholder: 'hello@maybe.co' }, + password: { label: 'Password', type: 'password' }, + }, + async authorize(credentials) { + console.log('inside the authorize method') + const parsedCredentials = z + .object({ + email: z.string().email(), + password: z.string().min(6), + provider: z.string().optional(), + }) + .safeParse(credentials) + + if (parsedCredentials.success) { + console.log("Credentials are valid, let's authorize") + const { email, password } = parsedCredentials.data + console.log('Here are the params', email, password) + const { data } = await axios.get(`/auth-users`, { + params: { email: email }, + headers: { 'Content-Type': 'application/json' }, + }) + + const user = data.data['json'] + + console.log('This is User', user) + + if (!user.id) { + console.log('User does not exist, creating new user') + const hashedPassword = await bcrypt.hash(password, 10) + const { data: newUser } = await axios.post( + '/auth-users', + { + email, + password: hashedPassword, + } + ) + console.log('Created new user', newUser) + if (newUser) return newUser + throw new Error('Could not create user') + } + + const passwordsMatch = await bcrypt.compare(password, user.password) + if (passwordsMatch) return user + } + + console.log('Invalid credentials') + return null + }, + }), + ], +} + +export default NextAuth(authOptions) diff --git a/apps/client/pages/login.tsx b/apps/client/pages/login.tsx index a6a393fc..4424c6f9 100644 --- a/apps/client/pages/login.tsx +++ b/apps/client/pages/login.tsx @@ -1,20 +1,88 @@ -import { useAuth0 } from '@auth0/auth0-react' -import { LoadingSpinner } from '@maybe-finance/design-system' +import type { ReactElement } from 'react' +import { useState } from 'react' +import { FullPageLayout } from '@maybe-finance/client/features' +import { Input, InputPassword, Button } from '@maybe-finance/design-system' +import { AiOutlineLoading3Quarters as LoadingIcon } from 'react-icons/ai' +import { signIn, useSession } from 'next-auth/react' import { useRouter } from 'next/router' import { useEffect } from 'react' +import Script from 'next/script' export default function LoginPage() { - const { isAuthenticated } = useAuth0() + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [isValid, setIsValid] = useState(false) + + const { data: session } = useSession() const router = useRouter() useEffect(() => { - if (isAuthenticated) router.push('/') - }, [isAuthenticated, router]) + if (session) router.push('/') + }, [session, router]) + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setEmail('') + setPassword('') + await signIn('credentials', { + email, + password, + callbackUrl: '/', + }) + } // _app.tsx will automatically redirect if not authenticated return ( -
- -
+ <> +