From 97dc26ed82290433e987702a7671dd654606f165 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Mon, 9 Mar 2026 12:00:00 +0700 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=91=94=20feat:=20add=20schema=20for?= =?UTF-8?q?=20token=20verification=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/index.ts | 5 +- .../auth/schemas/tokenValidation.schema.ts | 108 ++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/modules/auth/schemas/tokenValidation.schema.ts diff --git a/src/modules/auth/index.ts b/src/modules/auth/index.ts index bf0db41..85786c1 100644 --- a/src/modules/auth/index.ts +++ b/src/modules/auth/index.ts @@ -7,9 +7,10 @@ import { getOauthProvidersController } from "./controllers/getOauthProviders.con import { getCallbackProviderUrlController } from "./controllers/getCallbackProviderUrl.controller"; import { tokenValidationController } from "./controllers/tokenValidation.controller"; import { logoutController } from "./controllers/logout.controller"; +import { tokenValidationSchema } from "./schemas/tokenValidation.schema"; -export const authModule = new Elysia({ prefix: "/auth" }) - .post("/token/validate", tokenValidationController) +export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] }) + .post("/token/validate", tokenValidationController, tokenValidationSchema) .get("/providers", getOauthProvidersController) .get("/providers/:name/callback", getCallbackProviderUrlController) .get("/github", githubRequestController) diff --git a/src/modules/auth/schemas/tokenValidation.schema.ts b/src/modules/auth/schemas/tokenValidation.schema.ts new file mode 100644 index 0000000..050905b --- /dev/null +++ b/src/modules/auth/schemas/tokenValidation.schema.ts @@ -0,0 +1,108 @@ +import { t } from "elysia"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const tokenValidationSchema = { + headers: t.Object({ + cookie: t.String({ description: "Authentication token in cookie format, e.g., auth_token=your_jwt_token;" }), + }), + detail: { + summary: "Validate authentication JWT token", + description: + "Validates the provided authentication JWT token with checking its validity and expiration in redis cache, if not exists, it will be checked in the database. If the token is valid, it returns the user information associated with the token. if the token is invalid or expired, it returns an error message.", + responses: { + 200: { + description: "Validation successful", + content: { + "application/json": { + schema: { + type: "object", + properties: { + id: { type: "string", description: "Session ID", default: "xxxx-xxxxx-xxxxx-xxxx" }, + isAuthenticated: { + type: "boolean", + description: "Indicates if the token is valid and the user is authenticated", + default: true, + }, + validUntil: { + type: "string", + format: "date-time", + description: "Expiration date and time of the token", + default: "2024-12-31T23:59:59Z", + }, + user: { + type: "object", + properties: { + id: { type: "string", description: "User ID", default: "user-12345" }, + name: { type: "string", description: "User's full name", default: "Lena Nouzen" }, + email: { + type: "string", + format: "email", + description: "User's email address", + default: "lena@example.com", + }, + username: { type: "string", description: "User's username", default: "vladilena" }, + avatar: { + type: "string", + format: "uri", + description: "URL to the user's avatar image", + default: "https://example.com/avatar.jpg", + }, + birthDate: { + type: "string", + format: "date", + description: "User's birth date, can be null if not provided", + default: null, + }, + bioProfile: { + type: "string", + description: "User's bio/profile description, can be null if not provided", + default: null, + }, + preference: { + type: "object", + properties: { + id: { type: "string", description: "Preference ID", default: "pref-12345" }, + userId: { type: "string", description: "Associated User ID", default: "user-12345" }, + langPreference: { + type: "string", + description: "User's language preference, can be null if not provided", + default: null, + }, + adultFiltering: { + type: "string", + description: "User's adult content filtering setting", + default: "strict", + }, + adultAlert: { + type: "string", + description: "User's adult content alert setting", + default: "enabled", + }, + videoQuality: { + type: "string", + description: "User's preferred video quality setting", + default: "1080p", + }, + serviceDefaultId: { + type: "string", + description: "Default service ID for the user, can be null if not provided", + default: null, + }, + hideContries: { + type: "array", + items: { type: "string" }, + description: "List of country codes that the user has chosen to hide content from", + default: ["US", "CN"], + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; From dc350d006babf7659c2bbc9f908cc515873216fd Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Tue, 10 Mar 2026 12:00:00 +0700 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9D=20docs:=20add=20schema=20docum?= =?UTF-8?q?entation=20to=20OAuth=20provider=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../getOauthProviders.controller.ts | 4 +- src/modules/auth/index.ts | 3 +- .../auth/schemas/getOauthProviders.schema.ts | 56 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/modules/auth/schemas/getOauthProviders.schema.ts diff --git a/src/modules/auth/controllers/getOauthProviders.controller.ts b/src/modules/auth/controllers/getOauthProviders.controller.ts index 579ae1c..0caf730 100644 --- a/src/modules/auth/controllers/getOauthProviders.controller.ts +++ b/src/modules/auth/controllers/getOauthProviders.controller.ts @@ -9,8 +9,8 @@ export const getOauthProvidersController = (ctx: Context) => { return returnReadResponse( ctx.set, 200, - "Getting all oauth available list", - oauthProviderServices + "Successfully retrieved the list of oauth providers", + oauthProviderServices, ); } catch (error) { return mainErrorHandler(ctx.set, error); diff --git a/src/modules/auth/index.ts b/src/modules/auth/index.ts index 85786c1..55b4263 100644 --- a/src/modules/auth/index.ts +++ b/src/modules/auth/index.ts @@ -8,10 +8,11 @@ import { getCallbackProviderUrlController } from "./controllers/getCallbackProvi import { tokenValidationController } from "./controllers/tokenValidation.controller"; import { logoutController } from "./controllers/logout.controller"; import { tokenValidationSchema } from "./schemas/tokenValidation.schema"; +import { getOauthProvidersSchema } from "./schemas/getOauthProviders.schema"; export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] }) .post("/token/validate", tokenValidationController, tokenValidationSchema) - .get("/providers", getOauthProvidersController) + .get("/providers", getOauthProvidersController, getOauthProvidersSchema) .get("/providers/:name/callback", getCallbackProviderUrlController) .get("/github", githubRequestController) .get("/github/callback", githubCallbackController) diff --git a/src/modules/auth/schemas/getOauthProviders.schema.ts b/src/modules/auth/schemas/getOauthProviders.schema.ts new file mode 100644 index 0000000..1ec20eb --- /dev/null +++ b/src/modules/auth/schemas/getOauthProviders.schema.ts @@ -0,0 +1,56 @@ +import { success } from "zod"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const getOauthProvidersSchema = { + detail: { + summary: "Get all available oauth providers", + description: + "This endpoint returns a list of all available and active oauth providers that can be used for authentication.", + responses: { + 200: { + description: "Successfully retrieved the list of oauth providers", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { + type: "boolean", + example: true, + }, + status: { + type: "number", + example: 200, + }, + message: { + type: "string", + example: "Successfully retrieved the list of oauth providers", + }, + data: { + type: "array", + items: { + type: "object", + properties: { + name: { + type: "string", + example: "google", + }, + icon: { + type: "string", + example: "logos:google-icon", + }, + req_endpoint: { + type: "string", + example: "auth/google", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; From da74f5e3e1197f9195362a6e951bd2f69320c871 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Wed, 11 Mar 2026 09:24:25 +0700 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=93=A6=20chore:=20snapshot=20commit?= =?UTF-8?q?=20before=20major=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../getUserHeaderInformation/index.ts | 18 ++---- .../controllers/googleCallback.controller.ts | 2 +- .../controllers/googleRequest.controller.ts | 12 ++-- src/modules/auth/index.ts | 9 ++- .../schemas/getCallbackProviderUrl.schema.ts | 45 ++++++++++++++ .../auth/schemas/googleCallback.schema.ts | 58 +++++++++++++++++++ .../auth/schemas/googleRequest.schema.ts | 54 +++++++++++++++++ 7 files changed, 175 insertions(+), 23 deletions(-) create mode 100644 src/modules/auth/schemas/getCallbackProviderUrl.schema.ts create mode 100644 src/modules/auth/schemas/googleCallback.schema.ts create mode 100644 src/modules/auth/schemas/googleRequest.schema.ts diff --git a/src/helpers/http/userHeader/getUserHeaderInformation/index.ts b/src/helpers/http/userHeader/getUserHeaderInformation/index.ts index c704a50..a249340 100644 --- a/src/helpers/http/userHeader/getUserHeaderInformation/index.ts +++ b/src/helpers/http/userHeader/getUserHeaderInformation/index.ts @@ -10,25 +10,15 @@ export interface ClientInfoHeader { ip: string; } -export const getUserHeaderInformation = ( - ctx: Context, -): UserHeaderInformation => { +export const getUserHeaderInformation = (ctx: Context): UserHeaderInformation => { const clientInfoHeader = - (JSON.parse( - ctx.request.headers.get("x-client-info") as string, - ) as ClientInfoHeader) ?? ("unknown" as string); + (JSON.parse(ctx.request.headers.get("x-client-info") as string) as ClientInfoHeader) ?? ("unknown" as string); const userHeaderInformation = { ip: clientInfoHeader.ip ?? "unknown", deviceType: clientInfoHeader.deviceType ?? "unknown", - deviceOS: - (clientInfoHeader.os ?? "unknown") + - " " + - (clientInfoHeader.osVersion ?? "unknown"), - browser: - (clientInfoHeader.browser ?? "unknown") + - " " + - (clientInfoHeader.browserVersion ?? "unknown"), + deviceOS: (clientInfoHeader.os ?? "unknown") + " " + (clientInfoHeader.osVersion ?? "unknown"), + browser: (clientInfoHeader.browser ?? "unknown") + " " + (clientInfoHeader.browserVersion ?? "unknown"), }; return userHeaderInformation; diff --git a/src/modules/auth/controllers/googleCallback.controller.ts b/src/modules/auth/controllers/googleCallback.controller.ts index fcb501e..b02cf59 100644 --- a/src/modules/auth/controllers/googleCallback.controller.ts +++ b/src/modules/auth/controllers/googleCallback.controller.ts @@ -5,7 +5,7 @@ import { googleCallbackService } from "../services/http/googleCallback.service"; import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation"; export const googleCallbackController = async ( - ctx: Context & { query: { code: string; state: string; callbackURI: string } } + ctx: Context & { query: { code: string; state: string; callbackURI: string } }, ) => { try { const userHeaderInfo = getUserHeaderInformation(ctx); diff --git a/src/modules/auth/controllers/googleRequest.controller.ts b/src/modules/auth/controllers/googleRequest.controller.ts index 7b122a2..a778d8b 100644 --- a/src/modules/auth/controllers/googleRequest.controller.ts +++ b/src/modules/auth/controllers/googleRequest.controller.ts @@ -1,14 +1,16 @@ -import { Context } from "elysia"; +import { Context, Static } from "elysia"; import { mainErrorHandler } from "../../../helpers/error/handler"; import { googleRequestService } from "../services/http/googleRequest.service"; import { returnReadResponse } from "../../../helpers/callback/httpResponse"; +import { googleRequestSchema } from "../schemas/googleRequest.schema"; -export const googleRequestController = async ( - ctx: Context & { query: { callback?: string } } -) => { +export const googleRequestController = async (ctx: { + set: Context["set"]; + query: Static; +}) => { try { const loginUrl = await googleRequestService(ctx.query.callback); - return returnReadResponse(ctx.set, 200, "Google login url created!", { + return returnReadResponse(ctx.set, 200, "Google login URL created successfully.", { endpointUrl: loginUrl, }); } catch (error) { diff --git a/src/modules/auth/index.ts b/src/modules/auth/index.ts index 55b4263..72e8076 100644 --- a/src/modules/auth/index.ts +++ b/src/modules/auth/index.ts @@ -9,13 +9,16 @@ import { tokenValidationController } from "./controllers/tokenValidation.control import { logoutController } from "./controllers/logout.controller"; import { tokenValidationSchema } from "./schemas/tokenValidation.schema"; import { getOauthProvidersSchema } from "./schemas/getOauthProviders.schema"; +import { getCallbackProviderUrlSchema } from "./schemas/getCallbackProviderUrl.schema"; +import { googleRequestSchema } from "./schemas/googleRequest.schema"; +import { googleCallbackSchema } from "./schemas/googleCallback.schema"; export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] }) .post("/token/validate", tokenValidationController, tokenValidationSchema) .get("/providers", getOauthProvidersController, getOauthProvidersSchema) - .get("/providers/:name/callback", getCallbackProviderUrlController) + .get("/providers/:name/callback", getCallbackProviderUrlController, getCallbackProviderUrlSchema) + .get("/google", googleRequestController, googleRequestSchema) + .get("/google/callback", googleCallbackController, googleCallbackSchema) .get("/github", githubRequestController) .get("/github/callback", githubCallbackController) - .get("/google", googleRequestController) - .get("/google/callback", googleCallbackController) .post("/logout", logoutController); diff --git a/src/modules/auth/schemas/getCallbackProviderUrl.schema.ts b/src/modules/auth/schemas/getCallbackProviderUrl.schema.ts new file mode 100644 index 0000000..cef86cb --- /dev/null +++ b/src/modules/auth/schemas/getCallbackProviderUrl.schema.ts @@ -0,0 +1,45 @@ +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const getCallbackProviderUrlSchema = { + detail: { + summary: "Get the callback URL of oauth provider", + description: + "After users have successfully completed the authentication process on the OAuth provider page, they will be redirected to the callback page on the frontend. This endpoint aims to obtain the actual endpoint for each OAuth response handler.", + responses: { + 200: { + description: "The callback URL on the provider has been found.", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { + type: "boolean", + default: true, + }, + status: { + type: "number", + default: 200, + }, + message: { + type: "string", + default: "The callback URL on the provider has been found.", + }, + data: { + type: "object", + properties: { + callback_url: { + type: "string", + description: "The callback URL on the provider.", + example: "auth/google/callback", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; diff --git a/src/modules/auth/schemas/googleCallback.schema.ts b/src/modules/auth/schemas/googleCallback.schema.ts new file mode 100644 index 0000000..c5249fa --- /dev/null +++ b/src/modules/auth/schemas/googleCallback.schema.ts @@ -0,0 +1,58 @@ +import { t } from "elysia"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const googleCallbackSchema = { + headers: t.Object({ + "x-client-info": t.String({ + examples: [ + '{"os":"Windows","osVersion":"10","browser":"Chrome","browserVersion":"89.0.4389.82","deviceType":"Desktop","ip":"192.168.1.1"}', + ], + }), + }), + query: t.Object({ + code: t.String({ examples: ["4/0AY0e-xxxxxxxxx"] }), + state: t.String({ examples: ["random_state_string"] }), + callbackURI: t.String({ examples: ["https://example.com/auth/google/callback"] }), + }), + detail: { + summary: "Google OAuth callback endpoint", + description: + "Handles the callback from Google OAuth and processes the authentication response. This endpoint also processes the account provisioning if the user is logging in for the first time.", + responses: { + 200: { + description: "Authentication successful", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { + type: "boolean", + example: true, + }, + status: { + type: "number", + example: 200, + }, + message: { + type: "string", + example: "Authentication successful", + }, + data: { + type: "object", + properties: { + authToken: { + type: "string", + description: "JWT token for authenticated user", + example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; diff --git a/src/modules/auth/schemas/googleRequest.schema.ts b/src/modules/auth/schemas/googleRequest.schema.ts new file mode 100644 index 0000000..78b4e8c --- /dev/null +++ b/src/modules/auth/schemas/googleRequest.schema.ts @@ -0,0 +1,54 @@ +import { t } from "elysia"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const googleRequestSchema = { + query: t.Object({ + callback: t.Optional( + t.String({ + description: "The callback URL to redirect after Google authentication. It should be URL-encoded if provided.", + }), + ), + }), + detail: { + summary: "Initiate Google OAuth flow", + description: + "This endpoint initiates the Google OAuth flow by redirecting the user to Google's authentication page.", + responses: { + 200: { + description: "Google login URL created successfully.", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { + type: "boolean", + default: true, + }, + status: { + type: "number", + default: 200, + }, + message: { + type: "string", + default: "Google login URL created successfully.", + }, + data: { + type: "object", + properties: { + endpointUrl: { + type: "string", + description: "The URL to redirect the user for Google authentication.", + example: + "https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=email%20profile", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; From 864a9196800bcdb026b3b0422d186579c1e1c103 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Wed, 11 Mar 2026 10:07:33 +0700 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=93=9D=20docs:=20complete=20documenta?= =?UTF-8?q?tion=20for=20auth=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../getUserHeaderInformation/index.ts | 5 +- .../controllers/githubCallback.controller.ts | 13 ++- .../controllers/githubRequest.controller.ts | 21 ++-- .../controllers/googleCallback.controller.ts | 15 +-- src/modules/auth/index.ts | 9 +- .../auth/schemas/githubCallback.schema.ts | 57 +++++++++++ .../auth/schemas/githubRequest.schema.ts | 54 +++++++++++ src/modules/auth/schemas/logout.schema.ts | 97 +++++++++++++++++++ 8 files changed, 242 insertions(+), 29 deletions(-) create mode 100644 src/modules/auth/schemas/githubCallback.schema.ts create mode 100644 src/modules/auth/schemas/githubRequest.schema.ts create mode 100644 src/modules/auth/schemas/logout.schema.ts diff --git a/src/helpers/http/userHeader/getUserHeaderInformation/index.ts b/src/helpers/http/userHeader/getUserHeaderInformation/index.ts index a249340..267a61a 100644 --- a/src/helpers/http/userHeader/getUserHeaderInformation/index.ts +++ b/src/helpers/http/userHeader/getUserHeaderInformation/index.ts @@ -10,9 +10,8 @@ export interface ClientInfoHeader { ip: string; } -export const getUserHeaderInformation = (ctx: Context): UserHeaderInformation => { - const clientInfoHeader = - (JSON.parse(ctx.request.headers.get("x-client-info") as string) as ClientInfoHeader) ?? ("unknown" as string); +export const getUserHeaderInformation = (clientInfo: string): UserHeaderInformation => { + const clientInfoHeader = (JSON.parse(clientInfo) as ClientInfoHeader) ?? ("unknown" as string); const userHeaderInformation = { ip: clientInfoHeader.ip ?? "unknown", diff --git a/src/modules/auth/controllers/githubCallback.controller.ts b/src/modules/auth/controllers/githubCallback.controller.ts index 1c727cf..580d7e8 100644 --- a/src/modules/auth/controllers/githubCallback.controller.ts +++ b/src/modules/auth/controllers/githubCallback.controller.ts @@ -1,14 +1,17 @@ -import { Context } from "elysia"; +import { Context, Static } from "elysia"; import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; import { githubCallbackService } from "../services/http/githubCallback.service"; import { mainErrorHandler } from "../../../helpers/error/handler"; import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation"; +import { githubCallbackSchema } from "../schemas/githubCallback.schema"; -export const githubCallbackController = async ( - ctx: Context & { query: { code: string; callbackURI: string } } -) => { +export const githubCallbackController = async (ctx: { + set: Context["set"]; + query: Static; + headers: Static; +}) => { try { - const userHeaderInfo = getUserHeaderInformation(ctx); + const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]); const authToken = await githubCallbackService(ctx.query, userHeaderInfo); return returnWriteResponse(ctx.set, 200, "Authenticated successfully!", { diff --git a/src/modules/auth/controllers/githubRequest.controller.ts b/src/modules/auth/controllers/githubRequest.controller.ts index fc1a3bd..a2bf3c4 100644 --- a/src/modules/auth/controllers/githubRequest.controller.ts +++ b/src/modules/auth/controllers/githubRequest.controller.ts @@ -1,21 +1,18 @@ -import { Context } from "elysia"; +import { Context, Static } from "elysia"; import { returnReadResponse } from "../../../helpers/callback/httpResponse"; import { githubRequestService } from "../services/http/githubRequest.service"; import { mainErrorHandler } from "../../../helpers/error/handler"; +import { githubRequestSchema } from "../schemas/githubRequest.schema"; -export const githubRequestController = async ( - ctx: Context & { query: { callback?: string } }, -) => { +export const githubRequestController = async (ctx: { + set: Context["set"]; + query: Static; +}) => { try { const loginUrl = await githubRequestService(ctx.query.callback); - return returnReadResponse( - ctx.set, - 200, - "Login URL generated successfully", - { - endpointUrl: loginUrl, - }, - ); + return returnReadResponse(ctx.set, 200, "GitHub login URL created successfully.", { + endpointUrl: loginUrl, + }); } catch (error) { return mainErrorHandler(ctx.set, error); } diff --git a/src/modules/auth/controllers/googleCallback.controller.ts b/src/modules/auth/controllers/googleCallback.controller.ts index b02cf59..0930374 100644 --- a/src/modules/auth/controllers/googleCallback.controller.ts +++ b/src/modules/auth/controllers/googleCallback.controller.ts @@ -1,17 +1,20 @@ -import { Context } from "elysia"; +import { Context, Static } from "elysia"; import { returnReadResponse } from "../../../helpers/callback/httpResponse"; import { mainErrorHandler } from "../../../helpers/error/handler"; import { googleCallbackService } from "../services/http/googleCallback.service"; import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation"; +import { googleCallbackSchema } from "../schemas/googleCallback.schema"; -export const googleCallbackController = async ( - ctx: Context & { query: { code: string; state: string; callbackURI: string } }, -) => { +export const googleCallbackController = async (ctx: { + set: Context["set"]; + query: Static; + headers: Static; +}) => { try { - const userHeaderInfo = getUserHeaderInformation(ctx); + const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]); const authToken = await googleCallbackService(ctx.query, userHeaderInfo); - return returnReadResponse(ctx.set, 200, "Authenticated successfully!", { + return returnReadResponse(ctx.set, 200, "Authentication successful!", { authToken, }); } catch (error) { diff --git a/src/modules/auth/index.ts b/src/modules/auth/index.ts index 72e8076..3a52207 100644 --- a/src/modules/auth/index.ts +++ b/src/modules/auth/index.ts @@ -12,6 +12,9 @@ import { getOauthProvidersSchema } from "./schemas/getOauthProviders.schema"; import { getCallbackProviderUrlSchema } from "./schemas/getCallbackProviderUrl.schema"; import { googleRequestSchema } from "./schemas/googleRequest.schema"; import { googleCallbackSchema } from "./schemas/googleCallback.schema"; +import { githubRequestSchema } from "./schemas/githubRequest.schema"; +import { githubCallbackSchema } from "./schemas/githubCallback.schema"; +import { logoutSchema } from "./schemas/logout.schema"; export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] }) .post("/token/validate", tokenValidationController, tokenValidationSchema) @@ -19,6 +22,6 @@ export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] .get("/providers/:name/callback", getCallbackProviderUrlController, getCallbackProviderUrlSchema) .get("/google", googleRequestController, googleRequestSchema) .get("/google/callback", googleCallbackController, googleCallbackSchema) - .get("/github", githubRequestController) - .get("/github/callback", githubCallbackController) - .post("/logout", logoutController); + .get("/github", githubRequestController, githubRequestSchema) + .get("/github/callback", githubCallbackController, githubCallbackSchema) + .post("/logout", logoutController, logoutSchema); diff --git a/src/modules/auth/schemas/githubCallback.schema.ts b/src/modules/auth/schemas/githubCallback.schema.ts new file mode 100644 index 0000000..3404bd8 --- /dev/null +++ b/src/modules/auth/schemas/githubCallback.schema.ts @@ -0,0 +1,57 @@ +import { t } from "elysia"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const githubCallbackSchema = { + headers: t.Object({ + "x-client-info": t.String({ + examples: [ + '{"os":"Windows","osVersion":"10","browser":"Chrome","browserVersion":"89.0.4389.82","deviceType":"Desktop","ip":"192.168.1.1"}', + ], + }), + }), + query: t.Object({ + code: t.String({ examples: ["4/0AY0e-xxxxxxxxx"] }), + callbackURI: t.String({ examples: ["https://example.com/auth/github/callback"] }), + }), + detail: { + summary: "GitHub OAuth callback endpoint", + description: + "Handles the callback from GitHub OAuth and processes the authentication response. This endpoint also processes the account provisioning if the user is logging in for the first time.", + responses: { + 200: { + description: "Authentication successful", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { + type: "boolean", + example: true, + }, + status: { + type: "number", + example: 200, + }, + message: { + type: "string", + example: "Authentication successful", + }, + data: { + type: "object", + properties: { + authToken: { + type: "string", + description: "JWT token for authenticated user", + example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; diff --git a/src/modules/auth/schemas/githubRequest.schema.ts b/src/modules/auth/schemas/githubRequest.schema.ts new file mode 100644 index 0000000..8485d5e --- /dev/null +++ b/src/modules/auth/schemas/githubRequest.schema.ts @@ -0,0 +1,54 @@ +import { t } from "elysia"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const githubRequestSchema = { + query: t.Object({ + callback: t.Optional( + t.String({ + description: "The callback URL to redirect after GitHub authentication. It should be URL-encoded if provided.", + }), + ), + }), + detail: { + summary: "Initiate GitHub OAuth flow", + description: + "This endpoint initiates the GitHub OAuth flow by redirecting the user to GitHub's authentication page.", + responses: { + 200: { + description: "GitHub login URL created successfully.", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { + type: "boolean", + default: true, + }, + status: { + type: "number", + default: 200, + }, + message: { + type: "string", + default: "GitHub login URL created successfully.", + }, + data: { + type: "object", + properties: { + endpointUrl: { + type: "string", + description: "The URL to redirect the user for GitHub authentication.", + example: + "https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=user:email", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; diff --git a/src/modules/auth/schemas/logout.schema.ts b/src/modules/auth/schemas/logout.schema.ts new file mode 100644 index 0000000..a579bab --- /dev/null +++ b/src/modules/auth/schemas/logout.schema.ts @@ -0,0 +1,97 @@ +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const logoutSchema = { + detail: { + summary: "Logout endpoint", + description: "Logs out the authenticated user by invalidating their session or token.", + responses: { + 200: { + description: "Logout successful", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { + type: "boolean", + example: true, + }, + status: { + type: "number", + example: 200, + }, + message: { + type: "string", + example: "Logout successful", + }, + data: { + type: "object", + description: "Details about the logout operation. This only returned in development environment.", + properties: { + id: { + type: "string", + example: "123e4567-e89b-12d3-a456-426614174000", + }, + isAuthenticated: { + type: "boolean", + example: false, + }, + validUntil: { + type: "string", + format: "date-time", + example: "2024-12-31T23:59:59Z", + }, + userId: { + type: "string", + example: "user_12345", + }, + deletedAt: { + type: "string", + format: "date-time", + example: "2024-01-02T12:00:00Z", + }, + createdAt: { + type: "string", + format: "date-time", + example: "2024-01-01T12:00:00Z", + }, + updatedAt: { + type: "string", + format: "date-time", + example: "2024-01-02T12:00:00Z", + }, + deviceType: { + type: "string", + example: "Desktop", + }, + deviceOs: { + type: "string", + example: "Windows 10", + }, + deviceIp: { + type: "string", + example: "192.168.1.1", + }, + browser: { + type: "string", + example: "Chrome 89.0.4389.82", + }, + isOnline: { + type: "boolean", + example: false, + }, + lastOnline: { + type: "string", + format: "date-time", + example: "2024-01-02T12:00:00Z", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; From b5a0c2eda6325cb19e17ed1faac7e11273585012 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Wed, 11 Mar 2026 10:11:09 +0700 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=9A=A8=20fix:=20resolve=20linting=20t?= =?UTF-8?q?ype=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/helpers/http/userHeader/getUserHeaderInformation/index.ts | 1 - src/modules/auth/schemas/getOauthProviders.schema.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/helpers/http/userHeader/getUserHeaderInformation/index.ts b/src/helpers/http/userHeader/getUserHeaderInformation/index.ts index 267a61a..8b18cbe 100644 --- a/src/helpers/http/userHeader/getUserHeaderInformation/index.ts +++ b/src/helpers/http/userHeader/getUserHeaderInformation/index.ts @@ -1,4 +1,3 @@ -import { Context } from "elysia"; import { UserHeaderInformation } from "./types"; export interface ClientInfoHeader { diff --git a/src/modules/auth/schemas/getOauthProviders.schema.ts b/src/modules/auth/schemas/getOauthProviders.schema.ts index 1ec20eb..056d741 100644 --- a/src/modules/auth/schemas/getOauthProviders.schema.ts +++ b/src/modules/auth/schemas/getOauthProviders.schema.ts @@ -1,4 +1,3 @@ -import { success } from "zod"; import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; export const getOauthProvidersSchema = {