60 lines
1.4 KiB
TypeScript
60 lines
1.4 KiB
TypeScript
import { Elysia } from "elysia";
|
|
import { jwt } from "@elysiajs/jwt";
|
|
import { redis } from "@core/db/redis";
|
|
|
|
interface JWTPayload {
|
|
id: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
export const captchaMiddleware = new Elysia({ name: "captcha" })
|
|
.use(
|
|
jwt({
|
|
name: "captchaJwt",
|
|
secret: process.env.JWT_SECRET || "default-secret-key"
|
|
})
|
|
)
|
|
.derive(async ({ request, captchaJwt, set }) => {
|
|
const authHeader = request.headers.get("authorization");
|
|
|
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
set.status = 401;
|
|
throw new Error("Missing or invalid authorization header");
|
|
}
|
|
|
|
const token = authHeader.slice(7);
|
|
try {
|
|
const payload = (await captchaJwt.verify(token)) as JWTPayload;
|
|
|
|
if (!payload || !payload.id) {
|
|
set.status = 401;
|
|
throw new Error("Invalid JWT payload");
|
|
}
|
|
|
|
const redisKey = `captcha:${payload.id}`;
|
|
|
|
const exists = await redis.exists(redisKey);
|
|
|
|
if (exists) {
|
|
set.status = 400;
|
|
throw new Error("Captcha already used or expired");
|
|
}
|
|
|
|
await redis.setex(redisKey, 300, "used");
|
|
|
|
return {
|
|
captchaVerified: true,
|
|
userId: payload.id
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
set.status = 401;
|
|
throw new Error(`JWT verification failed: ${error.message}`);
|
|
}
|
|
set.status = 500;
|
|
throw new Error("Internal server error during captcha verification");
|
|
}
|
|
});
|
|
|
|
export default captchaMiddleware;
|