Compare commits

...

2 Commits

Author SHA1 Message Date
f320118880 fix(auth): found some bugs in the jwt token 2025-02-21 21:54:26 -06:00
026583815c refactor(lst): register added in 2025-02-21 15:26:56 -06:00
38 changed files with 682 additions and 485 deletions

View File

@@ -12,7 +12,7 @@ post {
body:json {
{
"username":"admin",
"password": "password123"
"username": "adm_matthes",
"password": "nova0511"
}
}

View File

@@ -0,0 +1,19 @@
meta {
name: Register
type: http
seq: 4
}
post {
url: http://localhost:3000/api/auth/register
body: json
auth: none
}
body:json {
{
"username": "adm_matthes",
"email":"blake@alpla.com",
"password": "nNova0511!"
}
}

View File

@@ -0,0 +1,18 @@
meta {
name: Api Hit
type: http
seq: 1
}
post {
url: http://localhost:3000/api/hits
body: json
auth: none
}
body:json {
{
"name": "something",
"arrayData": "{someghjdsfsd}"
}
}

View File

@@ -1,8 +1,10 @@
import {defineConfig} from "drizzle-kit";
const database = process.env.DATABASE_URL || "";
export default defineConfig({
dialect: "postgresql", // 'mysql' | 'sqlite' | 'turso'
schema: "./schema",
schema: "./server/database/schema/",
out: "./server/database/migrations",
dbCredentials: {
url: "postgresql://postgres:nova0511@localhost:5432/lst_db",
url: database,
},
});

View File

@@ -10,7 +10,7 @@ interface LstCardProps {
export function LstCard({children, className = "", style = {}}: LstCardProps) {
return (
<div className="m-auto">
<Card className={`border-solid border-2 border-[#00659c] ${className}`} style={style}>
<Card className={`border-solid border-1 border-[#00659c] ${className}`} style={style}>
{children}
</Card>
</div>

View File

@@ -50,7 +50,7 @@ function RouteComponent() {
const onSubmitLogin = async (value: z.infer<typeof FormSchema>) => {
// Do something with form data
console.log(value);
// first update the rememberMe incase it was selected
if (value.rememberMe) {
localStorage.setItem("rememberMe", value.rememberMe.toString());
@@ -69,17 +69,13 @@ function RouteComponent() {
body: JSON.stringify({username: value.username, password: value.password}),
});
// if (!response.ok) {
// throw new Error("Invalid credentials");
// }
const data = await response.json();
// Store token in localStorage
// localStorage.setItem("auth_token", data.data.token);
setSession(data.data.user, data.data.token);
toast.success(`You are logged in as ${data.data.user.username}`);
setSession(data.user, data.token);
toast.success(`You are logged in as ${data.user.username}`);
router.navigate({to: "/"});
} catch (err) {
toast.error("Invalid credentials");

10
globals.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
JWT_SECRET: string;
JWT_EXPIRES: string;
}
}
}
export {};

View File

@@ -4,10 +4,10 @@
"description": "",
"main": "index.ts",
"scripts": {
"dev": "concurrently -n 'server,frontend' -c '#007755,#2f6da3' 'cd server && bun run dev' 'cd frontend && bun run dev'",
"dev:server": "bun --env-file .env --watch server/index.ts",
"dev:ocme": "bun --env-file .env --watch ocme/index.ts",
"dev:frontend": "cd frontend && bunx --bun vite",
"dev": "concurrently -n 'server,frontend' -c '#007755,#2f6da3' 'bun --watch server/index.ts' 'cd frontend && bunx --bun vite'",
"dev:server": "bun --watch server/index.ts",
"dev:ocme": "bun --watch ocme/index.ts",
"dev:front": "cd frontend && bunx --bun vite",
"build:server": "cd apps/server && bun build index.js --outdir ../../dist/server",
"build:ocme": "rimraf dist/ocme && cd apps/ocme && bun build index.js --outdir ../../dist/ocme",
"build:front": "cd frontend && rimraf frontend/dist && bun run build",
@@ -15,7 +15,8 @@
"commit": "cz",
"clean": "rimraf dist/server",
"deploy": "standard-version --conventional-commits",
"ui:add": "cd frontend && bun shadcn add "
"ui:add": "cd frontend && bun shadcn add ",
"db:dev": "bun drizzle-kit generate && bun drizzle-kit migrate"
},
"keywords": [],
"author": "",
@@ -23,8 +24,9 @@
"dependencies": {
"@dotenvx/dotenvx": "^1.35.0",
"@hono/zod-openapi": "^0.18.4",
"@scalar/hono-api-reference": "^0.5.174",
"@scalar/hono-api-reference": "^0.5.175",
"@types/bun": "^1.2.2",
"@types/jsonwebtoken": "^9.0.8",
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"compression": "^1.8.0",
@@ -35,12 +37,17 @@
"hono": "^4.7.1",
"http-proxy-middleware": "^3.0.3",
"jsonwebtoken": "^9.0.2",
"pg": "^8.13.3",
"postgres": "^3.4.5",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/js-cookie": "^3.0.6",
"@types/pg": "^8.11.11",
"concurrently": "^9.1.2",
"cz-conventional-changelog": "^3.3.0",
"drizzle-kit": "^0.30.4",
"rimraf": "^6.0.1",
"standard-version": "^9.5.0",
"typescript": "~5.7.3"

View File

@@ -1,175 +0,0 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

View File

@@ -1,15 +0,0 @@
# database
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.2. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

View File

@@ -0,0 +1,7 @@
import {drizzle} from "drizzle-orm/postgres-js";
import postgres from "postgres";
const database = process.env.DATABASE_URL || "";
const queryClient = postgres(database);
export const db = drizzle({client: queryClient});

View File

@@ -1,12 +0,0 @@
CREATE TABLE "users" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"title" text NOT NULL,
"passwordToken" text NOT NULL,
"passwordTokenExpires" timestamp,
"active" boolean DEFAULT true NOT NULL,
"pingcode" numeric,
"add_Date" timestamp DEFAULT now(),
"add_User" text DEFAULT 'LST_System' NOT NULL,
"upd_date" timestamp DEFAULT now()
);

View File

@@ -1,13 +0,0 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1739914245651,
"tag": "0000_stormy_thunderbolt",
"breakpoints": true
}
]
}

