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/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 531ef30f3..a4bf1e631 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, { GoogleProfile } from 'next-auth/providers/google'; import { prisma } from '@documenso/prisma'; @@ -40,19 +41,64 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { } return { - id: String(user.id) as any, + id: Number(user.id), 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: Number(profile.sub), + name: profile.name || `${profile.given_name} ${profile.family_name}`.trim(), + email: profile.email, + }; + }, + }), ], - // callbacks: { - // jwt: async ({ token, user: _user }) => { - // return { - // ...token, - // }; - // }, - // }, + callbacks: { + async jwt({ token, user }) { + if (!token.email) { + throw new Error('No email in token'); + } + + const retrievedUser = await prisma.user.findFirst({ + where: { + email: token.email, + }, + }); + + if (!retrievedUser) { + return { + ...token, + id: user.id, + }; + } + + return { + id: retrievedUser.id, + name: retrievedUser.name, + email: retrievedUser.email, + }; + }, + + async session({ token, session }) { + if (token && token.email) { + return { + ...session, + user: { + id: Number(token.id), + name: token.name, + email: token.email, + }, + } 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/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/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; diff --git a/turbo.json b/turbo.json index 50407c2f9..c900def16 100644 --- a/turbo.json +++ b/turbo.json @@ -19,6 +19,8 @@ "NEXT_PRIVATE_DATABASE_URL", "NEXT_PRIVATE_NEXT_AUTH_SECRET", "NEXT_PUBLIC_SUBSCRIPTIONS_ENABLED", + "NEXT_PRIVATE_GOOGLE_CLIENT_ID", + "NEXT_PRIVATE_GOOGLE_CLIENT_SECRET", "NEXT_PRIVATE_SMTP_TRANSPORT", "NEXT_PRIVATE_MAILCHANNELS_API_KEY", "NEXT_PRIVATE_MAILCHANNELS_ENDPOINT",