1
0

add: auth middleware

This commit is contained in:
alikia2x (寒寒) 2025-11-09 05:16:18 +08:00
parent 6b2b035050
commit 35749c65a3
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG Key ID: 56209E0CCD8420C6
5 changed files with 148 additions and 62 deletions

View File

@ -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=="],

View 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;

View File

@ -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"
},

View File

@ -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);

View File

@ -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())