add: auth middleware
This commit is contained in:
parent
6b2b035050
commit
35749c65a3
15
bun.lock
15
bun.lock
@ -1,6 +1,5 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "cvsa",
|
||||
@ -88,11 +87,11 @@
|
||||
"@alikia/random-key": "^1.1.1",
|
||||
"@elysiajs/cors": "^1.4.0",
|
||||
"@elysiajs/jwt": "^1.4.0",
|
||||
"@elysiajs/openapi": "^1.4.0",
|
||||
"@elysiajs/openapi": "^1.4.11",
|
||||
"@elysiajs/server-timing": "^1.4.0",
|
||||
"@rabbit-company/argon2id": "^2.1.0",
|
||||
"chalk": "^5.6.2",
|
||||
"elysia": "^1.4.0",
|
||||
"elysia": "^1.4.15",
|
||||
"elysia-ip": "^1.0.10",
|
||||
"zod": "^4.1.12",
|
||||
},
|
||||
@ -415,7 +414,7 @@
|
||||
|
||||
"@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.10", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-bEsETp/CGcs1CqH3zW6/CAI2g6d0K/g8wUuH7HwXQm0gtP18s9RnljJESuv4of3ePUoYQgy85t+dha+ABv+L/A=="],
|
||||
"@elysiajs/openapi": ["@elysiajs/openapi@1.4.11", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-d75bMxYJpN6qSDi/z9L1S7SLk1S/8Px+cTb3W2lrYzU8uQ5E0kXdy1oOMJEfTyVsz3OA19NP9KNxE7ztSbLBLg=="],
|
||||
|
||||
"@elysiajs/server-timing": ["@elysiajs/server-timing@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-vDFdHyi8Q43vgA5MaTQMA9v4/bgKrtqPrpVqVuHlMCRQgfOpvYGXPj3okSttyendG5r2bRHfyPG11lTWWIrzrQ=="],
|
||||
|
||||
@ -1537,7 +1536,7 @@
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.222", "", {}, "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w=="],
|
||||
|
||||
"elysia": ["elysia@1.4.7", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "openapi-types": ">= 12.0.0" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-6dZjPO5wBBilZPzqZpuMq/bMiHn7jQ94sweiPTTR9MsofliZJcwcRB0XHAr7AvKblOlU72P+Cu8z6JNhZK8u/A=="],
|
||||
"elysia": ["elysia@1.4.15", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-RaDqqZdLuC4UJetfVRQ4Z5aVpGgEtQ+pZnsbI4ZzEaf3l/MzuHcqSVoL/Fue3d6qE4RV9HMB2rAZaHyPIxkyzg=="],
|
||||
|
||||
"elysia-api": ["elysia-api@workspace:packages/elysia"],
|
||||
|
||||
@ -2053,6 +2052,8 @@
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
|
||||
|
||||
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
@ -2969,8 +2970,6 @@
|
||||
|
||||
"@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
|
||||
"@cvsa/backend/@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
|
||||
|
||||
"@deno/shim-deno/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||
@ -3285,8 +3284,6 @@
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"@cvsa/backend/@types/bun/bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
||||
|
||||
"@deno/shim-deno/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
74
packages/elysia/middlewares/auth.ts
Normal file
74
packages/elysia/middlewares/auth.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { Elysia } from "elysia";
|
||||
import { validateSession, User } from "@elysia/lib/auth";
|
||||
|
||||
export interface AuthenticatedContext {
|
||||
user: User;
|
||||
session: any;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication middleware that validates session from cookie or authorization header
|
||||
*
|
||||
* This middleware:
|
||||
* 1. Checks for sessionId in cookie (primary method)
|
||||
* 2. Falls back to Authorization header with Bearer token
|
||||
* 3. Validates the session using validateSession from lib/auth
|
||||
* 4. Sets user and session context for authenticated routes
|
||||
* 5. Returns 401 if authentication fails
|
||||
*/
|
||||
export const requireAuth = new Elysia({ name: "require-auth" })
|
||||
.derive(async ({ cookie, headers, set }) => {
|
||||
let sessionId: string | null = null;
|
||||
|
||||
// Try to get sessionId from cookie first
|
||||
if (cookie.sessionId && typeof cookie.sessionId.value === "string") {
|
||||
sessionId = cookie.sessionId.value;
|
||||
}
|
||||
// Fallback to Authorization header
|
||||
else if (headers.authorization) {
|
||||
const authHeader = headers.authorization;
|
||||
if (authHeader.startsWith("Bearer ")) {
|
||||
sessionId = authHeader.substring(7);
|
||||
}
|
||||
}
|
||||
|
||||
// If no sessionId found, return unauthenticated
|
||||
if (!sessionId) {
|
||||
set.status = 401;
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
isAuthenticated: false
|
||||
};
|
||||
}
|
||||
|
||||
// Validate the session
|
||||
const validationResult = await validateSession(sessionId);
|
||||
|
||||
if (!validationResult) {
|
||||
set.status = 401;
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
isAuthenticated: false
|
||||
};
|
||||
}
|
||||
|
||||
// Session is valid, return user and session context
|
||||
return {
|
||||
user: validationResult.user,
|
||||
session: validationResult.session,
|
||||
isAuthenticated: true
|
||||
};
|
||||
})
|
||||
.onBeforeHandle({ as: "scoped" }, ({ user, status }) => {
|
||||
if (!user) {
|
||||
return status(401, {
|
||||
message: "Authentication required."
|
||||
});
|
||||
}
|
||||
})
|
||||
.as("scoped");
|
||||
|
||||
export default requireAuth;
|
||||
@ -10,11 +10,11 @@
|
||||
"@alikia/random-key": "^1.1.1",
|
||||
"@elysiajs/cors": "^1.4.0",
|
||||
"@elysiajs/jwt": "^1.4.0",
|
||||
"@elysiajs/openapi": "^1.4.0",
|
||||
"@elysiajs/openapi": "^1.4.11",
|
||||
"@elysiajs/server-timing": "^1.4.0",
|
||||
"@rabbit-company/argon2id": "^2.1.0",
|
||||
"chalk": "^5.6.2",
|
||||
"elysia": "^1.4.0",
|
||||
"elysia": "^1.4.15",
|
||||
"elysia-ip": "^1.0.10",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
|
||||
@ -3,7 +3,7 @@ import { dbMain } from "@core/drizzle";
|
||||
import { relations, singer, songs } from "@core/drizzle/main/schema";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { bv2av } from "@elysia/lib/av_bv";
|
||||
import captchaMiddleware from "@elysia/middlewares/captcha";
|
||||
import { requireAuth } from "@elysia/middlewares/auth";
|
||||
|
||||
async function getSongIDFromBiliID(id: string) {
|
||||
let aid: number;
|
||||
@ -113,56 +113,62 @@ const songInfoGetHandler = new Elysia({ prefix: "/song" }).get(
|
||||
}
|
||||
);
|
||||
|
||||
const songInfoUpdateHandler = new Elysia({ prefix: "/song" }).use(captchaMiddleware).patch(
|
||||
"/:id/info",
|
||||
async ({ params, status, body }) => {
|
||||
const id = params.id;
|
||||
const songID = await getSongID(id);
|
||||
if (!songID) {
|
||||
return status(404, {
|
||||
code: "SONG_NOT_FOUND",
|
||||
message: "Given song cannot be found."
|
||||
});
|
||||
}
|
||||
const info = await getSongInfo(songID);
|
||||
if (!info) {
|
||||
return status(404, {
|
||||
code: "SONG_NOT_FOUND",
|
||||
message: "Given song cannot be found."
|
||||
});
|
||||
}
|
||||
if (body.name) {
|
||||
await dbMain.update(songs).set({ name: body.name }).where(eq(songs.id, songID));
|
||||
}
|
||||
if (body.producer) {
|
||||
await dbMain
|
||||
.update(songs)
|
||||
.set({ producer: body.producer })
|
||||
.where(eq(songs.id, songID))
|
||||
.returning();
|
||||
}
|
||||
const updatedData = await dbMain.select().from(songs).where(eq(songs.id, songID));
|
||||
return {
|
||||
message: "Successfully updated song info.",
|
||||
updated: updatedData.length > 0 ? updatedData[0] : null
|
||||
};
|
||||
},
|
||||
{
|
||||
response: {
|
||||
200: t.Object({
|
||||
message: t.String(),
|
||||
updated: t.Any()
|
||||
}),
|
||||
404: t.Object({
|
||||
message: t.String(),
|
||||
code: t.String()
|
||||
})
|
||||
const songInfoUpdateHandler = new Elysia({ prefix: "/song" })
|
||||
.use(requireAuth)
|
||||
.patch(
|
||||
"/:id/info",
|
||||
async ({ params, status, body, user }) => {
|
||||
const id = params.id;
|
||||
const songID = await getSongID(id);
|
||||
if (!songID) {
|
||||
return status(404, {
|
||||
code: "SONG_NOT_FOUND",
|
||||
message: "Given song cannot be found."
|
||||
});
|
||||
}
|
||||
const info = await getSongInfo(songID);
|
||||
if (!info) {
|
||||
return status(404, {
|
||||
code: "SONG_NOT_FOUND",
|
||||
message: "Given song cannot be found."
|
||||
});
|
||||
}
|
||||
|
||||
if (body.name) {
|
||||
await dbMain.update(songs).set({ name: body.name }).where(eq(songs.id, songID));
|
||||
}
|
||||
if (body.producer) {
|
||||
await dbMain
|
||||
.update(songs)
|
||||
.set({ producer: body.producer })
|
||||
.where(eq(songs.id, songID))
|
||||
.returning();
|
||||
}
|
||||
const updatedData = await dbMain.select().from(songs).where(eq(songs.id, songID));
|
||||
return {
|
||||
message: "Successfully updated song info.",
|
||||
updated: updatedData.length > 0 ? updatedData[0] : null
|
||||
};
|
||||
},
|
||||
body: t.Object({
|
||||
name: t.Optional(t.String()),
|
||||
producer: t.Optional(t.String())
|
||||
})
|
||||
}
|
||||
);
|
||||
{
|
||||
response: {
|
||||
200: t.Object({
|
||||
message: t.String(),
|
||||
updated: t.Any()
|
||||
}),
|
||||
401: t.Object({
|
||||
message: t.String()
|
||||
}),
|
||||
404: t.Object({
|
||||
message: t.String(),
|
||||
code: t.String()
|
||||
})
|
||||
},
|
||||
body: t.Object({
|
||||
name: t.Optional(t.String()),
|
||||
producer: t.Optional(t.String())
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
export const songInfoHandler = new Elysia().use(songInfoGetHandler).use(songInfoUpdateHandler);
|
||||
|
||||
@ -20,6 +20,15 @@ const app = new Elysia({
|
||||
hostname: host
|
||||
}
|
||||
})
|
||||
.onError(({ code, status }) => {
|
||||
if (code === "NOT_FOUND")
|
||||
return status(404, {
|
||||
message: "The requested resource was not found."
|
||||
});
|
||||
return status(500, {
|
||||
message: "An internal server error occurred."
|
||||
});
|
||||
})
|
||||
.use(onAfterHandler)
|
||||
.use(cors())
|
||||
.use(openapi())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user