View File

@@ -1,7 +0,0 @@
import {drizzle} from "drizzle-orm/postgres-js";
import postgres from "postgres";
import "../../load-env";
const queryClient = postgres("postgresql://postgres:nova0511@localhost:5432/lst_db");
export const db = drizzle(queryClient);

View File

@@ -0,0 +1,15 @@
CREATE TABLE "users" (
"user_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"username" text NOT NULL,
"email" text NOT NULL,
"password" text NOT NULL,
"passwordToken" text,
"passwordTokenExpires" timestamp,
"active" boolean DEFAULT true NOT NULL,
"pingcode" numeric,
"lastLogin" timestamp DEFAULT now(),
"add_User" text DEFAULT 'LST_System' NOT NULL,
"add_Date" timestamp DEFAULT now(),
"upd_User" text DEFAULT 'LST_System' NOT NULL,
"upd_date" timestamp DEFAULT now()
);

View File

@@ -0,0 +1 @@
CREATE UNIQUE INDEX "username" ON "users" USING btree ("username");

View File

@@ -1,5 +1,5 @@
{
"id": "d0e2effa-c6ac-4f81-b546-ef6b10037eca",
"id": "d1b4a8a6-caa3-4c45-a3a1-cdfc99ca7bea",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@@ -8,20 +8,27 @@
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"title": {
"name": "title",
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
@@ -30,7 +37,7 @@
"name": "passwordToken",
"type": "text",
"primaryKey": false,
"notNull": true
"notNull": false
},
"passwordTokenExpires": {
"name": "passwordTokenExpires",
@@ -51,8 +58,8 @@
"primaryKey": false,
"notNull": false
},
"add_Date": {
"name": "add_Date",
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
@@ -65,6 +72,20 @@
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",

View File

@@ -0,0 +1,133 @@
{
"id": "d6c99236-0eea-49f1-817b-13139c0f42f5",
"prevId": "d1b4a8a6-caa3-4c45-a3a1-cdfc99ca7bea",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"passwordToken": {
"name": "passwordToken",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordTokenExpires": {
"name": "passwordTokenExpires",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"pingcode": {
"name": "pingcode",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"username": {
"name": "username",
"columns": [
{
"expression": "username",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,20 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1740160921910,
"tag": "0000_typical_frightful_four",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1740161259149,
"tag": "0001_sharp_pet_avengers",
"breakpoints": true
}
]
}

View File

@@ -1,20 +0,0 @@
{
"name": "database",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest",
"@types/pg": "^8.11.11",
"drizzle-kit": "^0.30.4",
"tsx": "^4.19.2"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"drizzle-orm": "^0.39.3",
"drizzle-zod": "^0.7.0",
"pg": "^8.13.3",
"postgres": "^3.4.5"
}
}

View File

@@ -1,27 +1,35 @@
import {text, pgTable, serial, numeric, index, timestamp, boolean} from "drizzle-orm/pg-core";
import {text, pgTable, numeric, index, timestamp, boolean, uuid, uniqueIndex} from "drizzle-orm/pg-core";
import {createInsertSchema, createSelectSchema} from "drizzle-zod";
import {z} from "zod";
export const users = pgTable("users", {
user_id: serial("id").primaryKey(),
username: text("user_id").notNull(),
email: text("title").notNull(),
passwordToken: text("passwordToken").notNull(),
export const users = pgTable(
"users",
{
user_id: uuid("user_id").defaultRandom().primaryKey(),
username: text("username").notNull(),
email: text("email").notNull(),
password: text("password").notNull(),
passwordToken: text("passwordToken"),
passwordTokenExpires: timestamp("passwordTokenExpires"),
acitve: boolean("active").default(true).notNull(),
pinCode: numeric("pingcode"),
lastLogin: timestamp("add_Date").defaultNow(),
lastLogin: timestamp("lastLogin").defaultNow(),
add_User: text("add_User").default("LST_System").notNull(),
add_Date: timestamp("add_Date").defaultNow(),
upd_user: text("add_User").default("LST_System").notNull(),
upd_user: text("upd_User").default("LST_System").notNull(),
upd_date: timestamp("upd_date").defaultNow(),
});
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
uniqueIndex("username").on(table.username),
]
);
// Schema for inserting a user - can be used to validate API requests
export const insertUsersSchema = createInsertSchema(users, {
username: z.string().min(3, {message: "Username must be at least 3 characters"}),
email: z.string().email({message: "Invalid email"}),
passwordToken: z.string().min(8, {message: "Password must be at least 8 characters"}),
password: z.string().min(8, {message: "Password must be at least 8 characters"}),
});
// Schema for selecting a Expenses - can be used to validate API responses
export const selectExpensesSchema = createSelectSchema(users);

View File

@@ -1,27 +0,0 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}

View File

@@ -1,16 +0,0 @@
{
"name": "lstv2-server",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"dev": "bun --env-file ../.env --watch ./index.ts",
"build": "bun build ./index.ts"
},
"devDependencies": {
"typescript": "^5.7.3"
},
"dependencies": {
"@scalar/hono-api-reference": "^0.5.174"
}
}

View File

@@ -6,7 +6,8 @@ import {OpenAPIHono} from "@hono/zod-openapi";
//routes
import auth from "./services/auth/authService";
import scalar from "./route/scalar";
import scalar from "./services/general/route/scalar";
import apiHits from "./services/general/route/apitHits";
// services
import {ocmeService} from "./services/ocme/ocmeServer";
@@ -38,10 +39,10 @@ app.all("/ocme/*", async (c) => {
return ocmeService(c);
});
const routes = [scalar, auth] as const;
const routes = [scalar, auth, apiHits] as const;
routes.forEach((route) => {
app.route("/", route);
app.route("/api/", route);
});
//app.basePath("/api/auth").route("/login", login).route("/session", session).route("/register", register);

View File

@@ -0,0 +1,39 @@
import {z, ZodError} from "zod";
import {Context} from "hono";
// Define the request body schema
const requestSchema = z.object({
ip: z.string().optional(),
endpoint: z.string(),
action: z.string().optional(),
stats: z.string().optional(),
});
type ApiHitData = z.infer<typeof requestSchema>;
export const apiHit = async (
c: Context,
data: unknown
): Promise<{success: boolean; data?: ApiHitData; errors?: any[]}> => {
// console.log(data);
try {
// Extract IP from request headers or connection info
const forwarded = c.req.header("host");
//console.log(forwarded);
// Validate the data
const validatedData = requestSchema.parse(data);
// Proceed with the validated data
// console.log("Validated Data:", validatedData);
return {success: true, data: validatedData};
} catch (error) {
// Explicitly check if the error is an instance of ZodError
if (error instanceof ZodError) {
// console.log({success: false, errors: error.errors});
return {success: false, errors: error.errors};
}
// Catch other unexpected errors
return {success: false, errors: [{message: "An unknown error occurred"}]};
}
};

View File

@@ -5,8 +5,8 @@ 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);
app.route("auth/login", login);
app.route("auth/register", register);
app.route("auth/session", session);
export default app;

View File

@@ -1,28 +1,39 @@
import {sign, verify} from "jsonwebtoken";
import {db} from "../../../../database/dbClient";
import {users} from "../../../../database/schema/users";
import {eq} from "drizzle-orm";
import {checkPassword} from "../utils/checkPassword";
/**
* Authenticate a user and return a JWT.
*/
const fakeUsers = [
{id: 1, username: "admin", password: "password123", role: "admin"},
{id: 2, username: "user", password: "pass", role: "user"},
{id: 3, username: "user2", password: "password123", role: "user"},
];
export function login(
export async function login(
username: string,
password: string
): {token: string; user: {id: number; username: string; role: string}} {
const user = fakeUsers.find((u) => u.username === username && u.password === password);
if (!user) {
throw new Error("Invalid credentials");
): Promise<{token: string; user: {user_id: string; username: string}}> {
const user = await db.select().from(users).where(eq(users.username, username));
if (user.length === 0) {
throw new Error("Invalid or Missing user");
}
// check the password
const checkedPass = await checkPassword(password, user[0]?.password);
console.log(checkedPass);
if (!checkedPass) {
throw new Error("Invalid Password");
}
// Create a JWT
const token = sign({user}, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES,
});
const secret: string = process.env.JWT_SECRET! || "bnghsjhsd";
const expiresIn: string = process.env.JWT_EXPIRES! || "1h";
return {token, user: {id: user?.id, username: user.username, role: user.role}};
const userData = {
user_id: user[0].user_id,
username: user[0].username,
email: user[0].email,
};
const token = sign({user: userData}, secret, {expiresIn: 60 * 60});
return {token, user: {user_id: user[0].user_id, username: user[0].username}};
}

View File

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

View File

@@ -1,17 +0,0 @@
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

@@ -5,38 +5,38 @@ const app = new OpenAPIHono();
const UserSchema = z
.object({
username: z.string().min(3).openapi({example: "smith002"}),
username: z.string().optional().openapi({example: "smith002"}),
//email: z.string().optional().openapi({example: "s.smith@example.com"}),
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}}}},
request: {
body: {
content: {
"application/json": {schema: UserSchema},
},
},
},
responses: {
200: {
content: {
"application/json": {
schema: LoginResponseSchema,
schema: z.object({
success: z.boolean().openapi({example: true}),
message: z.string().openapi({example: "Logged in"}),
}),
},
},
description: "Login successful",
description: "Response message",
},
400: {
content: {
"application/json": {
@@ -52,63 +52,40 @@ const route = createRoute({
content: {
"application/json": {
schema: z.object({
message: z.string().openapi({example: "Invalid credentials"}),
success: z.boolean().openapi({example: false}),
message: z.string().openapi({example: "Username and password required"}),
}),
},
},
description: "Unauthorized",
description: "Bad request",
},
},
});
app.openapi(route, async (c) => {
let body: {username: string; password: string};
const {username, password, email} = await c.req.json();
console.log(`Trying to login`);
try {
body = await c.req.json();
} catch (error) {
return c.json({success: false, message: "Username and password required"}, 400);
if (!username || !password) {
return c.json(
{
success: false,
message: "Username and password are required",
},
400
);
}
if (!body?.username || !body?.password) {
return c.json({success: false, message: "Username and password required"}, 400);
}
const {token, user} = await login(username.toLowerCase(), password);
try {
const {token, user} = login(body.username, body.password);
const {token, user} = await login(username.toLowerCase(), password);
// Set the JWT as an HTTP-only cookie
// c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Path=/; SameSite=None; Max-Age=3600`);
//c.header("Set-Cookie", `auth_token=${token}; HttpOnly; Secure; Path=/; SameSite=None; Max-Age=3600`);
return c.json({message: "Login successful", data: {token, user}});
return c.json({success: true, message: "Login successful", user, token}, 200);
} catch (err) {
return c.json({message: err instanceof Error ? err.message : "Invalid credentials"}, 401);
return c.json({success: false, message: "Incorrect 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

@@ -1,14 +1,31 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {db} from "../../../../database/dbClient";
import {users} from "../../../../database/schema/users";
import {apiHit} from "../../../globalUtils/apitHits";
import {createPassword} from "../utils/createPassword";
import {eq} from "drizzle-orm";
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");
const UserSchema = z.object({
username: z
.string()
.regex(/^[a-zA-Z0-9_]{3,30}$/)
.openapi({example: "smith034"}),
email: z.string().email().openapi({example: "smith@example.com"}),
password: z
.string()
.min(6, {message: "Passwords must be longer than 3 characters"})
.regex(/[A-Z]/, {message: "Password must contain at least one uppercase letter"})
.regex(/[\W_]/, {message: "Password must contain at least one special character"})
.openapi({example: "Password1!"}),
});
type User = z.infer<typeof UserSchema>;
const responseSchema = z.object({
message: z.string().optional().openapi({example: "User Created"}),
});
app.openapi(
createRoute({
@@ -16,17 +33,88 @@ app.openapi(
summary: "Register a new user",
method: "post",
path: "/",
request: {params: UserSchema},
request: {
body: {
content: {
"application/json": {schema: UserSchema},
},
},
},
responses: {
200: {
content: {"application/json": {schema: UserSchema}},
content: {"application/json": {schema: responseSchema}},
description: "Retrieve the user",
},
400: {
content: {
"application/json": {
schema: z.object({
success: z.boolean().openapi({example: false}),
message: z.string().openapi({example: "Invalid credentials passed"}),
}),
},
},
description: "Retrieve the user",
},
},
}),
(c) => {
const {id} = c.req.valid("param");
return c.json({id, age: 20, name: "Ultra-man"});
async (c) => {
// apit hit
apiHit(c, {endpoint: "api/auth/register"});
let {username, email, password} = await c.req.json();
if (!username || !email || !password) {
return c.json({success: false, message: "Credentials missing"}, 400);
}
// some usernames that should be ignored
const badActors = ["admin", "root"];
if (badActors.includes(username)) {
return c.json(
{
success: false,
message: `${username} is not a valid name to be registerd please try again`,
},
400
);
}
// make sure the user dose not already exist in the system
const userCheck = await db.select().from(users).where(eq(users.username, username));
if (userCheck.length === 1) {
return c.json(
{
success: false,
message: `${username} already exists please login or reset password, if you feel this is an error please contact your admin.`,
},
400
);
}
// make sure we only send over a username that is all lowercase
username = username.toLowerCase();
// get the good kinda password
password = await createPassword(password);
try {
const user = await db
.insert(users)
.values({username, email, password})
.returning({user: users.username, email: users.email});
return c.json({message: "User Registered", user}, 200);
} catch (error) {
console.log(error);
return c.json(
{
success: false,
message: `${username} already exists please login or reset password, if you feel this is an error please contact your admin.`,
},
400
);
}
}
);

View File

@@ -3,20 +3,50 @@ import {verify} from "hono/jwt";
const session = new OpenAPIHono();
const tags = ["Auth"];
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_SECRET = process.env.JWT_SECRET!;
const route = createRoute({
tags: ["Auth"],
const UserSchema = z.object({
username: z
.string()
.regex(/^[a-zA-Z0-9_]{3,30}$/)
.openapi({example: "smith034"}),
email: z.string().email().openapi({example: "smith@example.com"}),
password: z
.string()
.min(6, {message: "Passwords must be longer than 3 characters"})
.regex(/[A-Z]/, {message: "Password must contain at least one uppercase letter"})
.regex(/[\W_]/, {message: "Password must contain at least one special character"})
.openapi({example: "Password1!"}),
});
session.openapi(
createRoute({
tags,
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: ""}}}}},
// request: {
// body: {
// content: {
// "application/json": {schema: UserSchema},
// },
// },
// },
responses: {
200: {
content: {
"application/json": {
schema: {session: ""},
schema: z.object({
data: z.object({
token: z.string().openapi({example: "sdkjhgsldkvhdakl;jvhs;adkjfhvds.kvnsad;ovhads"}),
// user: z.object({
// user_id: z.string().openapi({example: "04316c86-f086-4cc6-b3d4-cca164a26f3f"}),
// username: z.string().openapi({example: "smith"}),
// email: z.string().openapi({example: "smith@example.com"}).optional(),
// }),
}),
}),
},
},
description: "Login successful",
@@ -24,34 +54,46 @@ const route = createRoute({
401: {
content: {
"application/json": {
schema: {message: ""},
schema: z.object({
message: z.string().openapi({example: "Unathenticated"}),
}),
},
},
description: "Error of why you were not logged in.",
},
},
});
session.openapi(route, async (c) => {
}),
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);
return c.json({message: "Unauthorized"}, 401);
}
const token = authHeader?.split("Bearer ")[1] || "";
try {
const payload = await verify(token, JWT_SECRET);
//console.log(payload);
return c.json({data: {token, user: payload.user}});
} catch (err) {
return c.json({error: "Invalid or expired token"}, 401);
const payload = await verify(token, process.env.JWT_SECRET!);
return c.json({data: {token: token, user: payload.user}}, 200);
} catch (error) {}
return c.json({data: {token: "tsfds"}}, 200);
}
});
);
// const token = authHeader?.split("Bearer ")[1] || "";
// try {
// const payload = await verify(token, process.env.JWT_SECRET!);
// //console.log(payload);
// //return c.json({data: {token, user: payload.user}}, 200);
// return c.json({message: "something"});
// } catch (err) {
// return c.json({error: "Invalid or expired token"}, 401);
// }
// });
export default session;

View File

@@ -0,0 +1,10 @@
import bcrypt from "bcrypt";
export const checkPassword = async (currentPassword: string, dbPassword: string) => {
// encypt password
const pass: string | undefined = process.env.SECRET;
const checked = bcrypt.compareSync(pass + currentPassword, dbPassword);
return checked;
};

View File

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

View File

@@ -2,7 +2,7 @@ import {OpenAPIHono} from "@hono/zod-openapi";
const app = new OpenAPIHono();
// the doc endpoint
app.doc("/api", {
app.doc("/", {
openapi: "3.0.0",
info: {
version: "1.0.0",

View File

@@ -0,0 +1,53 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {apiHit} from "../../../globalUtils/apitHits";
const app = new OpenAPIHono();
// Define the request body schema
const requestSchema = z.object({
ip: z.string().optional(),
endpoint: z.string().optional(),
action: z.string().optional(),
stats: z.string().optional(),
});
// Define the response schema
const responseSchema = z.object({
message: z.string(),
});
app.openapi(
createRoute({
tags: ["api"],
summary: "Tracks the API posts and how often",
method: "post",
path: "/hits",
request: {
body: {
content: {
"application/json": {schema: requestSchema},
},
},
},
responses: {
200: {
content: {
"application/json": {schema: responseSchema},
},
description: "Response message",
},
},
}),
async (c) => {
const data = await c.req.json();
apiHit(data);
// Return response with the received data
return c.json({
message: `Received name: ${data.name}, arrayData: ${data.arrayData}`,
});
}
);
export default app;

View File

@@ -4,7 +4,7 @@ import {apiReference} from "@scalar/hono-api-reference";
const app = new OpenAPIHono();
app.get(
"/api/docs",
"/docs",
apiReference({
theme: "kepler",
layout: "classic",
@@ -56,7 +56,7 @@ app.get(
description: "Production",
},
{
url: "http://localhost:4000",
url: "http://localhost:3000",
description: "dev server",
},
],

View File

@@ -21,5 +21,5 @@
],
"outDir": "dist"
},
"include": ["./server/src", "./server/index.ts"]
"include": ["./server/src", "./server/index.ts", "./server/database"]
}