From 2b84636993a01f99c03381eb8cad0900c22cab67 Mon Sep 17 00:00:00 2001 From: Doug Andrade Date: Tue, 13 Jun 2023 01:53:12 -0400 Subject: [PATCH 1/3] feat: google auth without schema change --- apps/web/process-env.d.ts | 3 ++ apps/web/src/components/forms/signin.tsx | 4 +- packages/lib/next-auth/auth-options.ts | 62 ++++++++++++++++++++---- packages/lib/types/next-auth.d.ts | 23 +++++++++ turbo.json | 4 +- 5 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 packages/lib/types/next-auth.d.ts diff --git a/apps/web/process-env.d.ts b/apps/web/process-env.d.ts index 92a6401fc..e91137fe1 100644 --- a/apps/web/process-env.d.ts +++ b/apps/web/process-env.d.ts @@ -11,5 +11,8 @@ declare namespace NodeJS { NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string; NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED: string; + + NEXT_PRIVATE_GOOGLE_CLIENT_ID: string; + NEXT_PRIVATE_GOOGLE_CLIENT_SECRET: string; } } diff --git a/apps/web/src/components/forms/signin.tsx b/apps/web/src/components/forms/signin.tsx index be1991e95..449863e71 100644 --- a/apps/web/src/components/forms/signin.tsx +++ b/apps/web/src/components/forms/signin.tsx @@ -61,8 +61,8 @@ export const SignInForm = ({ className }: SignInFormProps) => { const onSignInWithGoogleClick = async () => { try { - // await signIn('google', { callbackUrl: '/dashboard' }); - throw new Error('Not implemented'); + await signIn('google', { callbackUrl: '/dashboard' }); + // throw new Error('Not implemented'); } catch (err) { toast({ title: 'An unknown error occurred', diff --git a/packages/lib/next-auth/auth-options.ts b/packages/lib/next-auth/auth-options.ts index 4d3c9fa4d..6c285fae3 100644 --- a/packages/lib/next-auth/auth-options.ts +++ b/packages/lib/next-auth/auth-options.ts @@ -1,7 +1,8 @@ import { PrismaAdapter } from '@next-auth/prisma-adapter'; import { compare } from 'bcrypt'; -import { AuthOptions, User } from 'next-auth'; +import { AuthOptions, Session, User } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; +import GoogleProvider from 'next-auth/providers/google'; import { prisma } from '@documenso/prisma'; @@ -44,16 +45,59 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { id: String(user.id) as any, email: user.email, name: user.name, - image: '', } satisfies User; }, }), + GoogleProvider({ + clientId: process.env.NEXT_PRIVATE_GOOGLE_CLIENT_ID ?? '', + clientSecret: process.env.NEXT_PRIVATE_GOOGLE_CLIENT_SECRET ?? '', + allowDangerousEmailAccountLinking: true, + profile(profile) { + return { + id: profile.sub as any, + name: profile.name, + email: profile.email, + }; + }, + }), ], - // callbacks: { - // jwt: async ({ token, user: _user }) => { - // return { - // ...token, - // }; - // }, - // }, + callbacks: { + async jwt({ token, user, account, profile }) { + console.log('jwt', { token, user, account, profile }); + const dbUser = await prisma.user.findFirst({ + where: { + email: token.email as string, + }, + }); + + if (!dbUser) { + if (user) { + token.id = user?.id; + } + return token; + } + + return { + id: dbUser.id, + name: dbUser.name, + email: dbUser.email, + }; + }, + async session({ token, session }) { + console.log('session', { token, session }); + if (token) { + const documensoSession = { + ...session, + user: { + id: Number(token.id), + name: token.name, + email: token.email, + }, + } as Session; + + return documensoSession; + } + return session; + }, + }, }; diff --git a/packages/lib/types/next-auth.d.ts b/packages/lib/types/next-auth.d.ts new file mode 100644 index 000000000..c36fbbc5f --- /dev/null +++ b/packages/lib/types/next-auth.d.ts @@ -0,0 +1,23 @@ +import type { User as PrismaUser } from '@prisma/client'; +import type { DefaultUser } from 'next-auth'; + +declare module 'next-auth' { + interface Session { + user: User; + } + + interface User extends Omit { + id: PrismaUser['id']; + email?: PrismaUser['email']; + name?: PrismaUser['name']; + emailVerified?: PrismaUser['emailVerified']; + } +} + +declare module 'next-auth/jwt' { + interface JWT { + id: string | number; + name?: string | null; + email: string | null; + } +} diff --git a/turbo.json b/turbo.json index 388f952af..1d40fcdbd 100644 --- a/turbo.json +++ b/turbo.json @@ -18,6 +18,8 @@ "NEXT_PUBLIC_SITE_URL", "NEXT_PRIVATE_DATABASE_URL", "NEXT_PRIVATE_NEXT_AUTH_SECRET", - "NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED" + "NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED", + "NEXT_PRIVATE_GOOGLE_CLIENT_ID", + "NEXT_PRIVATE_GOOGLE_CLIENT_SECRET" ] } From d1bc948f3c1fe84e69b494a65177a1a67099650c Mon Sep 17 00:00:00 2001 From: Doug Andrade Date: Tue, 13 Jun 2023 02:00:45 -0400 Subject: [PATCH 2/3] clean up console.log() used for testing --- packages/lib/next-auth/auth-options.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/lib/next-auth/auth-options.ts b/packages/lib/next-auth/auth-options.ts index 6c285fae3..359715f73 100644 --- a/packages/lib/next-auth/auth-options.ts +++ b/packages/lib/next-auth/auth-options.ts @@ -62,8 +62,7 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { }), ], callbacks: { - async jwt({ token, user, account, profile }) { - console.log('jwt', { token, user, account, profile }); + async jwt({ token, user }) { const dbUser = await prisma.user.findFirst({ where: { email: token.email as string, From 918018c7ca6dc7d6b9ec90a6edbe26dd4e2ba6f8 Mon Sep 17 00:00:00 2001 From: Mythie Date: Mon, 31 Jul 2023 13:53:55 +1000 Subject: [PATCH 3/3] fix: improve typesafety --- .env.example | 4 +++ packages/lib/next-auth/auth-options.ts | 45 ++++++++++++++------------ packages/lib/tsconfig.json | 5 +++ packages/tsconfig/process-env.d.ts | 3 ++ 4 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 packages/lib/tsconfig.json diff --git a/.env.example b/.env.example index 3ce57722b..47dea109e 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,10 @@ NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET="secret" +# [[AUTH OPTIONAL]] +NEXT_PRIVATE_GOOGLE_CLIENT_ID="" +NEXT_PRIVATE_GOOGLE_CLIENT_SECRET="" + # [[APP]] NEXT_PUBLIC_SITE_URL="http://localhost:3000" NEXT_PUBLIC_APP_URL="http://localhost:3000" diff --git a/packages/lib/next-auth/auth-options.ts b/packages/lib/next-auth/auth-options.ts index 827b3b624..a4bf1e631 100644 --- a/packages/lib/next-auth/auth-options.ts +++ b/packages/lib/next-auth/auth-options.ts @@ -2,7 +2,7 @@ import { PrismaAdapter } from '@next-auth/prisma-adapter'; import { compare } from 'bcrypt'; import { AuthOptions, Session, User } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; -import GoogleProvider from 'next-auth/providers/google'; +import GoogleProvider, { GoogleProfile } from 'next-auth/providers/google'; import { prisma } from '@documenso/prisma'; @@ -41,20 +41,20 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { } return { - id: String(user.id) as any, + id: Number(user.id), email: user.email, name: user.name, } satisfies User; }, }), - GoogleProvider({ + GoogleProvider({ clientId: process.env.NEXT_PRIVATE_GOOGLE_CLIENT_ID ?? '', clientSecret: process.env.NEXT_PRIVATE_GOOGLE_CLIENT_SECRET ?? '', allowDangerousEmailAccountLinking: true, profile(profile) { return { - id: profile.sub as any, - name: profile.name, + id: Number(profile.sub), + name: profile.name || `${profile.given_name} ${profile.family_name}`.trim(), email: profile.email, }; }, @@ -62,39 +62,42 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { ], callbacks: { async jwt({ token, user }) { - const dbUser = await prisma.user.findFirst({ + if (!token.email) { + throw new Error('No email in token'); + } + + const retrievedUser = await prisma.user.findFirst({ where: { - email: token.email as string, + email: token.email, }, }); - if (!dbUser) { - if (user) { - token.id = user?.id; - } - return token; + if (!retrievedUser) { + return { + ...token, + id: user.id, + }; } return { - id: dbUser.id, - name: dbUser.name, - email: dbUser.email, + id: retrievedUser.id, + name: retrievedUser.name, + email: retrievedUser.email, }; }, + async session({ token, session }) { - console.log('session', { token, session }); - if (token) { - const documensoSession = { + if (token && token.email) { + return { ...session, user: { id: Number(token.id), name: token.name, email: token.email, }, - } as Session; - - return documensoSession; + } satisfies Session; } + return session; }, }, diff --git a/packages/lib/tsconfig.json b/packages/lib/tsconfig.json new file mode 100644 index 000000000..4aefcb98c --- /dev/null +++ b/packages/lib/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@documenso/tsconfig/react-library.json", + "include": ["."], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/tsconfig/process-env.d.ts b/packages/tsconfig/process-env.d.ts index a6e1be1e2..8f8b6f108 100644 --- a/packages/tsconfig/process-env.d.ts +++ b/packages/tsconfig/process-env.d.ts @@ -2,6 +2,9 @@ declare namespace NodeJS { export interface ProcessEnv { NEXT_PUBLIC_SITE_URL?: string; + NEXT_PRIVATE_GOOGLE_CLIENT_ID?: string; + NEXT_PRIVATE_GOOGLE_CLIENT_SECRET?: string; + NEXT_PRIVATE_DATABASE_URL: string; NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string;