1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-08 23:15:24 +02:00

work on auth replacement

This commit is contained in:
Tyler Myracle 2024-01-11 21:16:43 -06:00
parent 63c39e2067
commit 3c7b461fb1
23 changed files with 726 additions and 46 deletions

View file

@ -22,4 +22,4 @@ AWS_SESSION_TOKEN=
NX_PLAID_SECRET=
NX_FINICITY_APP_KEY=
NX_FINICITY_PARTNER_SECRET=
NX_CONVERTKIT_SECRET=
NX_CONVERTKIT_SECRET=

View file

@ -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 (
<ModalManager>
<UserAccountContextProvider>
<AccountContextProvider>
{children}
const WithAuth = function ({ children }: PropsWithChildren) {
const { data: session } = useSession()
const router = useRouter()
{/* Add, edit, delete connections and manual accounts */}
<AccountsManager />
</AccountContextProvider>
</UserAccountContextProvider>
</ModalManager>
)
})
useEffect(() => {
if (!session) {
router.push('/login')
}
}, [session, router])
if (session) {
return (
<ModalManager>
<UserAccountContextProvider>
<AccountContextProvider>
{children}
{/* Add, edit, delete connections and manual accounts */}
<AccountsManager />
</AccountContextProvider>
</UserAccountContextProvider>
</ModalManager>
)
}
return null
}
export default function App({
Component: Page,
@ -72,16 +85,18 @@ export default function App({
<Analytics />
<QueryProvider>
<AuthProvider>
<AxiosProvider>
<>
<APM />
{Page.isPublic === true ? (
getLayout(<Page {...pageProps} />)
) : (
<WithAuth>{getLayout(<Page {...pageProps} />)}</WithAuth>
)}
</>
</AxiosProvider>
<SessionProvider>
<AxiosProvider>
<>
<APM />
{Page.isPublic === true ? (
getLayout(<Page {...pageProps} />)
) : (
<WithAuth>{getLayout(<Page {...pageProps} />)}</WithAuth>
)}
</>
</AxiosProvider>
</SessionProvider>
</AuthProvider>
</QueryProvider>
</ErrorBoundary>

View file

@ -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<SharedType.AuthUser>(
'/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)

View file

@ -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<HTMLFormElement>) => {
e.preventDefault()
setEmail('')
setPassword('')
await signIn('credentials', {
email,
password,
callbackUrl: '/',
})
}
// _app.tsx will automatically redirect if not authenticated
return (
<div className="absolute inset-0 flex items-center justify-center">
<LoadingSpinner />
</div>
<>
<Script
src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.4.2/zxcvbn.js"
strategy="lazyOnload"
/>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<img
className="mb-8"
src="/assets/maybe.svg"
alt="Maybe Finance Logo"
height={140}
width={140}
/>
<form className="space-y-4" onSubmit={onSubmit}>
<Input
type="text"
label="Email"
value={email}
onChange={(e) => setEmail(e.currentTarget.value)}
/>
<InputPassword
autoComplete="password"
label="Password"
value={password}
showPasswordRequirements={!isValid}
onValidityChange={(checks) => {
const passwordValid = checks.filter((c) => !c.isValid).length === 0
setIsValid(passwordValid)
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setPassword(e.target.value)
}
/>
<Button
type="submit"
disabled={!isValid}
variant={isValid ? 'primary' : 'secondary'}
>
Log in
</Button>
</form>
</div>
</>
)
}
LoginPage.getLayout = function getLayout(page: ReactElement) {
return <FullPageLayout>{page}</FullPageLayout>
}
LoginPage.isPublic = true

View file

@ -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 (
<div className="absolute inset-0 flex items-center justify-center">
<LoadingSpinner />
<div className="text-4xl font-bold text-white">THIS IS THE LOGIN PAGE</div>
<Input type="text" placeholder="Email" />
<InputPassword placeholder="Password" />
</div>
)
}
RegisterPage.getLayout = function getLayout(page: ReactElement) {
return <FullPageLayout>{page}</FullPageLayout>
}
RegisterPage.isPublic = true

View file

@ -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)

View file

@ -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<Request['user']>) {
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,

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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 {

View file

@ -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'

View file

@ -1,5 +1,6 @@
export * from './useAccountApi'
export * from './useAccountConnectionApi'
export * from './useAuthUserApi'
export * from './useFinicityApi'
export * from './useInstitutionApi'
export * from './useUserApi'

View file

@ -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<SharedType.AuthUser>(`/auth-users/${email}`)
return data
},
async update(userData: SharedType.UpdateUser) {
const { data } = await axios.put<SharedType.User>('/users', userData)
return data
},
async get() {
const { data } = await axios.get<SharedType.User>('/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<SharedType.User>('/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,
}
}

View file

@ -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<AuthUser>
delete(id: AuthUser['id']): Promise<AuthUser>
}
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<AuthUser> {
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
}
}

View file

@ -0,0 +1 @@
export * from './auth-user.service'

View file

@ -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'

View file

@ -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 ======

View file

@ -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",

View file

@ -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;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "auth_user" ADD COLUMN "password" TEXT;

View file

@ -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

187
yarn.lock
View file

@ -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"