From 98abf54a239279c63486212ff2e2f6fc1d04e5dd Mon Sep 17 00:00:00 2001 From: "brandon.skewes@outlook.com" Date: Sun, 14 Dec 2025 15:15:14 +1100 Subject: [PATCH] jwt middleware --- backend/app/bun.lock | 5 ++++ backend/app/package.json | 1 + backend/app/src/api/index.ts | 2 ++ backend/app/src/api/protected.ts | 11 +++++++++ backend/app/src/auth/index.ts | 41 ++++++++++++++++++++++++++++++++ backend/app/src/index.ts | 2 ++ 6 files changed, 62 insertions(+) create mode 100644 backend/app/src/api/protected.ts create mode 100644 backend/app/src/auth/index.ts diff --git a/backend/app/bun.lock b/backend/app/bun.lock index ff3a6bc..2d4483d 100644 --- a/backend/app/bun.lock +++ b/backend/app/bun.lock @@ -7,6 +7,7 @@ "dependencies": { "@bogeychan/elysia-logger": "^0.1.10", "@elysiajs/cors": "^1.4.0", + "@elysiajs/jwt": "^1.4.0", "@elysiajs/openapi": "^1.4.11", "elysia": "^1.4.18", "logixlysia": "^5.3.0", @@ -26,6 +27,8 @@ "@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="], + "@elysiajs/jwt": ["@elysiajs/jwt@1.4.0", "", { "dependencies": { "jose": "^6.0.11" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Z0PvZhQxdDeKZ8HslXzDoXXD83NKExNPmoiAPki3nI2Xvh5wtUrBH+zWOD17yP14IbRo8fxGj3L25MRCAPsgPA=="], + "@elysiajs/openapi": ["@elysiajs/openapi@1.4.11", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-d75bMxYJpN6qSDi/z9L1S7SLk1S/8Px+cTb3W2lrYzU8uQ5E0kXdy1oOMJEfTyVsz3OA19NP9KNxE7ztSbLBLg=="], "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], @@ -70,6 +73,8 @@ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], "logixlysia": ["logixlysia@5.3.0", "", { "dependencies": { "chalk": "^5.6.2", "elysia": "^1.4.10", "pino": "^10.0.0", "pino-pretty": "^13.1.2" }, "peerDependencies": { "typescript": "^5.2.2" }, "os": [ "linux", "win32", "darwin", ] }, "sha512-RXjITx+o3ejV3XHaqjTGyoxeKFi6V0fY+uVO1K+VGDGOVic3ViVRFxOTF9+SxvmZAMNTa/G6566qLNEmkYDmFw=="], diff --git a/backend/app/package.json b/backend/app/package.json index e2b2a69..486b558 100644 --- a/backend/app/package.json +++ b/backend/app/package.json @@ -8,6 +8,7 @@ "dependencies": { "@bogeychan/elysia-logger": "^0.1.10", "@elysiajs/cors": "^1.4.0", + "@elysiajs/jwt": "^1.4.0", "@elysiajs/openapi": "^1.4.11", "elysia": "^1.4.18", "logixlysia": "^5.3.0" diff --git a/backend/app/src/api/index.ts b/backend/app/src/api/index.ts index e5aa0b8..fb62b28 100644 --- a/backend/app/src/api/index.ts +++ b/backend/app/src/api/index.ts @@ -1,6 +1,8 @@ import { Elysia } from "elysia"; import { health } from "./health"; +import { protectedApi } from "./protected"; export const api = new Elysia({ prefix: "/api" }) .use(health) + .use(protectedApi) diff --git a/backend/app/src/api/protected.ts b/backend/app/src/api/protected.ts new file mode 100644 index 0000000..ef10ccf --- /dev/null +++ b/backend/app/src/api/protected.ts @@ -0,0 +1,11 @@ +import { Elysia } from "elysia"; +import { authGuard, jwtConfig } from "../auth/index"; + +export const protectedApi = new Elysia() + .use(jwtConfig) + .guard(authGuard, (router) => + router + .get("/secret", () => { + return "hehe secret"; + }) + ) diff --git a/backend/app/src/auth/index.ts b/backend/app/src/auth/index.ts new file mode 100644 index 0000000..1ba38f3 --- /dev/null +++ b/backend/app/src/auth/index.ts @@ -0,0 +1,41 @@ +import { Elysia } from 'elysia' +import { jwt } from '@elysiajs/jwt' + +export const jwtConfig = jwt({ + name: 'jwt', + exp: '7d', + secret: 'Fischl von Luftschloss Narfidort' +}) + +export const auth = new Elysia({ prefix: "/auth" }) + .use( + jwtConfig + ) + .get('/sign/:name', ({ jwt, params: { name } }) => { + return jwt.sign({ name }) + }) + .get('/profile', async ({ jwt, status, headers: { authorization } }) => { + const profile = await jwt.verify(authorization) + + if (!profile) + return status(401, 'Unauthorized') + + return `Hello ${profile.name}` + }) + + +export const authGuard = { + async beforeHandle({ jwt, request, status }: any) { + const authHeader = request.headers.get("authorization"); + if (!authHeader) return status(401, "Missing authorization header"); + + + const ok = await jwt.verify(authHeader); + if (!ok) return status(401, "Invalid or expired token"); + + return { user: ok }; + } +} + + + diff --git a/backend/app/src/index.ts b/backend/app/src/index.ts index 0324330..3f6c474 100644 --- a/backend/app/src/index.ts +++ b/backend/app/src/index.ts @@ -4,6 +4,7 @@ import { ws } from "./ws/index"; import logixlysia from "logixlysia"; import { openapi } from "@elysiajs/openapi"; import { cors } from "@elysiajs/cors"; +import { auth } from "./auth/index"; const app = new Elysia({ name: "Rat Chat" }) .use(cors()) @@ -28,6 +29,7 @@ const app = new Elysia({ name: "Rat Chat" }) '🦊 {now} {level} {duration} {method} {pathname} {status} {message} {ip}' } })) + .use(auth) .use(api) .use(ws)