Compare commits

...

2 Commits

Author SHA1 Message Date
864a919680 📝 docs: complete documentation for auth module
Some checks failed
Integration Tests / integration-tests (pull_request) Failing after 42s
2026-03-11 10:07:33 +07:00
da74f5e3e1 📦 chore: snapshot commit before major changes 2026-03-11 09:24:25 +07:00
12 changed files with 414 additions and 49 deletions

View File

@ -10,25 +10,14 @@ export interface ClientInfoHeader {
ip: string; ip: string;
} }
export const getUserHeaderInformation = ( export const getUserHeaderInformation = (clientInfo: string): UserHeaderInformation => {
ctx: Context, const clientInfoHeader = (JSON.parse(clientInfo) as ClientInfoHeader) ?? ("unknown" as string);
): UserHeaderInformation => {
const clientInfoHeader =
(JSON.parse(
ctx.request.headers.get("x-client-info") as string,
) as ClientInfoHeader) ?? ("unknown" as string);
const userHeaderInformation = { const userHeaderInformation = {
ip: clientInfoHeader.ip ?? "unknown", ip: clientInfoHeader.ip ?? "unknown",
deviceType: clientInfoHeader.deviceType ?? "unknown", deviceType: clientInfoHeader.deviceType ?? "unknown",
deviceOS: deviceOS: (clientInfoHeader.os ?? "unknown") + " " + (clientInfoHeader.osVersion ?? "unknown"),
(clientInfoHeader.os ?? "unknown") + browser: (clientInfoHeader.browser ?? "unknown") + " " + (clientInfoHeader.browserVersion ?? "unknown"),
" " +
(clientInfoHeader.osVersion ?? "unknown"),
browser:
(clientInfoHeader.browser ?? "unknown") +
" " +
(clientInfoHeader.browserVersion ?? "unknown"),
}; };
return userHeaderInformation; return userHeaderInformation;

View File

@ -1,14 +1,17 @@
import { Context } from "elysia"; import { Context, Static } from "elysia";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { githubCallbackService } from "../services/http/githubCallback.service"; import { githubCallbackService } from "../services/http/githubCallback.service";
import { mainErrorHandler } from "../../../helpers/error/handler"; import { mainErrorHandler } from "../../../helpers/error/handler";
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation"; import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
import { githubCallbackSchema } from "../schemas/githubCallback.schema";
export const githubCallbackController = async ( export const githubCallbackController = async (ctx: {
ctx: Context & { query: { code: string; callbackURI: string } } set: Context["set"];
) => { query: Static<typeof githubCallbackSchema.query>;
headers: Static<typeof githubCallbackSchema.headers>;
}) => {
try { try {
const userHeaderInfo = getUserHeaderInformation(ctx); const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]);
const authToken = await githubCallbackService(ctx.query, userHeaderInfo); const authToken = await githubCallbackService(ctx.query, userHeaderInfo);
return returnWriteResponse(ctx.set, 200, "Authenticated successfully!", { return returnWriteResponse(ctx.set, 200, "Authenticated successfully!", {

View File

@ -1,21 +1,18 @@
import { Context } from "elysia"; import { Context, Static } from "elysia";
import { returnReadResponse } from "../../../helpers/callback/httpResponse"; import { returnReadResponse } from "../../../helpers/callback/httpResponse";
import { githubRequestService } from "../services/http/githubRequest.service"; import { githubRequestService } from "../services/http/githubRequest.service";
import { mainErrorHandler } from "../../../helpers/error/handler"; import { mainErrorHandler } from "../../../helpers/error/handler";
import { githubRequestSchema } from "../schemas/githubRequest.schema";
export const githubRequestController = async ( export const githubRequestController = async (ctx: {
ctx: Context & { query: { callback?: string } }, set: Context["set"];
) => { query: Static<typeof githubRequestSchema.query>;
}) => {
try { try {
const loginUrl = await githubRequestService(ctx.query.callback); const loginUrl = await githubRequestService(ctx.query.callback);
return returnReadResponse( return returnReadResponse(ctx.set, 200, "GitHub login URL created successfully.", {
ctx.set,
200,
"Login URL generated successfully",
{
endpointUrl: loginUrl, endpointUrl: loginUrl,
}, });
);
} catch (error) { } catch (error) {
return mainErrorHandler(ctx.set, error); return mainErrorHandler(ctx.set, error);
} }

View File

@ -1,17 +1,20 @@
import { Context } from "elysia"; import { Context, Static } from "elysia";
import { returnReadResponse } from "../../../helpers/callback/httpResponse"; import { returnReadResponse } from "../../../helpers/callback/httpResponse";
import { mainErrorHandler } from "../../../helpers/error/handler"; import { mainErrorHandler } from "../../../helpers/error/handler";
import { googleCallbackService } from "../services/http/googleCallback.service"; import { googleCallbackService } from "../services/http/googleCallback.service";
import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation"; import { getUserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation";
import { googleCallbackSchema } from "../schemas/googleCallback.schema";
export const googleCallbackController = async ( export const googleCallbackController = async (ctx: {
ctx: Context & { query: { code: string; state: string; callbackURI: string } } set: Context["set"];
) => { query: Static<typeof googleCallbackSchema.query>;
headers: Static<typeof googleCallbackSchema.headers>;
}) => {
try { try {
const userHeaderInfo = getUserHeaderInformation(ctx); const userHeaderInfo = getUserHeaderInformation(ctx.headers["x-client-info"]);
const authToken = await googleCallbackService(ctx.query, userHeaderInfo); const authToken = await googleCallbackService(ctx.query, userHeaderInfo);
return returnReadResponse(ctx.set, 200, "Authenticated successfully!", { return returnReadResponse(ctx.set, 200, "Authentication successful!", {
authToken, authToken,
}); });
} catch (error) { } catch (error) {

View File

@ -1,14 +1,16 @@
import { Context } from "elysia"; import { Context, Static } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler"; import { mainErrorHandler } from "../../../helpers/error/handler";
import { googleRequestService } from "../services/http/googleRequest.service"; import { googleRequestService } from "../services/http/googleRequest.service";
import { returnReadResponse } from "../../../helpers/callback/httpResponse"; import { returnReadResponse } from "../../../helpers/callback/httpResponse";
import { googleRequestSchema } from "../schemas/googleRequest.schema";
export const googleRequestController = async ( export const googleRequestController = async (ctx: {
ctx: Context & { query: { callback?: string } } set: Context["set"];
) => { query: Static<typeof googleRequestSchema.query>;
}) => {
try { try {
const loginUrl = await googleRequestService(ctx.query.callback); 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, endpointUrl: loginUrl,
}); });
} catch (error) { } catch (error) {

View File

@ -9,13 +9,19 @@ import { tokenValidationController } from "./controllers/tokenValidation.control
import { logoutController } from "./controllers/logout.controller"; import { logoutController } from "./controllers/logout.controller";
import { tokenValidationSchema } from "./schemas/tokenValidation.schema"; import { tokenValidationSchema } from "./schemas/tokenValidation.schema";
import { getOauthProvidersSchema } from "./schemas/getOauthProviders.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";
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"] }) export const authModule = new Elysia({ prefix: "/auth", tags: ["Authentication"] })
.post("/token/validate", tokenValidationController, tokenValidationSchema) .post("/token/validate", tokenValidationController, tokenValidationSchema)
.get("/providers", getOauthProvidersController, getOauthProvidersSchema) .get("/providers", getOauthProvidersController, getOauthProvidersSchema)
.get("/providers/:name/callback", getCallbackProviderUrlController) .get("/providers/:name/callback", getCallbackProviderUrlController, getCallbackProviderUrlSchema)
.get("/github", githubRequestController) .get("/google", googleRequestController, googleRequestSchema)
.get("/github/callback", githubCallbackController) .get("/google/callback", googleCallbackController, googleCallbackSchema)
.get("/google", googleRequestController) .get("/github", githubRequestController, githubRequestSchema)
.get("/google/callback", googleCallbackController) .get("/github/callback", githubCallbackController, githubCallbackSchema)
.post("/logout", logoutController); .post("/logout", logoutController, logoutSchema);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;