From 0d71710b14921200efa6767434b3dd6ec6aca4cd Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Wed, 6 Aug 2025 15:31:24 +0700 Subject: [PATCH] :sparkles: add new oauth provider add google idconnect as new auth provider --- prisma/schema.prisma | 4 +++ .../controllers/googleCallback.controller.ts | 20 +++++++++++ .../controllers/googleRequest.controller.ts | 18 ++++++++++ src/modules/auth/index.ts | 6 +++- src/modules/auth/providers/google.provider.ts | 10 ++++++ .../auth/services/googleCallback.service.ts | 36 +++++++++++++++++++ .../auth/services/googleRequest.service.ts | 28 +++++++++++++++ 7 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/modules/auth/controllers/googleCallback.controller.ts create mode 100644 src/modules/auth/controllers/googleRequest.controller.ts create mode 100644 src/modules/auth/providers/google.provider.ts create mode 100644 src/modules/auth/services/googleCallback.service.ts create mode 100644 src/modules/auth/services/googleRequest.service.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 66245ac..7ec06af 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -205,6 +205,10 @@ model User { bioProfile String? @db.Text avatar String? @db.Text commentBackground String? @db.Text + provider String? @db.VarChar(255) + providerId String? @unique @db.VarChar(255) + providerToken String? @db.Text + providerPayload Json? @db.Json preference UserPreference? @relation(fields: [preferenceId], references: [id]) preferenceId String? @unique verifiedAt DateTime? diff --git a/src/modules/auth/controllers/googleCallback.controller.ts b/src/modules/auth/controllers/googleCallback.controller.ts new file mode 100644 index 0000000..7b0192e --- /dev/null +++ b/src/modules/auth/controllers/googleCallback.controller.ts @@ -0,0 +1,20 @@ +import { Context } from "elysia"; +import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { googleCallbackService } from "../services/googleCallback.service"; + +export const googleCallbackController = async ( + ctx: Context & { query: { code: string; state: string } } +) => { + try { + const userData = await googleCallbackService(ctx.query); + return returnWriteResponse( + ctx.set, + 200, + "Authenticated successfully!", + userData + ); + } catch (error) { + return mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/auth/controllers/googleRequest.controller.ts b/src/modules/auth/controllers/googleRequest.controller.ts new file mode 100644 index 0000000..5ae0ad1 --- /dev/null +++ b/src/modules/auth/controllers/googleRequest.controller.ts @@ -0,0 +1,18 @@ +import { Context } from "elysia"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { googleRequestService } from "../services/googleRequest.service"; +import { returnReadResponse } from "../../../helpers/callback/httpResponse"; + +export const googleRequestController = async (ctx: Context) => { + try { + const loginUrl = await googleRequestService(); + return returnReadResponse( + ctx.set, + 200, + "Google login url created!", + loginUrl + ); + } catch (error) { + return mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/auth/index.ts b/src/modules/auth/index.ts index 1da74e4..ad5c1d1 100644 --- a/src/modules/auth/index.ts +++ b/src/modules/auth/index.ts @@ -1,7 +1,11 @@ import Elysia from "elysia"; import { githubRequestController } from "./controllers/githubRequest.controller"; import { githubCallbackController } from "./controllers/githubCallback.controller"; +import { googleRequestController } from "./controllers/googleRequest.controller"; +import { googleCallbackController } from "./controllers/googleCallback.controller"; export const authModule = new Elysia({ prefix: "/auth" }) .get("/github", githubRequestController) - .get("/github/callback", githubCallbackController); + .get("/github/callback", githubCallbackController) + .get("/google", googleRequestController) + .get("/google/callback", googleCallbackController); diff --git a/src/modules/auth/providers/google.provider.ts b/src/modules/auth/providers/google.provider.ts new file mode 100644 index 0000000..9bb0288 --- /dev/null +++ b/src/modules/auth/providers/google.provider.ts @@ -0,0 +1,10 @@ +import { Google } from "arctic"; + +export const googleProvider = () => { + const redirectURI = `${process.env.APP_PROTOCOL}://${process.env.APP_DOMAIN}${process.env.GOOGLE_CLIENT_CALLBACK}`; + return new Google( + process.env.GOOGLE_CLIENT_ID!, + process.env.GOOGLE_CLIENT_SECRET!, + redirectURI + ); +}; diff --git a/src/modules/auth/services/googleCallback.service.ts b/src/modules/auth/services/googleCallback.service.ts new file mode 100644 index 0000000..0b46b89 --- /dev/null +++ b/src/modules/auth/services/googleCallback.service.ts @@ -0,0 +1,36 @@ +import { AppError } from "../../../helpers/error/instances/app"; +import { googleProvider } from "../providers/google.provider"; +import { redis } from "../../../utils/databases/redis/connection"; +import { ErrorForwarder } from "../../../helpers/error/instances/forwarder"; + +export const googleCallbackService = async (query: { + state: string; + code: string; +}) => { + try { + const state = query.state; + const codeVerifier = await redis.get( + `${process.env.APP_NAME}:pkce:${state}` + ); + if (!codeVerifier) throw new AppError(408, "Request timeout"); + await redis.del(`${process.env.APP_NAME}:pkce:${state}`); + + const google = googleProvider(); + const tokens = await google.validateAuthorizationCode( + query.code, + codeVerifier + ); + const accessToken = tokens.accessToken(); + const response = await fetch( + "https://openidconnect.googleapis.com/v1/userinfo", + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ); + return await response.json(); + } catch (error) { + ErrorForwarder(error, 500, "Authentication service error"); + } +}; diff --git a/src/modules/auth/services/googleRequest.service.ts b/src/modules/auth/services/googleRequest.service.ts new file mode 100644 index 0000000..2b4f077 --- /dev/null +++ b/src/modules/auth/services/googleRequest.service.ts @@ -0,0 +1,28 @@ +import * as arctic from "arctic"; +import { AppError } from "../../../helpers/error/instances/app"; +import { googleProvider } from "../providers/google.provider"; +import { redis } from "../../../utils/databases/redis/connection"; + +export const googleRequestService = async () => { + try { + const google = googleProvider(); + const state = arctic.generateState(); + const codeVerifier = arctic.generateCodeVerifier(); + const scopes = ["openid", "profile", "email"]; + const url = google.createAuthorizationURL(state, codeVerifier, scopes); + + await redis.setex( + `${process.env.APP_NAME}:pkce:${state}`, + 300, + codeVerifier + ); + + return url; + } catch (error) { + throw new AppError( + 500, + "Google Auth provider is experiencing issues.", + error + ); + } +};