refactor(lst): refactor to monolithic completed

This commit is contained in:
2025-02-19 14:07:51 -06:00
parent b15f1d8ae8
commit dae00716ec
71 changed files with 225 additions and 624 deletions

60
server/src/app.ts Normal file
View File

@@ -0,0 +1,60 @@
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
import {serveStatic} from "hono/bun";
import {logger} from "hono/logger";
import {cors} from "hono/cors";
import {OpenAPIHono} from "@hono/zod-openapi";
//routes
import auth from "./services/auth/authService";
import scalar from "./route/scalar";
// services
import {ocmeService} from "./services/ocme/ocmeServer";
console.log(process.env.JWT_SECRET);
const app = new OpenAPIHono();
app.use("*", logger());
app.use(
"*",
cors({
origin: `http://localhost:5173`,
allowHeaders: ["X-Custom-Header", "Upgrade-Insecure-Requests"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
maxAge: 600,
credentials: true,
})
);
app.doc("/api", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "LST API",
},
});
// as we dont want to change ocme again well use a proxy to this
app.all("/ocme/*", async (c) => {
return ocmeService(c);
});
const routes = [scalar, auth] as const;
routes.forEach((route) => {
app.route("/", route);
});
//app.basePath("/api/auth").route("/login", login).route("/session", session).route("/register", register);
//auth stuff
// app.get("/api/protected", authMiddleware, (c) => {
// return c.json({success: true, message: "is authenticated"});
// });
app.get("*", serveStatic({root: "./frontend/dist"}));
app.get("*", serveStatic({path: "./frontend/dist/index.html"}));
export default app;
//export type ApiRoute = typeof apiRoute;

View File

@@ -0,0 +1,13 @@
import {OpenAPIHono} from "@hono/zod-openapi";
const app = new OpenAPIHono();
// the doc endpoint
app.doc("/api", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "LST API",
},
});
export default app;

View File

@@ -0,0 +1,79 @@
import {OpenAPIHono} from "@hono/zod-openapi";
import {apiReference} from "@scalar/hono-api-reference";
const app = new OpenAPIHono();
app.get(
"/api/docs",
apiReference({
theme: "kepler",
layout: "classic",
defaultHttpClient: {targetKey: "node", clientKey: "axios"},
pageTitle: "Lst API Reference",
hiddenClients: [
"libcurl",
"clj_http",
"httpclient",
"restsharp",
"native",
"http1.1",
"asynchttp",
"nethttp",
"okhttp",
"unirest",
"xhr",
"fetch",
"jquery",
"okhttp",
"native",
"request",
"unirest",
"nsurlsession",
"cohttp",
"curl",
"guzzle",
"http1",
"http2",
"webrequest",
"restmethod",
"python3",
"requests",
"httr",
"native",
"curl",
"httpie",
"wget",
"nsurlsession",
"undici",
],
spec: {
url: "/api",
},
baseServerURL: "https://scalar.com",
servers: [
{
url: "http://usday1vms006:3000",
description: "Production",
},
{
url: "http://localhost:4000",
description: "dev server",
},
],
// authentication: {
// preferredSecurityScheme: {'bearerAuth'},
// },
// metaData: {
// title: "Page title",
// description: "My page page",
// ogDescription: "Still about my my page",
// ogTitle: "Page title",
// ogImage: "https://example.com/image.png",
// twitterCard: "summary_large_image",
// // Add more...
// },
})
);
export default app;

View File

@@ -0,0 +1,12 @@
import {OpenAPIHono} from "@hono/zod-openapi";
import login from "./routes/login";
import register from "./routes/register";
import session from "./routes/session";
const app = new OpenAPIHono();
app.route("api/auth/login", login);
app.route("api/auth//register", register);
app.route("api/auth/session", session);
export default app;

View File

