From 3c7b461fb177d14f03d0ee8d51d8c560b982be50 Mon Sep 17 00:00:00 2001 From: Tyler Myracle Date: Thu, 11 Jan 2024 21:16:43 -0600 Subject: [PATCH] work on auth replacement --- .env.example | 2 +- apps/client/pages/_app.tsx | 65 +++--- apps/client/pages/api/auth/[...nextauth].ts | 83 ++++++++ apps/client/pages/login.tsx | 84 +++++++- apps/client/pages/register.tsx | 22 ++- apps/server/src/app/app.ts | 2 + apps/server/src/app/lib/endpoint.ts | 7 + .../server/src/app/routes/auth-user.router.ts | 38 ++++ apps/server/src/app/routes/e2e.router.ts | 1 + apps/server/src/app/routes/index.ts | 1 + .../features/src/layout/DesktopLayout.tsx | 2 - .../features/src/layout/MobileLayout.tsx | 2 - libs/client/shared/src/api/index.ts | 1 + libs/client/shared/src/api/useAuthUserApi.ts | 88 +++++++++ .../src/auth-user/auth-user.service.ts | 38 ++++ libs/server/features/src/auth-user/index.ts | 1 + libs/server/features/src/index.ts | 1 + libs/shared/src/types/user-types.ts | 9 + package.json | 3 + .../migration.sql | 79 ++++++++ .../migration.sql | 2 + prisma/schema.prisma | 54 +++++ yarn.lock | 187 +++++++++++++++++- 23 files changed, 726 insertions(+), 46 deletions(-) create mode 100644 apps/client/pages/api/auth/[...nextauth].ts create mode 100644 apps/server/src/app/routes/auth-user.router.ts create mode 100644 libs/client/shared/src/api/useAuthUserApi.ts create mode 100644 libs/server/features/src/auth-user/auth-user.service.ts create mode 100644 libs/server/features/src/auth-user/index.ts create mode 100644 prisma/migrations/20240111213125_next_auth_models/migration.sql create mode 100644 prisma/migrations/20240111213725_add_password_to_auth_user/migration.sql 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 ( -
- -
+ <> +