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;