@@ -0,0 +1,25 @@
import {sign, verify} from "jsonwebtoken";
/**
* Authenticate a user and return a JWT.
*/
const fakeUsers = [
{id: 1, username: "admin", password: "password123"},
{id: 2, username: "user", password: "password123"},
{id: 3, username: "user2", password: "password123"},
];
export function login(username: string, password: string): {token: string; user: {id: number; username: string}} {
const user = fakeUsers.find((u) => u.username === username && u.password === password);
if (!user) {
throw new Error("Invalid credentials");
}
// Create a JWT
const token = sign({userId: user?.id, username: user?.username}, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES,
});
return {token, user: {id: user?.id, username: user.username}};
}

View File

@@ -0,0 +1,8 @@
/**
* Logout (clear the token).
* This is a placeholder function since JWTs are stateless.
* In a real app, you might want to implement token blacklisting.
*/
export function logout(): {message: string} {
return {message: "Logout successful"};
}

View File

@@ -0,0 +1 @@
export const registerUser = async () => {};

View File

@@ -0,0 +1,13 @@
import {sign, verify} from "jsonwebtoken";
/**
* Verify a JWT and return the decoded payload.
*/
export function verifyToken(token: string): {userId: number} {
try {
const payload = verify(token, process.env.JWT_SECRET) as {userId: number};
return payload;
} catch (err) {
throw new Error("Invalid token");
}
}

View File

@@ -0,0 +1,17 @@
import bcrypt from "bcrypt";
export const passwordUpdate = (password: string) => {
// encypt password
let pass: string = process.env.SECRET;
let salt: string = process.env.SALTING;
if (!pass || !salt) {
pass = "error";
} else {
pass = bcrypt.hashSync(process.env.SECRET + password, parseInt(process.env.SALTING));
pass = btoa(pass);
}
return pass;
};

View File

@@ -0,0 +1,41 @@
import {type MiddlewareHandler} from "hono";
import {sign, verify} from "jsonwebtoken";
export const authMiddleware: MiddlewareHandler = async (c, next) => {
const authHeader = c.req.header("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return c.json({error: "Unauthorized"}, 401);
}
const token = authHeader.split(" ")[1];
try {
const decoded = verify(token, process.env.JWT_SECRET, {ignoreExpiration: false}) as {
userId: number;
exp: number;
};
const currentTime = Math.floor(Date.now() / 1000); // Get current timestamp
const timeLeft = decoded.exp - currentTime;
// If the token has less than REFRESH_THRESHOLD seconds left, refresh it
let newToken = null;
if (timeLeft < parseInt(process.env.REFRESH_THRESHOLD)) {
newToken = sign({userId: decoded.userId}, process.env.JWT_SECRET, {expiresIn: process.env.EXPIRATION_TIME});
c.res.headers.set("Authorization", `Bearer ${newToken}`);
}
c.set("user", {id: decoded.userId});
await next();
// If a new token was generated, send it in response headers
if (newToken) {
console.log("token was refreshed");
c.res.headers.set("X-Refreshed-Token", newToken);
}
} catch (err) {
return c.json({error: "Invalid token"}, 401);
}
};

View File

