This commit is contained in:
bipproduction
2025-10-28 15:49:46 +08:00
parent 65b24ab031
commit 3e2245da29
3 changed files with 35 additions and 20 deletions

View File

@@ -4,6 +4,7 @@
"": { "": {
"name": "bun-react-template", "name": "bun-react-template",
"dependencies": { "dependencies": {
"@elysiajs/bearer": "^1.4.1",
"@elysiajs/cors": "^1.4.0", "@elysiajs/cors": "^1.4.0",
"@elysiajs/eden": "^1.4.4", "@elysiajs/eden": "^1.4.4",
"@elysiajs/jwt": "^1.4.0", "@elysiajs/jwt": "^1.4.0",
@@ -48,6 +49,8 @@
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
"@elysiajs/bearer": ["@elysiajs/bearer@1.4.1", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-vLLSMEVsLKp/8p/eoAbXZdXKRs1jEQO4OkrfcKM2x8FkiK2aKNcFgLID45bH+6rYbCf8Ihg0NKw59zxMLl43OQ=="],
"@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="], "@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="],
"@elysiajs/eden": ["@elysiajs/eden@1.4.4", "", { "peerDependencies": { "elysia": ">= 1.4.0-exp.0" } }, "sha512-/LVqflmgUcCiXb8rz1iRq9Rx3SWfIV/EkoNqDFGMx+TvOyo8QHAygFXAVQz7RHs+jk6n6mEgpI6KlKBANoErsQ=="], "@elysiajs/eden": ["@elysiajs/eden@1.4.4", "", { "peerDependencies": { "elysia": ">= 1.4.0-exp.0" } }, "sha512-/LVqflmgUcCiXb8rz1iRq9Rx3SWfIV/EkoNqDFGMx+TvOyo8QHAygFXAVQz7RHs+jk6n6mEgpI6KlKBANoErsQ=="],

View File

@@ -11,6 +11,7 @@
"lint": "bunx oxlint src" "lint": "bunx oxlint src"
}, },
"dependencies": { "dependencies": {
"@elysiajs/bearer": "^1.4.1",
"@elysiajs/cors": "^1.4.0", "@elysiajs/cors": "^1.4.0",
"@elysiajs/eden": "^1.4.4", "@elysiajs/eden": "^1.4.4",
"@elysiajs/jwt": "^1.4.0", "@elysiajs/jwt": "^1.4.0",

View File

@@ -1,51 +1,62 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { Elysia } from 'elysia'
import jwt, { type JWTPayloadSpec } from '@elysiajs/jwt' import jwt, { type JWTPayloadSpec } from '@elysiajs/jwt'
import Elysia from 'elysia' import bearer from '@elysiajs/bearer'
import { prisma } from '../lib/prisma' import { prisma } from '../lib/prisma'
// =========================================================
// JWT Secret Validation
// =========================================================
const secret = process.env.JWT_SECRET const secret = process.env.JWT_SECRET
if (!secret) throw new Error('JWT_SECRET environment variable is missing')
// =========================================================
// Auth Middleware Plugin
// =========================================================
export default function apiAuth(app: Elysia) { export default function apiAuth(app: Elysia) {
if (!secret) { if (!secret) throw new Error('JWT_SECRET environment variable is missing')
throw new Error('JWT_SECRET is not defined')
}
return app return app
// Register Bearer and JWT plugins
.use(bearer()) // ✅ Extracts Bearer token automatically (case-insensitive)
.use( .use(
jwt({ jwt({
name: 'jwt', name: 'jwt',
secret, secret,
}) })
) )
.derive(async ({ cookie, headers, jwt }) => {
let token: string | undefined
if (cookie?.token?.value) { // Derive user from JWT or cookie
token = cookie.token.value as any .derive(async ({ bearer, cookie, jwt }) => {
} // Normalize token type to string or undefined
if (headers['x-token']?.startsWith('Bearer ')) { const token =
token = (headers['x-token'] as string).slice(7) (typeof bearer === 'string' ? bearer : undefined) ??
} (typeof cookie?.token?.value === 'string' ? cookie.token.value : undefined)
if (headers['authorization']?.startsWith('Bearer ')) {
token = (headers['authorization'] as string).slice(7) let user: Awaited<ReturnType<typeof prisma.user.findUnique>> | null = null
}
let user: null | Awaited<ReturnType<typeof prisma.user.findUnique>> = null
if (token) { if (token) {
try { try {
const decoded = (await jwt.verify(token)) as JWTPayloadSpec const decoded = (await jwt.verify(token)) as JWTPayloadSpec
if (decoded.sub) {
if (decoded?.sub && typeof decoded.sub === 'string') {
user = await prisma.user.findUnique({ user = await prisma.user.findUnique({
where: { id: decoded.sub as string }, where: { id: decoded.sub },
}) })
} }
} catch (err) { } catch (err) {
console.warn('[SERVER][apiAuth] Invalid token', err) console.warn('[SERVER][apiAuth] Invalid token:', (err as Error).message)
} }
} }
return { user } return { user }
}) })
.onBeforeHandle(({ user, set }) => {
// Protect all routes by default
.onBeforeHandle(({ user, set, request }) => {
// Whitelist public routes if needed
const publicPaths = ['/auth/login', '/auth/register', '/public']
if (publicPaths.some((path) => request.url.includes(path))) return
if (!user) { if (!user) {
set.status = 401 set.status = 401
return { error: 'Unauthorized' } return { error: 'Unauthorized' }