From 546585c288fcbf9c05e40717c36928f1fbd3a971 Mon Sep 17 00:00:00 2001 From: Mehdy Boulahbas Date: Wed, 10 Sep 2025 17:46:51 +0200 Subject: [PATCH] Feat: Add Google OAuth2 --- README.md | 7 ++- .../dashboard/pages/api/auth/[...nextauth].ts | 57 +++++++++++++++++-- apps/dashboard/pages/login.tsx | 20 +++++-- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 33f59c9..9dcbb6b 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,11 @@ Once pm2.web is installed and running, you can perform the following actions: - **Usage**: Only registered users (per credentials) can login with auth2, which links the oauth2 with the existing user account in the database - **Setup Registration Code** - Go to the settings page and add/generate the registration code - - **Setup Google OAuth (Planned/On Demand)** - - TBD + - **Setup Google OAuth** + - https://console.cloud.google.com/apis/credentials -> New OAuth 2.0 client + - Configure the callback url to `http:///api/auth/callback/google` + - Add `NEXT_PUBLIC_GOOGLE_CLIENT_ID` and `GOOGLE_SECRET` to the `.env` file with the values from the OAuth App + ## Up Next diff --git a/apps/dashboard/pages/api/auth/[...nextauth].ts b/apps/dashboard/pages/api/auth/[...nextauth].ts index 15dfb6e..b0f05e7 100644 --- a/apps/dashboard/pages/api/auth/[...nextauth].ts +++ b/apps/dashboard/pages/api/auth/[...nextauth].ts @@ -78,13 +78,58 @@ const providers = () => { ); } - if (process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) { + if (process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID && process.env.GOOGLE_SECRET) { p.push( - GoogleProvider({ - clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - }), - ); + GoogleProvider({ + clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID as string, + clientSecret: process.env.GOOGLE_SECRET as string, + + userinfo: { + async request({ client, tokens }) { + const profile = await client.userinfo(tokens.access_token!); + await connectDB(); + + if (!profile.email) throw new Error("NoEmail"); + + const user = await userModel.findOne({ + email: { $regex: new RegExp(profile.email, "i") }, + }); + + if (!user) throw new Error("NotRegistered"); + //check if auth provider is already linked + if (!user.oauth2?.provider) { + user.oauth2 = { + provider: "google", + providerUserId: profile.sub as string, + }; + await user.save(); + } + + const u = user.toJSON(); + + if (!u.acl.owner && !u.acl.admin && !u.acl?.servers?.length) throw new Error("Unauthorized"); + + // spread userObj to use in profile function + return { + ...profile, + id: user._id, + name: user.name, + email: user.email, + userObj: u, + }; + }, + }, + profile(profile) { + return { + id: profile.id.toString(), + name: profile.name, + email: profile.email, + image: profile.picture, + ...profile.userObj, + }; + }, + }) + ); } p.push( diff --git a/apps/dashboard/pages/login.tsx b/apps/dashboard/pages/login.tsx index 57b5b19..6462d04 100644 --- a/apps/dashboard/pages/login.tsx +++ b/apps/dashboard/pages/login.tsx @@ -72,9 +72,21 @@ export default function AuthenticationForm({ <> {process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID && ( - + + + )} {process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID && ( @@ -234,4 +246,4 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { registrationCodeRequired: await helpers.setting.registrationCodeRequired.fetch(), }, }; -} +} \ No newline at end of file