@@ -0,0 +1,112 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {login} from "../controllers/login";
const app = new OpenAPIHono();
const UserSchema = z
.object({
username: z.string().min(3).openapi({example: "smith002"}),
password: z.string().openapi({example: "password123"}),
})
.openapi("User");
// Define the response schema for the login endpoint
const LoginResponseSchema = z
.object({
message: z.string().openapi({example: "Login successful"}),
user: z.object({
username: z.string().openapi({example: "smith002"}),
// Add other user fields as needed
}),
})
.openapi("LoginResponse");
const route = createRoute({
tags: ["Auth"],
summary: "Login as user",
description: "Login as a user to get a JWT token",
method: "post",
path: "/",
request: {body: {content: {"application/json": {schema: UserSchema}}}},
responses: {
200: {
content: {
"application/json": {
schema: LoginResponseSchema,
},
},
description: "Login successful",
},
400: {
content: {
"application/json": {
schema: z.object({
success: z.boolean().openapi({example: false}),
message: z.string().openapi({example: "Username and password required"}),
}),
},
},
description: "Bad request",
},
401: {
content: {
"application/json": {
schema: z.object({
message: z.string().openapi({example: "Invalid credentials"}),
}),
},
},
description: "Unauthorized",
},
},
});
app.openapi(route, async (c) => {
let body: {username: string; password: string};
try {
body = await c.req.json();
} catch (error) {
return c.json({success: false, message: "Username and password required"}, 400);
}
if (!body?.username || !body?.password) {
return c.json({success: false, message: "Username and password required"}, 400);
}
try {
const {token, user} = login(body.username, body.password);
// Set the JWT as an HTTP-only cookie
// c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Path=/; SameSite=None; Max-Age=3600`);
return c.json({message: "Login successful", data: {token, user}});
} catch (err) {
return c.json({message: err instanceof Error ? err.message : "Invalid credentials"}, 401);
}
});
/*
let body = {username: "", password: "", error: ""};
try {
body = await c.req.json();
} catch (error) {
return c.json({success: false, message: "Username and password required"}, 400);
}
if (!body?.username || !body?.password) {
return c.json({message: "Username and password required"}, 400);
}
try {
const {token, user} = login(body?.username, body?.password);
// Set the JWT as an HTTP-only cookie
c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Secure; Path=/; SameSite=None; Max-Age=3600`);
return c.json({message: "Login successful", user});
} catch (err) {
// console.log(err);
return c.json({message: err}, 401);
}
*/
export default app;

View File

@@ -0,0 +1,33 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
const app = new OpenAPIHono();
const UserSchema = z
.object({
id: z.string().openapi({example: "123"}),
name: z.string().min(3).openapi({example: "John Doe"}),
age: z.number().openapi({example: 42}),
})
.openapi("User");
app.openapi(
createRoute({
tags: ["Auth"],
summary: "Register a new user",
method: "post",
path: "/",
request: {params: UserSchema},
responses: {
200: {
content: {"application/json": {schema: UserSchema}},
description: "Retrieve the user",
},
},
}),
(c) => {
const {id} = c.req.valid("param");
return c.json({id, age: 20, name: "Ultra-man"});
}
);
export default app;

View File

@@ -0,0 +1,49 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {verify} from "hono/jwt";
const session = new OpenAPIHono();
const tags = ["Auth"];
const JWT_SECRET = "your-secret-key";
const route = createRoute({
tags: ["Auth"],
summary: "Checks a user session based on there token",
description: "Can post there via Authentiaction header or cookies",
method: "get",
path: "/",
request: {body: {content: {"application/json": {schema: {username: "", password: ""}}}}},
responses: {
200: {
content: {
"application/json": {
schema: {session: ""},
},
},
description: "Login successful",
},
},
});
session.openapi(route, async (c) => {
const authHeader = c.req.header("Authorization");
if (authHeader?.includes("Basic")) {
//
return c.json({message: "You are a Basic user! Please login to get a token"}, 401);
}
if (!authHeader) {
return c.json({error: "Unauthorized"}, 401);
}
const token = authHeader?.split("Bearer ")[1] || "";
try {
const payload = await verify(token, JWT_SECRET);
console.log(payload);
return c.json({token});
} catch (err) {
return c.json({error: "Invalid or expired token"}, 401);
}
});
export default session;

View File

@@ -0,0 +1,23 @@
import { Context } from "hono";
export const ocmeService = async (c: Context) => {
const url = new URL(c.req.url);
const ocmeUrl = `http://localhost:${
process.env.OCME_PORT
}${url.pathname.replace("/ocme", "")}`;
console.log(ocmeUrl);
const ocmeResponse = await fetch(ocmeUrl, {
method: c.req.method,
headers: c.req.raw.headers,
body:
c.req.method !== "GET" && c.req.method !== "HEAD"
? await c.req.text()
: undefined,
});
return new Response(ocmeResponse.body, {
status: ocmeResponse.status,
headers: ocmeResponse.headers,
});
};