From 1633e56b1e559f485cb1dc458624de40a2aea2b6 Mon Sep 17 00:00:00 2001 From: alikia2x Date: Sun, 11 May 2025 19:40:24 +0800 Subject: [PATCH] update: returns JWT in the /captcha/:id/result endpoint --- bun.lock | 3 + packages/backend/package.json | 1 + .../backend/routes/captcha/[id]/result/GET.ts | 70 ++++++++++++++++--- packages/backend/routes/user/register.ts | 2 +- .../backend/routes/video/[id]/snapshots.ts | 2 +- packages/backend/src/schema.d.ts | 11 ++- 6 files changed, 76 insertions(+), 13 deletions(-) diff --git a/bun.lock b/bun.lock index 848c9da..f5d6db7 100644 --- a/bun.lock +++ b/bun.lock @@ -19,6 +19,7 @@ "hono": "^4.7.8", "hono-rate-limiter": "^0.4.2", "ioredis": "^5.6.1", + "jose": "^6.0.11", "limiter": "^3.0.0", "postgres": "^3.4.5", "rate-limit-redis": "^4.2.0", @@ -814,6 +815,8 @@ "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + "jose": ["jose@6.0.11", "", {}, "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg=="], + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], diff --git a/packages/backend/package.json b/packages/backend/package.json index 2cc883d..aede4e7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -10,6 +10,7 @@ "hono": "^4.7.8", "hono-rate-limiter": "^0.4.2", "ioredis": "^5.6.1", + "jose": "^6.0.11", "limiter": "^3.0.0", "postgres": "^3.4.5", "rate-limit-redis": "^4.2.0", diff --git a/packages/backend/routes/captcha/[id]/result/GET.ts b/packages/backend/routes/captcha/[id]/result/GET.ts index aa1a975..6a507da 100644 --- a/packages/backend/routes/captcha/[id]/result/GET.ts +++ b/packages/backend/routes/captcha/[id]/result/GET.ts @@ -2,6 +2,12 @@ import { Context } from "hono"; import { Bindings, BlankEnv, BlankInput } from "hono/types"; import { ErrorResponse } from "src/schema"; import { createHandlers } from "src/utils.ts"; +import * as jose from "jose"; + +interface CaptchaResponse { + success: boolean; + error?: string; +} const getChallengeVerificationResult = async (id: string, ans: string) => { const baseURL = process.env["UCAPTCHA_URL"]; @@ -19,19 +25,63 @@ const getChallengeVerificationResult = async (id: string, ans: string) => { return res; }; - export const verifyChallengeHandler = createHandlers( async (c: Context) => { const id = c.req.param("id"); const ans = c.req.query("ans"); - if (!ans) { - const response: ErrorResponse = { - message: "Missing required query parameter: ans", - code: "INVALID_QUERY_PARAMS" - }; - return c.json>(response, 400); - } - const res = await getChallengeVerificationResult(id, ans); - return res; + if (!ans) { + const response: ErrorResponse = { + message: "Missing required query parameter: ans", + code: "INVALID_QUERY_PARAMS" + }; + return c.json>(response, 400); + } + const res = await getChallengeVerificationResult(id, ans); + const data: CaptchaResponse = await res.json(); + if (data.error && res.status === 404) { + const response: ErrorResponse = { + message: data.error, + code: "ENTITY_NOT_FOUND" + }; + return c.json>(response, 401); + } else if (data.error && res.status === 400) { + const response: ErrorResponse = { + message: data.error, + code: "INVALID_QUERY_PARAMS" + }; + return c.json>(response, 400); + } else if (data.error) { + const response: ErrorResponse = { + message: data.error, + code: "UNKNOWN_ERROR" + }; + return c.json>(response, 500); + } + if (!data.success) { + const response: ErrorResponse = { + message: "Incorrect answer", + code: "INVALID_CREDENTIALS" + }; + return c.json>(response, 401); + } + + const secret = process.env["JWT_SECRET"]; + if (!secret) { + const response: ErrorResponse = { + message: "JWT_SECRET is not set", + code: "SERVER_ERROR" + }; + return c.json>(response, 500); + } + const jwtSecret = new TextEncoder().encode(secret); + const alg = "HS256"; + + const jwt = await new jose.SignJWT() + .setProtectedHeader({ alg }) + .setIssuedAt() + .sign(jwtSecret); + return c.json({ + token: jwt + }); } ); diff --git a/packages/backend/routes/user/register.ts b/packages/backend/routes/user/register.ts index bf1aeeb..ac8200d 100644 --- a/packages/backend/routes/user/register.ts +++ b/packages/backend/routes/user/register.ts @@ -66,7 +66,7 @@ export const registerHandler = createHandlers(async (c: ContextType) => { const response: ErrorResponse = { message: "Invalid JSON payload.", errors: [(e as Error).message], - code: "UNKNOWN_ERR" + code: "UNKNOWN_ERROR" }; return c.json>(response, 500); } diff --git a/packages/backend/routes/video/[id]/snapshots.ts b/packages/backend/routes/video/[id]/snapshots.ts index a946e75..e738e50 100644 --- a/packages/backend/routes/video/[id]/snapshots.ts +++ b/packages/backend/routes/video/[id]/snapshots.ts @@ -96,7 +96,7 @@ export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => { return c.json>(response, 400); } else { const response: ErrorResponse = { - code: "UNKNOWN_ERR", + code: "UNKNOWN_ERROR", message: "Unhandled error", errors: [e] }; diff --git a/packages/backend/src/schema.d.ts b/packages/backend/src/schema.d.ts index db279b7..51f54fa 100644 --- a/packages/backend/src/schema.d.ts +++ b/packages/backend/src/schema.d.ts @@ -1,4 +1,13 @@ -type ErrorCode = "INVALID_QUERY_PARAMS" | "UNKNOWN_ERR" | "INVALID_PAYLOAD" | "INVALID_FORMAT" | "BODY_TOO_LARGE"; +type ErrorCode = + | "INVALID_QUERY_PARAMS" + | "UNKNOWN_ERROR" + | "INVALID_PAYLOAD" + | "INVALID_FORMAT" + | "BODY_TOO_LARGE" + | "UNAUTHORIZED" + | "INVALID_CREDENTIALS" + | "ENTITY_NOT_FOUND" + | "SERVER_ERROR"; export interface ErrorResponse { code: ErrorCode;