From 633765d4b0f52fb4219dd28cf86e4bf74a2127ee Mon Sep 17 00:00:00 2001 From: Tyler Myracle Date: Fri, 12 Jan 2024 18:34:42 -0600 Subject: [PATCH] add middleware check, refactor axios provider --- .env.example | 2 +- apps/client/components/ModalManager.tsx | 40 +-------- apps/client/pages/api/auth/[...nextauth].ts | 90 ++++++++++++++----- apps/client/pages/login.tsx | 2 +- apps/client/pages/register.tsx | 21 +++-- apps/server/src/app/app.ts | 10 +-- apps/server/src/app/lib/endpoint.ts | 12 +-- apps/server/src/app/middleware/index.ts | 1 + .../src/app/middleware/validate-auth-jwt.ts | 30 +++++++ .../server/src/app/routes/auth-user.router.ts | 33 +------ apps/server/src/app/routes/e2e.router.ts | 5 +- apps/server/src/app/routes/users.router.ts | 20 +++-- .../app/services/bull-queue-event-handler.ts | 2 +- .../shared/src/providers/AxiosProvider.tsx | 54 +++-------- .../src/auth-user/auth-user.service.ts | 6 +- .../features/src/stripe/stripe.webhook.ts | 2 +- libs/server/features/src/user/user.service.ts | 38 ++++---- libs/server/shared/src/utils/auth-utils.ts | 4 +- package.json | 2 + .../migration.sql | 11 +++ .../migration.sql | 3 + prisma/schema.prisma | 3 +- yarn.lock | 25 ++++-- 23 files changed, 220 insertions(+), 196 deletions(-) create mode 100644 apps/server/src/app/middleware/validate-auth-jwt.ts create mode 100644 prisma/migrations/20240112201750_remove_auth0id_from_user/migration.sql create mode 100644 prisma/migrations/20240112204004_add_first_last_to_authuser/migration.sql diff --git a/.env.example b/.env.example index bf094509..4f12df6d 100644 --- a/.env.example +++ b/.env.example @@ -16,7 +16,7 @@ NX_AUTH0_CLIENT_SECRET= AUTH0_DEPLOY_CLIENT_SECRET= POSTMARK_SMTP_PASS= NX_SESSION_SECRET= -AUTH_SECRET= +NEXTAUTH_SECRET= # If you want to test any code that utilizes the AWS SDK locally, add temporary STAGING credentials (can be retrieved from SSO dashboard) AWS_ACCESS_KEY_ID= diff --git a/apps/client/components/ModalManager.tsx b/apps/client/components/ModalManager.tsx index cbf7800a..f1e27d6b 100644 --- a/apps/client/components/ModalManager.tsx +++ b/apps/client/components/ModalManager.tsx @@ -2,11 +2,8 @@ import { type ModalKey, type ModalManagerAction, ModalManagerContext, - useUserApi, - useLocalStorage, } from '@maybe-finance/client/shared' -import { type PropsWithChildren, useReducer, useEffect } from 'react' -import { LinkAccountFlow } from '@maybe-finance/client/features' +import { type PropsWithChildren, useReducer } from 'react' function reducer( state: Record, @@ -24,42 +21,11 @@ function reducer( * Manages auto-prompt modals and regular modals to avoid stacking collisions */ export default function ModalManager({ children }: PropsWithChildren) { - const [accountLinkHidden, setAccountLinkHidden] = useLocalStorage('account-link-hidden', false) - - const [state, dispatch] = useReducer(reducer, { + const [, dispatch] = useReducer(reducer, { linkAuth0Accounts: { isOpen: false, props: null }, }) - const allClosed = Object.values(state).every((v) => v.isOpen === false) - - const { useAuth0Profile } = useUserApi() - const auth0Profile = useAuth0Profile() - - useEffect(() => { - const autoPrompt = auth0Profile.data?.autoPromptEnabled === true - const provider = auth0Profile.data?.suggestedIdentities?.[0]?.provider - - if (autoPrompt && provider && allClosed && !accountLinkHidden) { - dispatch({ - type: 'open', - key: 'linkAuth0Accounts', - props: { secondaryProvider: provider }, - }) - } - }, [auth0Profile.data, dispatch, allClosed, accountLinkHidden]) - return ( - - {children} - - { - dispatch({ type: 'close', key: 'linkAuth0Accounts' }) - setAccountLinkHidden(true) - }} - {...state.linkAuth0Accounts.props} - /> - + {children} ) } diff --git a/apps/client/pages/api/auth/[...nextauth].ts b/apps/client/pages/api/auth/[...nextauth].ts index 12a56c8f..1b7dd354 100644 --- a/apps/client/pages/api/auth/[...nextauth].ts +++ b/apps/client/pages/api/auth/[...nextauth].ts @@ -1,13 +1,35 @@ -import NextAuth, { type SessionStrategy } from 'next-auth' +import NextAuth from 'next-auth' +import type { SessionStrategy, NextAuthOptions } from 'next-auth' import CredentialsProvider from 'next-auth/providers/credentials' import { z } from 'zod' -import { PrismaClient } from '@prisma/client' +import { PrismaClient, type Prisma } 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` +let prismaInstance: PrismaClient | null = null + +function getPrismaInstance() { + if (!prismaInstance) { + prismaInstance = new PrismaClient() + } + return prismaInstance +} + +const prisma = getPrismaInstance() + +async function createAuthUser( + data: Prisma.AuthUserCreateInput & { firstName: string; lastName: string } +) { + const authUser = await prisma.authUser.create({ data: { ...data } }) + return authUser +} + +async function getAuthUserByEmail(email: string) { + if (!email) throw new Error('No email provided') + return await prisma.authUser.findUnique({ + where: { email }, + }) +} const authPrisma = { account: prisma.authAccount, @@ -18,62 +40,82 @@ const authPrisma = { export const authOptions = { adapter: PrismaAdapter(authPrisma), - secret: process.env.AUTH_SECRET || 'CHANGE_ME', + secret: process.env.NEXTAUTH_SECRET || 'CHANGE_ME', pages: { signIn: '/login', }, session: { strategy: 'jwt' as SessionStrategy, - maxAge: 7 * 24 * 60 * 60, // 7 Days + maxAge: 1 * 24 * 60 * 60, // 1 Day }, + providers: [ CredentialsProvider({ name: 'Credentials', type: 'credentials', credentials: { + firstName: { label: 'First name', type: 'text', placeholder: 'First name' }, + lastName: { label: 'Last name', type: 'text', placeholder: 'Last name' }, email: { label: 'Email', type: 'email', placeholder: 'hello@maybe.co' }, password: { label: 'Password', type: 'password' }, }, async authorize(credentials) { const parsedCredentials = z .object({ - name: z.string().optional(), + firstName: z.string().optional(), + lastName: z.string().optional(), email: z.string().email(), password: z.string().min(6), }) .safeParse(credentials) if (parsedCredentials.success) { - const { name, email, password } = parsedCredentials.data + const { firstName, lastName, email, password } = parsedCredentials.data - const { data } = await axios.get(`/auth-users`, { - params: { email: email }, - headers: { 'Content-Type': 'application/json' }, - }) + const authUser = await getAuthUserByEmail(email) - // TODO: use superjson to parse this more cleanly - const user = data.data['json'] - - if (!user) { + if (!authUser) { + if (!firstName || !lastName) throw new Error('First and last name required') const hashedPassword = await bcrypt.hash(password, 10) - const { data } = await axios.post('/auth-users', { - name, + const newAuthUser = await createAuthUser({ + firstName, + lastName, + name: `${firstName} ${lastName}`, email, password: hashedPassword, }) - const newUser = data.data['json'] - if (newUser) return newUser + + if (newAuthUser) return newAuthUser throw new Error('Could not create user') } - const passwordsMatch = await bcrypt.compare(password, user.password) - if (passwordsMatch) return user + const passwordsMatch = await bcrypt.compare(password, authUser.password!) + if (passwordsMatch) return authUser } return null }, }), ], -} + callbacks: { + async jwt({ token, user: authUser, account }: { token: any; user: any; account: any }) { + if (authUser && account) { + token.sub = authUser.id + token['https://maybe.co/email'] = authUser.email + token.firstName = authUser.firstName + token.lastName = authUser.lastName + } + return token + }, + async session({ session, token }: { session: any; token: any }) { + session.user = token.sub + session.sub = token.sub + session['https://maybe.co/email'] = token['https://maybe.co/email'] + session.firstName = token.firstName + session.lastName = token.lastName + return session + }, + }, +} as NextAuthOptions export default NextAuth(authOptions) diff --git a/apps/client/pages/login.tsx b/apps/client/pages/login.tsx index 4c5b1a9d..b45d1fc3 100644 --- a/apps/client/pages/login.tsx +++ b/apps/client/pages/login.tsx @@ -38,7 +38,7 @@ export default function LoginPage() { strategy="lazyOnload" />
-
+
) => { e.preventDefault() - setName('') + setFirstName('') + setLastName('') setEmail('') setPassword('') await signIn('credentials', { email, password, - name, + firstName, + lastName, redirect: false, }) } @@ -55,9 +58,15 @@ export default function RegisterPage() {
setName(e.currentTarget.value)} + label="First name" + value={firstName} + onChange={(e) => setFirstName(e.currentTarget.value)} + /> + setLastName(e.currentTarget.value)} /> ) { if (!jwt.sub) throw new Error(`jwt missing sub`) if (!jwt['https://maybe.co/email']) throw new Error(`jwt missing email`) const user = (await prisma.user.findUnique({ - where: { auth0Id: jwt.sub }, + where: { authId: jwt.sub }, })) ?? (await prisma.user.upsert({ - where: { auth0Id: jwt.sub }, + where: { authId: 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'], - lastName: jwt[SharedType.Auth0CustomNamespace.UserMetadata]?.['lastName'], + picture: jwt['picture'], + firstName: jwt['firstName'], + lastName: jwt['lastName'], }, update: {}, })) diff --git a/apps/server/src/app/middleware/index.ts b/apps/server/src/app/middleware/index.ts index 8e60cf38..28b008a7 100644 --- a/apps/server/src/app/middleware/index.ts +++ b/apps/server/src/app/middleware/index.ts @@ -3,6 +3,7 @@ export * from './error-handler' export * from './auth-error-handler' export * from './superjson' export * from './validate-auth0-jwt' +export * from './validate-auth-jwt' export * from './validate-plaid-jwt' export * from './validate-finicity-signature' export { default as maintenance } from './maintenance' diff --git a/apps/server/src/app/middleware/validate-auth-jwt.ts b/apps/server/src/app/middleware/validate-auth-jwt.ts new file mode 100644 index 00000000..8eebe1c1 --- /dev/null +++ b/apps/server/src/app/middleware/validate-auth-jwt.ts @@ -0,0 +1,30 @@ +import cookieParser from 'cookie-parser' +import { getToken } from 'next-auth/jwt' + +const SECRET = process.env.NEXTAUTH_SECRET + +export const validateAuthJwt = async (req, res, next) => { + cookieParser(SECRET)(req, res, async (err) => { + if (err) { + return res.status(500).json({ message: 'Internal Server Error' }) + } + + if (req.cookies && 'next-auth.session-token' in req.cookies) { + try { + const token = await getToken({ req, secret: SECRET }) + + if (token) { + req.user = token + return next() + } else { + return res.status(401).json({ message: 'Unauthorized' }) + } + } catch (error) { + console.error('Error in token validation', error) + return res.status(500).json({ message: 'Internal Server Error' }) + } + } else { + return res.status(401).json({ message: 'Unauthorized' }) + } + }) +} diff --git a/apps/server/src/app/routes/auth-user.router.ts b/apps/server/src/app/routes/auth-user.router.ts index a1d8a071..030e92dc 100644 --- a/apps/server/src/app/routes/auth-user.router.ts +++ b/apps/server/src/app/routes/auth-user.router.ts @@ -1,37 +1,6 @@ import { Router } from 'express' -import { z } from 'zod' -import endpoint from '../lib/endpoint' +// Placeholder router if needed const router = Router() -router.get( - '/', - endpoint.create({ - resolve: async ({ ctx, req }) => { - const email = req.query.email - const user = await ctx.authUserService.getByEmail(email as string) - if (user) return user - return null - }, - }) -) - -router.post( - '/', - endpoint.create({ - input: z.object({ - name: z.string(), - email: z.string().email(), - password: z.string().min(6), - }), - resolve: async ({ input, ctx }) => { - return await ctx.authUserService.create({ - name: input.name, - 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 5a98db21..0b4bc830 100644 --- a/apps/server/src/app/routes/e2e.router.ts +++ b/apps/server/src/app/routes/e2e.router.ts @@ -47,13 +47,12 @@ router.post( trialLapsed: z.boolean().default(false), }), resolve: async ({ ctx, input }) => { - ctx.logger.debug(`Resetting CI user ${ctx.user!.auth0Id}`) + ctx.logger.debug(`Resetting CI user ${ctx.user!.authId}`) await ctx.prisma.$transaction([ - ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth0_id=${ctx.user!.auth0Id};`, + ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth_id=${ctx.user!.authId};`, ctx.prisma.user.create({ data: { - auth0Id: ctx.user!.auth0Id, authId: ctx.user!.authId, email: 'REPLACE_THIS', dob: new Date('1990-01-01'), diff --git a/apps/server/src/app/routes/users.router.ts b/apps/server/src/app/routes/users.router.ts index 15b80f16..f009fea2 100644 --- a/apps/server/src/app/routes/users.router.ts +++ b/apps/server/src/app/routes/users.router.ts @@ -105,6 +105,7 @@ router.put( }) ) +// TODO: Remove this endpoint router.get( '/auth0-profile', endpoint.create({ @@ -122,7 +123,7 @@ router.put( }), resolve: ({ input, ctx }) => { return ctx.managementClient.updateUser( - { id: ctx.user!.auth0Id }, + { id: ctx.user!.authId }, // TODO: Remove this endpoint { user_metadata: { enrolled_mfa: input.enrolled_mfa } } ) }, @@ -276,6 +277,7 @@ router.get( }) ) +// TODO: Remove this endpoint or refactor to work with new Auth router.post( '/link-accounts', endpoint.create({ @@ -284,7 +286,7 @@ router.post( secondaryProvider: z.string(), }), resolve: async ({ input, ctx }) => { - return ctx.userService.linkAccounts(ctx.user!.auth0Id, input.secondaryProvider, { + return ctx.userService.linkAccounts(ctx.user!.authId, input.secondaryProvider, { token: input.secondaryJWT, domain: env.NX_AUTH0_CUSTOM_DOMAIN, audience: env.NX_AUTH0_AUDIENCE, @@ -293,6 +295,7 @@ router.post( }) ) +// TODO: Remove this endpoint or refactor to work with new Auth router.post( '/unlink-account', endpoint.create({ @@ -302,7 +305,7 @@ router.post( }), resolve: async ({ input, ctx }) => { return ctx.userService.unlinkAccounts( - ctx.user!.auth0Id, + ctx.user!.authId, input.secondaryAuth0Id, input.secondaryProvider as UnlinkAccountsParamsProvider ) @@ -310,19 +313,20 @@ router.post( }) ) +// TODO: Refactor this to use the Auth Id instead of Auth0 router.post( '/resend-verification-email', endpoint.create({ input: z.object({ - auth0Id: z.string().optional(), + authId: z.string().optional(), }), resolve: async ({ input, ctx }) => { - const auth0Id = input.auth0Id ?? ctx.user?.auth0Id - if (!auth0Id) throw new Error('User not found') + const authId = input.authId ?? ctx.user?.authId + if (!authId) throw new Error('User not found') - await ctx.managementClient.sendEmailVerification({ user_id: auth0Id }) + await ctx.managementClient.sendEmailVerification({ user_id: authId }) - ctx.logger.info(`Sent verification email to ${auth0Id}`) + ctx.logger.info(`Sent verification email to ${authId}`) return { success: true } }, diff --git a/apps/workers/src/app/services/bull-queue-event-handler.ts b/apps/workers/src/app/services/bull-queue-event-handler.ts index 9f80c8d1..8e1c1379 100644 --- a/apps/workers/src/app/services/bull-queue-event-handler.ts +++ b/apps/workers/src/app/services/bull-queue-event-handler.ts @@ -82,7 +82,7 @@ export class BullQueueEventHandler implements IBullQueueEventHandler { } private async getUserFromJob(job: Job) { - let user: Pick | undefined + let user: Pick | undefined try { if (job.queue.name === 'sync-account' && 'accountId' in job.data) { diff --git a/libs/client/shared/src/providers/AxiosProvider.tsx b/libs/client/shared/src/providers/AxiosProvider.tsx index 0daae1c3..46a709f1 100644 --- a/libs/client/shared/src/providers/AxiosProvider.tsx +++ b/libs/client/shared/src/providers/AxiosProvider.tsx @@ -1,11 +1,8 @@ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import type { SharedType } from '@maybe-finance/shared' import { superjson } from '@maybe-finance/shared' -import { createContext, type PropsWithChildren, useCallback, useMemo } from 'react' -import { useAuth0 } from '@auth0/auth0-react' +import { createContext, type PropsWithChildren, useMemo } from 'react' import Axios from 'axios' -import * as Sentry from '@sentry/react' -import { useRouter } from 'next/router' type CreateInstanceOptions = { getToken?: () => Promise @@ -15,7 +12,6 @@ type CreateInstanceOptions = { } export type AxiosContextValue = { - getToken: () => Promise defaultBaseUrl: string axios: AxiosInstance createInstance: (options?: CreateInstanceOptions) => AxiosInstance @@ -78,56 +74,28 @@ function createInstance(options?: CreateInstanceOptions) { */ export function AxiosProvider({ children }: PropsWithChildren) { // Rather than storing access token in localStorage (insecure), we use this method to retrieve it prior to making API calls - const { getAccessTokenSilently, isAuthenticated, loginWithRedirect } = useAuth0() - const router = useRouter() - const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3333' - const getToken = useCallback(async () => { - if (!isAuthenticated) return null - - try { - const token = await getAccessTokenSilently() - return token - } catch (err) { - const authErr = - err && typeof err === 'object' && 'error' in err && typeof err['error'] === 'string' - ? err['error'] - : null - const isRecoverable = authErr - ? [ - 'mfa_required', - 'consent_required', - 'interaction_required', - 'login_required', - ].includes(authErr) - : false - - if (isRecoverable) { - await loginWithRedirect({ appState: { returnTo: router.asPath } }) - } else { - Sentry.captureException(err) - } - - return null - } - }, [isAuthenticated, getAccessTokenSilently, loginWithRedirect, router]) - // Expose a default instance with auth, superjson, headers const defaultInstance = useMemo(() => { - const defaultHeaders = { 'Content-Type': 'application/json' } + const defaultHeaders = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Credentials': true, + } return createInstance({ - getToken, - axiosOptions: { baseURL: `${API_URL}/v1`, headers: defaultHeaders }, + axiosOptions: { + baseURL: `${API_URL}/v1`, + headers: defaultHeaders, + withCredentials: true, + }, serialize: true, deserialize: true, }) - }, [getToken, API_URL]) + }, [API_URL]) return ( { if (!user.email) throw new Error('No email found for user') const usersWithMatchingEmail = await this.auth0.getUsersByEmail(user.email) const autoPromptEnabled = user.linkAccountDismissedAt == null - const currentUser = usersWithMatchingEmail.find((u) => u.user_id === user.auth0Id) + // TODO: Update this to use new Auth + const currentUser = usersWithMatchingEmail.find((u) => u.user_id === user.authId) const primaryIdentity = currentUser?.identities?.find( (identity) => !('profileData' in identity) ) @@ -85,7 +87,7 @@ export class UserService implements IUserService { .filter( (match) => match.email_verified && - match.user_id !== user.auth0Id && + match.user_id !== user.authId && match.identities?.at(0) != null ) .map((user) => user.identities!.at(0)!) @@ -157,9 +159,10 @@ export class UserService implements IUserService { // Delete Stripe customer, ending any active subscriptions if (user.stripeCustomerId) await this.stripe.customers.del(user.stripeCustomerId) - // Delete user from Auth0 so that it cannot be accessed in a partially-purged state - this.logger.info(`Removing user ${user.id} from Auth0 (${user.auth0Id})`) - await this.auth0.deleteUser({ id: user.auth0Id }) + // Delete user from Auth so that it cannot be accessed in a partially-purged state + // TODO: Update this to use new Auth + this.logger.info(`Removing user ${user.id} from Auth (${user.authId})`) + await this.prisma.authUser.delete({ where: { id: user.authId } }) await this.purgeQueue.add('purge-user', { userId: user.id }) @@ -312,7 +315,7 @@ export class UserService implements IUserService { const user = await this.prisma.user.findUniqueOrThrow({ where: { id: userId }, select: { - auth0Id: true, + authId: true, onboarding: true, dob: true, household: true, @@ -327,10 +330,12 @@ export class UserService implements IUserService { }, }) - const auth0User = await this.auth0.getUser({ id: user.auth0Id }) + const authUser = await this.prisma.authUser.findUniqueOrThrow({ + where: { id: user.authId }, + }) - // Auth0 has this mis-typed and it comes in as a 'true' string - const email_verified = auth0User.email_verified as unknown as string | boolean + // NextAuth used DateTime for this field + const email_verified = authUser.emailVerified === null ? false : true const typedOnboarding = user.onboarding as OnboardingState | null const onboardingState = typedOnboarding @@ -341,8 +346,8 @@ export class UserService implements IUserService { { ...user, onboarding: onboardingState, - emailVerified: email_verified === true || email_verified === 'true', - isAppleIdentity: auth0User.identities?.[0].provider === 'apple', + emailVerified: email_verified, + isAppleIdentity: false, }, onboardingState.markedComplete ) @@ -543,8 +548,9 @@ export class UserService implements IUserService { return onboarding } + // TODO: Update to work with new Auth async linkAccounts( - primaryAuth0Id: User['auth0Id'], + primaryAuth0Id: User['authId'], provider: string, secondaryJWT: { token: string; domain: string; audience: string } ) { @@ -554,7 +560,7 @@ export class UserService implements IUserService { secondaryJWT.audience ) - const user = await this.prisma.user.findFirst({ where: { auth0Id: validatedJWT.auth0Id } }) + const user = await this.prisma.user.findFirst({ where: { authId: validatedJWT.authId } }) if (user?.stripePriceId) { throw new Error( @@ -563,14 +569,14 @@ export class UserService implements IUserService { } return this.auth0.linkUsers(primaryAuth0Id, { - user_id: validatedJWT.auth0Id, + user_id: validatedJWT.authId, provider, }) } async unlinkAccounts( - primaryAuth0Id: User['auth0Id'], - secondaryAuth0Id: User['auth0Id'], + primaryAuth0Id: User['authId'], + secondaryAuth0Id: User['authId'], secondaryProvider: UnlinkAccountsParamsProvider ) { const response = await this.auth0.unlinkUsers({ diff --git a/libs/server/shared/src/utils/auth-utils.ts b/libs/server/shared/src/utils/auth-utils.ts index 08d7492b..42c509c5 100644 --- a/libs/server/shared/src/utils/auth-utils.ts +++ b/libs/server/shared/src/utils/auth-utils.ts @@ -13,7 +13,7 @@ export async function validateRS256JWT( domain: string, audience: string ): Promise<{ - auth0Id: string + authId: string userMetadata: SharedType.MaybeUserMetadata appMetadata: SharedType.MaybeAppMetadata }> { @@ -50,7 +50,7 @@ export async function validateRS256JWT( if (typeof payload !== 'object') return reject('payload not an object') resolve({ - auth0Id: payload.sub!, + authId: payload.sub!, appMetadata: payload[SharedType.Auth0CustomNamespace.AppMetadata] ?? {}, userMetadata: payload[SharedType.Auth0CustomNamespace.UserMetadata] ?? {}, }) diff --git a/package.json b/package.json index 780779ef..cd72e456 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "bcrypt": "^5.1.1", "bull": "^4.10.2", "classnames": "^2.3.1", + "cookie-parser": "^1.4.6", "core-js": "^3.6.5", "cors": "^2.8.5", "crypto-js": "^4.1.1", @@ -203,6 +204,7 @@ "@testing-library/react": "13.4.0", "@testing-library/user-event": "^13.2.1", "@types/auth0": "^2.35.7", + "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.12", "@types/crypto-js": "^4.1.1", "@types/d3-array": "^3.0.3", diff --git a/prisma/migrations/20240112201750_remove_auth0id_from_user/migration.sql b/prisma/migrations/20240112201750_remove_auth0id_from_user/migration.sql new file mode 100644 index 00000000..ae4de81f --- /dev/null +++ b/prisma/migrations/20240112201750_remove_auth0id_from_user/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `auth0_id` on the `user` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "user_auth0_id_key"; + +-- AlterTable +ALTER TABLE "user" DROP COLUMN "auth0_id"; diff --git a/prisma/migrations/20240112204004_add_first_last_to_authuser/migration.sql b/prisma/migrations/20240112204004_add_first_last_to_authuser/migration.sql new file mode 100644 index 00000000..ddb975b2 --- /dev/null +++ b/prisma/migrations/20240112204004_add_first_last_to_authuser/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "auth_user" ADD COLUMN "first_name" TEXT, +ADD COLUMN "last_name" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 91f04859..587a274c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -395,7 +395,6 @@ model User { id Int @id @default(autoincrement()) 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 @@ -585,6 +584,8 @@ model AuthAccount { model AuthUser { id String @id @default(cuid()) name String? + firstName String? @map("first_name") + lastName String? @map("last_name") email String? @unique emailVerified DateTime? @map("email_verified") password String? diff --git a/yarn.lock b/yarn.lock index 5450e8d4..f6388665 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5724,6 +5724,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bcrypt@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.2.tgz#22fddc11945ea4fbc3655b3e8b8847cc9f811477" + integrity sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ== + dependencies: + "@types/node" "*" + "@types/body-parser@*": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" @@ -9412,11 +9419,24 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +cookie-parser@^1.4.6: + version "1.4.6" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594" + integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA== + dependencies: + cookie "0.4.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== +cookie@0.4.1, cookie@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookie@0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" @@ -9432,11 +9452,6 @@ cookie@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" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== - cookiejar@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc"