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 (
-
-
-
+ <>
+
+
+

+
+
+ >
)
}
+
+LoginPage.getLayout = function getLayout(page: ReactElement) {
+ return {page}
+}
+
+LoginPage.isPublic = true
diff --git a/apps/client/pages/register.tsx b/apps/client/pages/register.tsx
index fbf2be5e..ac1e3f1d 100644
--- a/apps/client/pages/register.tsx
+++ b/apps/client/pages/register.tsx
@@ -1,20 +1,30 @@
-import { useAuth0 } from '@auth0/auth0-react'
-import { LoadingSpinner } from '@maybe-finance/design-system'
+import type { ReactElement } from 'react'
+import { Input, InputPassword } from '@maybe-finance/design-system'
+import { FullPageLayout } from '@maybe-finance/client/features'
+import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
export default function RegisterPage() {
- const { isAuthenticated } = useAuth0()
+ const { data: session } = useSession()
const router = useRouter()
useEffect(() => {
- if (isAuthenticated) router.push('/')
- }, [isAuthenticated, router])
+ if (session) router.push('/')
+ }, [session, router])
// _app.tsx will automatically redirect if not authenticated
return (
-
+
THIS IS THE LOGIN PAGE
+
+
)
}
+
+RegisterPage.getLayout = function getLayout(page: ReactElement) {
+ return {page}
+}
+
+RegisterPage.isPublic = true
diff --git a/apps/server/src/app/app.ts b/apps/server/src/app/app.ts
index e50f0e16..f825a845 100644
--- a/apps/server/src/app/app.ts
+++ b/apps/server/src/app/app.ts
@@ -28,6 +28,7 @@ import {
} from './middleware'
import {
usersRouter,
+ authUserRouter,
accountsRouter,
connectionsRouter,
adminRouter,
@@ -149,6 +150,7 @@ app.use('/tools', devOnly, toolsRouter)
app.use('/v1', webhooksRouter)
app.use('/v1', publicRouter)
+app.use('/v1/auth-users', authUserRouter)
// All routes AFTER this line are protected via OAuth
app.use('/v1', validateAuth0Jwt)
diff --git a/apps/server/src/app/lib/endpoint.ts b/apps/server/src/app/lib/endpoint.ts
index cb02b1b3..17265894 100644
--- a/apps/server/src/app/lib/endpoint.ts
+++ b/apps/server/src/app/lib/endpoint.ts
@@ -24,6 +24,7 @@ import Redis from 'ioredis'
import {
AccountService,
AccountConnectionService,
+ AuthUserService,
UserService,
EmailService,
AccountQueryService,
@@ -208,6 +209,10 @@ const accountService = new AccountService(
balanceSyncStrategyFactory
)
+// auth-user
+
+const authUserService = new AuthUserService(logger.child({ service: 'AuthUserService' }), prisma)
+
// user
const userService = new UserService(
@@ -291,6 +296,7 @@ async function getCurrentUser(jwt: NonNullable) {
where: { auth0Id: jwt.sub },
create: {
auth0Id: jwt.sub,
+ authId: jwt.sub,
email: jwt['https://maybe.co/email'],
picture: jwt[SharedType.Auth0CustomNamespace.Picture],
firstName: jwt[SharedType.Auth0CustomNamespace.UserMetadata]?.['firstName'],
@@ -326,6 +332,7 @@ export async function createContext(req: Request) {
transactionService,
holdingService,
accountConnectionService,
+ authUserService,
userService,
valuationService,
institutionService,
diff --git a/apps/server/src/app/routes/auth-user.router.ts b/apps/server/src/app/routes/auth-user.router.ts
new file mode 100644
index 00000000..322d32ba
--- /dev/null
+++ b/apps/server/src/app/routes/auth-user.router.ts
@@ -0,0 +1,38 @@
+import { Router } from 'express'
+import { z } from 'zod'
+import endpoint from '../lib/endpoint'
+
+const router = Router()
+
+router.get(
+ '/',
+ endpoint.create({
+ resolve: async ({ ctx, req }) => {
+ const email = req.query.email
+ console.log('Going to get the user for email', email)
+ const user = await ctx.authUserService.getByEmail(email as string)
+ console.log('Got the user', user)
+ if (user) return user
+ console.log('No user found')
+ return { data: null }
+ },
+ })
+)
+
+router.post(
+ '/',
+ endpoint.create({
+ input: z.object({
+ email: z.string().email(),
+ password: z.string().min(6),
+ }),
+ resolve: async ({ input, ctx }) => {
+ return await ctx.authUserService.create({
+ email: input.email,
+ password: input.password,
+ })
+ },
+ })
+)
+
+export default router
diff --git a/apps/server/src/app/routes/e2e.router.ts b/apps/server/src/app/routes/e2e.router.ts
index 31004185..5a98db21 100644
--- a/apps/server/src/app/routes/e2e.router.ts
+++ b/apps/server/src/app/routes/e2e.router.ts
@@ -54,6 +54,7 @@ router.post(
ctx.prisma.user.create({
data: {
auth0Id: ctx.user!.auth0Id,
+ authId: ctx.user!.authId,
email: 'REPLACE_THIS',
dob: new Date('1990-01-01'),
linkAccountDismissedAt: new Date(), // ensures our auto-account link doesn't trigger
diff --git a/apps/server/src/app/routes/index.ts b/apps/server/src/app/routes/index.ts
index fd988bc3..94c879fa 100644
--- a/apps/server/src/app/routes/index.ts
+++ b/apps/server/src/app/routes/index.ts
@@ -2,6 +2,7 @@ export { default as accountsRouter } from './accounts.router'
export { default as accountRollupRouter } from './account-rollup.router'
export { default as connectionsRouter } from './connections.router'
export { default as usersRouter } from './users.router'
+export { default as authUserRouter } from './auth-user.router'
export { default as webhooksRouter } from './webhooks.router'
export { default as plaidRouter } from './plaid.router'
export { default as finicityRouter } from './finicity.router'
diff --git a/libs/client/features/src/layout/DesktopLayout.tsx b/libs/client/features/src/layout/DesktopLayout.tsx
index 1af64799..cee54604 100644
--- a/libs/client/features/src/layout/DesktopLayout.tsx
+++ b/libs/client/features/src/layout/DesktopLayout.tsx
@@ -19,13 +19,11 @@ import {
RiMore2Fill,
RiPieChart2Line,
RiFlagLine,
- RiChatPollLine,
RiArrowRightSLine,
} from 'react-icons/ri'
import { Button, Tooltip } from '@maybe-finance/design-system'
import { useAuth0 } from '@auth0/auth0-react'
import { MenuPopover } from './MenuPopover'
-import { UpgradePrompt } from '../user-billing'
import { SidebarOnboarding } from '../onboarding'
export interface DesktopLayoutProps {
diff --git a/libs/client/features/src/layout/MobileLayout.tsx b/libs/client/features/src/layout/MobileLayout.tsx
index f2904cf3..206df618 100644
--- a/libs/client/features/src/layout/MobileLayout.tsx
+++ b/libs/client/features/src/layout/MobileLayout.tsx
@@ -1,7 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { motion } from 'framer-motion'
import {
- RiChatPollLine,
RiCloseLine,
RiFlagLine,
RiFolderOpenLine,
@@ -15,7 +14,6 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { ProfileCircle } from '@maybe-finance/client/shared'
import { usePopoutContext, LayoutContextProvider } from '@maybe-finance/client/shared'
-import { UpgradePrompt } from '../user-billing'
import classNames from 'classnames'
import type { IconType } from 'react-icons'
diff --git a/libs/client/shared/src/api/index.ts b/libs/client/shared/src/api/index.ts
index 2601e1e1..0eb1a782 100644
--- a/libs/client/shared/src/api/index.ts
+++ b/libs/client/shared/src/api/index.ts
@@ -1,5 +1,6 @@
export * from './useAccountApi'
export * from './useAccountConnectionApi'
+export * from './useAuthUserApi'
export * from './useFinicityApi'
export * from './useInstitutionApi'
export * from './useUserApi'
diff --git a/libs/client/shared/src/api/useAuthUserApi.ts b/libs/client/shared/src/api/useAuthUserApi.ts
new file mode 100644
index 00000000..5eb994c1
--- /dev/null
+++ b/libs/client/shared/src/api/useAuthUserApi.ts
@@ -0,0 +1,88 @@
+import type { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query'
+import type { SharedType } from '@maybe-finance/shared'
+import type { AxiosInstance } from 'axios'
+import Axios from 'axios'
+import * as Sentry from '@sentry/react'
+import { useMemo } from 'react'
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
+import { toast } from 'react-hot-toast'
+import { DateTime } from 'luxon'
+import { useAxiosWithAuth } from '../hooks/useAxiosWithAuth'
+
+const AuthUserApi = (axios: AxiosInstance) => ({
+ async getByEmail(email: string) {
+ const { data } = await axios.get(`/auth-users/${email}`)
+ return data
+ },
+
+ async update(userData: SharedType.UpdateUser) {
+ const { data } = await axios.put('/users', userData)
+ return data
+ },
+
+ async get() {
+ const { data } = await axios.get('/users')
+ return data
+ },
+
+ async delete() {
+ return axios.delete('/users', { data: { confirm: true } })
+ },
+
+ async updateOnboarding(input: {
+ flow: SharedType.OnboardingFlow
+ updates: { key: string; markedComplete: boolean }[]
+ markedComplete?: boolean
+ }) {
+ const { data } = await axios.put('/users/onboarding', input)
+ return data
+ },
+
+ async changePassword(newPassword: SharedType.PasswordReset) {
+ const { data } = await axios.put<
+ SharedType.PasswordReset,
+ SharedType.ApiResponse<{ success: boolean; error?: string }>
+ >('/users/change-password', newPassword)
+ return data
+ },
+})
+
+const staleTimes = {
+ user: 30_000,
+ netWorth: 30_000,
+ insights: 30_000,
+}
+
+export function useAuthUserApi() {
+ const queryClient = useQueryClient()
+ const { axios } = useAxiosWithAuth()
+ const api = useMemo(() => AuthUserApi(axios), [axios])
+
+ const useGetByEmail = (email: string) =>
+ useQuery(['auth-users', email], () => api.getByEmail(email), { staleTime: staleTimes.user })
+
+ const useUpdateOnboarding = (
+ options?: UseMutationOptions<
+ SharedType.User,
+ unknown,
+ {
+ flow: SharedType.OnboardingFlow
+ updates: { key: string; markedComplete: boolean }[]
+ markedComplete?: boolean
+ }
+ >
+ ) =>
+ useMutation(api.updateOnboarding, {
+ onSettled: () => queryClient.invalidateQueries(['users', 'onboarding']),
+ ...options,
+ })
+
+ const useDelete = (options?: UseMutationOptions<{}, unknown, any>) =>
+ useMutation(api.delete, options)
+
+ return {
+ useGetByEmail,
+ useUpdateOnboarding,
+ useDelete,
+ }
+}
diff --git a/libs/server/features/src/auth-user/auth-user.service.ts b/libs/server/features/src/auth-user/auth-user.service.ts
new file mode 100644
index 00000000..7b8efe07
--- /dev/null
+++ b/libs/server/features/src/auth-user/auth-user.service.ts
@@ -0,0 +1,38 @@
+import type { AuthUser, PrismaClient, Prisma } from '@prisma/client'
+import type { Logger } from 'winston'
+
+export interface IAuthUserService {
+ get(id: AuthUser['id']): Promise
+ delete(id: AuthUser['id']): Promise
+}
+
+export class AuthUserService implements IAuthUserService {
+ constructor(private readonly logger: Logger, private readonly prisma: PrismaClient) {}
+
+ async get(id: AuthUser['id']) {
+ return await this.prisma.authUser.findUniqueOrThrow({
+ where: { id },
+ })
+ }
+
+ async getByEmail(email: AuthUser['email']) {
+ if (!email) throw new Error('No email provided')
+ return await this.prisma.authUser.findUnique({
+ where: { email },
+ })
+ }
+
+ async create(data: Prisma.AuthUserCreateInput): Promise {
+ const user = await this.prisma.authUser.create({ data: { ...data } })
+ return user
+ }
+
+ async delete(id: AuthUser['id']) {
+ const authUser = await this.get(id)
+
+ // Delete user from prisma
+ this.logger.info(`Removing user ${authUser.id} from Prisma`)
+ const user = await this.prisma.authUser.delete({ where: { id } })
+ return user
+ }
+}
diff --git a/libs/server/features/src/auth-user/index.ts b/libs/server/features/src/auth-user/index.ts
new file mode 100644
index 00000000..b1075bc3
--- /dev/null
+++ b/libs/server/features/src/auth-user/index.ts
@@ -0,0 +1 @@
+export * from './auth-user.service'
diff --git a/libs/server/features/src/index.ts b/libs/server/features/src/index.ts
index ab7e0196..4fa66d75 100644
--- a/libs/server/features/src/index.ts
+++ b/libs/server/features/src/index.ts
@@ -4,6 +4,7 @@ export * from './account-balance'
export * from './email'
export * from './institution'
export * from './security-pricing'
+export * from './auth-user'
export * from './user'
export * from './valuation'
export * from './providers'
diff --git a/libs/shared/src/types/user-types.ts b/libs/shared/src/types/user-types.ts
index b26c63d8..f2db2fd8 100644
--- a/libs/shared/src/types/user-types.ts
+++ b/libs/shared/src/types/user-types.ts
@@ -8,6 +8,7 @@ import type {
Prisma,
Security,
User as PrismaUser,
+ AuthUser,
} from '@prisma/client'
import type { Institution } from 'plaid'
import type { TimeSeries, TimeSeriesResponseWithDetail, Trend } from './general-types'
@@ -28,6 +29,14 @@ export type UpdateUser = Partial<
}
>
+/**
+ * ================================================================
+ * ====== Auth User ======
+ * ================================================================
+ */
+
+export type { AuthUser }
+
/**
* ================================================================
* ====== Net Worth ======
diff --git a/package.json b/package.json
index 129f4d67..780779ef 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
},
"private": true,
"dependencies": {
+ "@auth/prisma-adapter": "^1.0.14",
"@auth0/auth0-react": "^2.0.0",
"@auth0/nextjs-auth0": "^2.0.0",
"@aws-sdk/client-s3": "^3.231.0",
@@ -98,6 +99,7 @@
"auth0-deploy-cli": "^7.15.1",
"autoprefixer": "10.4.13",
"axios": "^0.26.1",
+ "bcrypt": "^5.1.1",
"bull": "^4.10.2",
"classnames": "^2.3.1",
"core-js": "^3.6.5",
@@ -127,6 +129,7 @@
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"next": "13.1.1",
+ "next-auth": "^4.24.5",
"pg": "^8.8.0",
"plaid": "^12.1.0",
"postcss": "8.4.19",
diff --git a/prisma/migrations/20240111213125_next_auth_models/migration.sql b/prisma/migrations/20240111213125_next_auth_models/migration.sql
new file mode 100644
index 00000000..26719743
--- /dev/null
+++ b/prisma/migrations/20240111213125_next_auth_models/migration.sql
@@ -0,0 +1,79 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[auth_id]` on the table `user` will be added. If there are existing duplicate values, this will fail.
+ - Added the required column `auth_id` to the `user` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE "user" ADD COLUMN "auth_id" TEXT NOT NULL;
+
+-- CreateTable
+CREATE TABLE "auth_account" (
+ "id" TEXT NOT NULL,
+ "user_id" TEXT NOT NULL,
+ "type" TEXT NOT NULL,
+ "provider" TEXT NOT NULL,
+ "provider_account_id" TEXT NOT NULL,
+ "refresh_token" TEXT,
+ "access_token" TEXT,
+ "expires_at" INTEGER,
+ "token_type" TEXT,
+ "scope" TEXT,
+ "id_token" TEXT,
+ "session_state" TEXT,
+
+ CONSTRAINT "auth_account_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "auth_user" (
+ "id" TEXT NOT NULL,
+ "name" TEXT,
+ "email" TEXT,
+ "email_verified" TIMESTAMP(3),
+ "image" TEXT,
+
+ CONSTRAINT "auth_user_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "auth_session" (
+ "id" TEXT NOT NULL,
+ "session_token" TEXT NOT NULL,
+ "user_id" TEXT NOT NULL,
+ "expires" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "auth_session_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "auth_verification_token" (
+ "identifier" TEXT NOT NULL,
+ "token" TEXT NOT NULL,
+ "expires" TIMESTAMP(3) NOT NULL
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "auth_account_provider_provider_account_id_key" ON "auth_account"("provider", "provider_account_id");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "auth_user_email_key" ON "auth_user"("email");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "auth_session_session_token_key" ON "auth_session"("session_token");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "auth_verification_token_token_key" ON "auth_verification_token"("token");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "auth_verification_token_identifier_token_key" ON "auth_verification_token"("identifier", "token");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "user_auth_id_key" ON "user"("auth_id");
+
+-- AddForeignKey
+ALTER TABLE "auth_account" ADD CONSTRAINT "auth_account_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "auth_session" ADD CONSTRAINT "auth_session_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/migrations/20240111213725_add_password_to_auth_user/migration.sql b/prisma/migrations/20240111213725_add_password_to_auth_user/migration.sql
new file mode 100644
index 00000000..846bae97
--- /dev/null
+++ b/prisma/migrations/20240111213725_add_password_to_auth_user/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "auth_user" ADD COLUMN "password" TEXT;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 775b7ebc..32c2bdb0 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -396,6 +396,7 @@ model User {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
auth0Id String @unique @map("auth0_id")
+ authId String @unique @map("auth_id") // NextAuth user id
// profile
email String @db.Citext
@@ -564,6 +565,59 @@ model PlanMilestone {
@@map("plan_milestone")
}
+// Next Auth Models
+model AuthAccount {
+ id String @id @default(cuid())
+ userId String @map("user_id")
+ type String
+ provider String
+ providerAccountId String @map("provider_account_id")
+ refresh_token String? @db.Text
+ access_token String? @db.Text
+ expires_at Int?
+ token_type String?
+ scope String?
+ id_token String? @db.Text
+ session_state String?
+
+ user AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@unique([provider, providerAccountId])
+ @@map("auth_account")
+}
+
+model AuthUser {
+ id String @id @default(cuid())
+ name String?
+ email String? @unique
+ emailVerified DateTime? @map("email_verified")
+ password String?
+ image String?
+ accounts AuthAccount[]
+ sessions AuthSession[]
+
+ @@map("auth_user")
+}
+
+model AuthSession {
+ id String @id @default(cuid())
+ sessionToken String @unique @map("session_token")
+ userId String @map("user_id")
+ expires DateTime
+ user AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@map("auth_session")
+}
+
+model AuthVerificationToken {
+ identifier String
+ token String @unique
+ expires DateTime
+
+ @@unique([identifier, token])
+ @@map("auth_verification_token")
+}
+
enum ApprovalStatus {
pending
approved
diff --git a/yarn.lock b/yarn.lock
index 245956c6..5450e8d4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9,6 +9,26 @@
dependencies:
"@jridgewell/trace-mapping" "^0.3.0"
+"@auth/core@0.20.0":
+ version "0.20.0"
+ resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.20.0.tgz#18b706b9708973b1fd4cb6aac5ef47d89201f925"
+ integrity sha512-04lQH58H5d/9xQ63MOTDTOC7sXWYlr/RhJ97wfFLXzll7nYyCKbkrT3ZMdzdLC5O+qt90sQDK85TAtLlcZ2WBg==
+ dependencies:
+ "@panva/hkdf" "^1.1.1"
+ "@types/cookie" "0.6.0"
+ cookie "0.6.0"
+ jose "^5.1.3"
+ oauth4webapi "^2.4.0"
+ preact "10.11.3"
+ preact-render-to-string "5.2.3"
+
+"@auth/prisma-adapter@^1.0.14":
+ version "1.0.14"
+ resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-1.0.14.tgz#3b46f86beec618ab5d6756fdd1b520535cf010ac"
+ integrity sha512-7urwnDT+K81SocU0SbfY/vtY/NbXgj8/AU2k6Ek8waHT/7YPLsOQnXQsTWROmolFshNVkt2kq9Z/HOVnRdHrkQ==
+ dependencies:
+ "@auth/core" "0.20.0"
+
"@auth0/auth0-react@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-2.0.0.tgz#74e4d3662896e71dd95cca70b395715825da3b4e"
@@ -2734,6 +2754,13 @@
dependencies:
regenerator-runtime "^0.13.10"
+"@babel/runtime@^7.20.13":
+ version "7.23.8"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
+ integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
"@babel/template@^7.12.7", "@babel/template@^7.16.7", "@babel/template@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31"
@@ -3432,6 +3459,21 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
+"@mapbox/node-pre-gyp@^1.0.11":
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
+ integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
+ dependencies:
+ detect-libc "^2.0.0"
+ https-proxy-agent "^5.0.0"
+ make-dir "^3.1.0"
+ node-fetch "^2.6.7"
+ nopt "^5.0.0"
+ npmlog "^5.0.1"
+ rimraf "^3.0.2"
+ semver "^7.3.5"
+ tar "^6.1.11"
+
"@mdx-js/mdx@^1.6.22":
version "1.6.22"
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba"
@@ -3966,6 +4008,11 @@
resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.0.2.tgz#bab0f09d09de9fd83628220d496627681bc440d6"
integrity sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==
+"@panva/hkdf@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d"
+ integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==
+
"@parcel/watcher@2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b"
@@ -5717,6 +5764,11 @@
dependencies:
"@types/node" "*"
+"@types/cookie@0.6.0":
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
+ integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==
+
"@types/cors@^2.8.12":
version "2.8.12"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
@@ -7154,6 +7206,11 @@ abab@^2.0.6:
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
+abbrev@1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+ integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
accepts@~1.3.4:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -8138,6 +8195,14 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
+bcrypt@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2"
+ integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==
+ dependencies:
+ "@mapbox/node-pre-gyp" "^1.0.11"
+ node-addon-api "^5.0.0"
+
bcryptjs@^2.3.0:
version "2.4.3"
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
@@ -9362,6 +9427,11 @@ cookie@0.5.0, cookie@^0.5.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+cookie@0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
+ integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
+
cookie@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
@@ -10196,6 +10266,11 @@ detab@2.0.4:
dependencies:
repeat-string "^1.5.4"
+detect-libc@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d"
+ integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==
+
detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -14393,6 +14468,16 @@ jose@^4.10.3:
resolved "https://registry.yarnpkg.com/jose/-/jose-4.11.0.tgz#1c7f5c7806383d3e836434e8f49da531cb046a9d"
integrity sha512-wLe+lJHeG8Xt6uEubS4x0LVjS/3kXXu9dGoj9BNnlhYq7Kts0Pbb2pvv5KiI0yaKH/eaiR0LUOBhOVo9ktd05A==
+jose@^4.11.4, jose@^4.15.4:
+ version "4.15.4"
+ resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.4.tgz#02a9a763803e3872cf55f29ecef0dfdcc218cc03"
+ integrity sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==
+
+jose@^5.1.3:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/jose/-/jose-5.2.0.tgz#d0ffd7f7e31253f633eefb190a930cd14a916995"
+ integrity sha512-oW3PCnvyrcm1HMvGTzqjxxfnEs9EoFOFWi2HsEGhlFVOXxTE3K9GKWVMFoFw06yPUqwpvEWic1BmtUZBI/tIjw==
+
js-string-escape@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
@@ -15652,6 +15737,11 @@ minipass@^3.0.0, minipass@^3.1.1:
dependencies:
yallist "^4.0.0"
+minipass@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
+ integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
+
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
@@ -15879,6 +15969,21 @@ nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0:
resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5"
integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==
+next-auth@^4.24.5:
+ version "4.24.5"
+ resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.24.5.tgz#1fd1bfc0603c61fd2ba6fd81b976af690edbf07e"
+ integrity sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ "@panva/hkdf" "^1.0.2"
+ cookie "^0.5.0"
+ jose "^4.11.4"
+ oauth "^0.9.15"
+ openid-client "^5.4.0"
+ preact "^10.6.3"
+ preact-render-to-string "^5.1.19"
+ uuid "^8.3.2"
+
next-tick@^1.0.0, next-tick@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
@@ -15942,6 +16047,11 @@ node-addon-api@^3.2.1:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
+node-addon-api@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762"
+ integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==
+
node-dir@^0.1.10:
version "0.1.17"
resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
@@ -16010,6 +16120,13 @@ node-releases@^2.0.2:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
+nopt@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
+ integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
+ dependencies:
+ abbrev "1"
+
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -16124,6 +16241,16 @@ nx@15.5.2:
yargs "^17.6.2"
yargs-parser "21.1.1"
+oauth4webapi@^2.4.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/oauth4webapi/-/oauth4webapi-2.6.0.tgz#776a2eb5ca6ad5e5249c4bb6194516318406d254"
+ integrity sha512-4P43og0d8fQ61RMQEl9L7zwGVduuYbLED7uP99MkFSGuOUvJL1Fs52/D3tRtKoFtiSwKblScTYJI+utQn3SUDg==
+
+oauth@^0.9.15:
+ version "0.9.15"
+ resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
+ integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==
+
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1, object-assign@latest:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -16138,7 +16265,7 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
-object-hash@^2.0.1:
+object-hash@^2.0.1, object-hash@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
@@ -16297,6 +16424,11 @@ oidc-token-hash@^5.0.1:
resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6"
integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==
+oidc-token-hash@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6"
+ integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==
+
on-finished@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
@@ -16382,6 +16514,16 @@ openid-client@^5.2.1:
object-hash "^2.0.1"
oidc-token-hash "^5.0.1"
+openid-client@^5.4.0:
+ version "5.6.4"
+ resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.6.4.tgz#b2c25e6d5338ba3ce00e04341bb286798a196177"
+ integrity sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA==
+ dependencies:
+ jose "^4.15.4"
+ lru-cache "^6.0.0"
+ object-hash "^2.2.0"
+ oidc-token-hash "^5.0.3"
+
opn@latest:
version "6.0.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-6.0.0.tgz#3c5b0db676d5f97da1233d1ed42d182bc5a27d2d"
@@ -17452,11 +17594,30 @@ postmark@^3.0.14:
dependencies:
axios "^0.25.0"
-preact@^10.5.13:
+preact-render-to-string@5.2.3:
+ version "5.2.3"
+ resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz#23d17376182af720b1060d5a4099843c7fe92fe4"
+ integrity sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==
+ dependencies:
+ pretty-format "^3.8.0"
+
+preact-render-to-string@^5.1.19:
+ version "5.2.6"
+ resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz#0ff0c86cd118d30affb825193f18e92bd59d0604"
+ integrity sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==
+ dependencies:
+ pretty-format "^3.8.0"
+
+preact@10.11.3, preact@^10.5.13:
version "10.11.3"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.3.tgz#8a7e4ba19d3992c488b0785afcc0f8aa13c78d19"
integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==
+preact@^10.6.3:
+ version "10.19.3"
+ resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
+ integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
+
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -17526,6 +17687,11 @@ pretty-format@^28.1.1, pretty-format@^28.1.3:
ansi-styles "^5.0.0"
react-is "^18.0.0"
+pretty-format@^3.8.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
+ integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
+
pretty-hrtime@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
@@ -18281,6 +18447,11 @@ regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+regenerator-runtime@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
+ integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
regenerator-transform@^0.15.0:
version "0.15.0"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537"
@@ -20087,6 +20258,18 @@ tar@^6.0.2:
mkdirp "^1.0.3"
yallist "^4.0.0"
+tar@^6.1.11:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73"
+ integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==
+ dependencies:
+ chownr "^2.0.0"
+ fs-minipass "^2.0.0"
+ minipass "^5.0.0"
+ minizlib "^2.1.1"
+ mkdirp "^1.0.3"
+ yallist "^4.0.0"
+
telejson@^6.0.8:
version "6.0.8"
resolved "https://registry.yarnpkg.com/telejson/-/telejson-6.0.8.tgz#1c432db7e7a9212c1fbd941c3e5174ec385148f7"