update: returns JWT in the /captcha/:id/result endpoint
This commit is contained in:
parent
a063f2401b
commit
1633e56b1e
3
bun.lock
3
bun.lock
@ -19,6 +19,7 @@
|
|||||||
"hono": "^4.7.8",
|
"hono": "^4.7.8",
|
||||||
"hono-rate-limiter": "^0.4.2",
|
"hono-rate-limiter": "^0.4.2",
|
||||||
"ioredis": "^5.6.1",
|
"ioredis": "^5.6.1",
|
||||||
|
"jose": "^6.0.11",
|
||||||
"limiter": "^3.0.0",
|
"limiter": "^3.0.0",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"rate-limit-redis": "^4.2.0",
|
"rate-limit-redis": "^4.2.0",
|
||||||
@ -814,6 +815,8 @@
|
|||||||
|
|
||||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
"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=="],
|
"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=="],
|
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"hono": "^4.7.8",
|
"hono": "^4.7.8",
|
||||||
"hono-rate-limiter": "^0.4.2",
|
"hono-rate-limiter": "^0.4.2",
|
||||||
"ioredis": "^5.6.1",
|
"ioredis": "^5.6.1",
|
||||||
|
"jose": "^6.0.11",
|
||||||
"limiter": "^3.0.0",
|
"limiter": "^3.0.0",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"rate-limit-redis": "^4.2.0",
|
"rate-limit-redis": "^4.2.0",
|
||||||
|
@ -2,6 +2,12 @@ import { Context } from "hono";
|
|||||||
import { Bindings, BlankEnv, BlankInput } from "hono/types";
|
import { Bindings, BlankEnv, BlankInput } from "hono/types";
|
||||||
import { ErrorResponse } from "src/schema";
|
import { ErrorResponse } from "src/schema";
|
||||||
import { createHandlers } from "src/utils.ts";
|
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 getChallengeVerificationResult = async (id: string, ans: string) => {
|
||||||
const baseURL = process.env["UCAPTCHA_URL"];
|
const baseURL = process.env["UCAPTCHA_URL"];
|
||||||
@ -19,19 +25,63 @@ const getChallengeVerificationResult = async (id: string, ans: string) => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const verifyChallengeHandler = createHandlers(
|
export const verifyChallengeHandler = createHandlers(
|
||||||
async (c: Context<BlankEnv & { Bindings: Bindings }, "/captcha/:id/result", BlankInput>) => {
|
async (c: Context<BlankEnv & { Bindings: Bindings }, "/captcha/:id/result", BlankInput>) => {
|
||||||
const id = c.req.param("id");
|
const id = c.req.param("id");
|
||||||
const ans = c.req.query("ans");
|
const ans = c.req.query("ans");
|
||||||
if (!ans) {
|
if (!ans) {
|
||||||
const response: ErrorResponse<string> = {
|
const response: ErrorResponse<string> = {
|
||||||
message: "Missing required query parameter: ans",
|
message: "Missing required query parameter: ans",
|
||||||
code: "INVALID_QUERY_PARAMS"
|
code: "INVALID_QUERY_PARAMS"
|
||||||
};
|
};
|
||||||
return c.json<ErrorResponse<string>>(response, 400);
|
return c.json<ErrorResponse<string>>(response, 400);
|
||||||
}
|
}
|
||||||
const res = await getChallengeVerificationResult(id, ans);
|
const res = await getChallengeVerificationResult(id, ans);
|
||||||
return res;
|
const data: CaptchaResponse = await res.json();
|
||||||
|
if (data.error && res.status === 404) {
|
||||||
|
const response: ErrorResponse<string> = {
|
||||||
|
message: data.error,
|
||||||
|
code: "ENTITY_NOT_FOUND"
|
||||||
|
};
|
||||||
|
return c.json<ErrorResponse<string>>(response, 401);
|
||||||
|
} else if (data.error && res.status === 400) {
|
||||||
|
const response: ErrorResponse<string> = {
|
||||||
|
message: data.error,
|
||||||
|
code: "INVALID_QUERY_PARAMS"
|
||||||
|
};
|
||||||
|
return c.json<ErrorResponse<string>>(response, 400);
|
||||||
|
} else if (data.error) {
|
||||||
|
const response: ErrorResponse<string> = {
|
||||||
|
message: data.error,
|
||||||
|
code: "UNKNOWN_ERROR"
|
||||||
|
};
|
||||||
|
return c.json<ErrorResponse<string>>(response, 500);
|
||||||
|
}
|
||||||
|
if (!data.success) {
|
||||||
|
const response: ErrorResponse<string> = {
|
||||||
|
message: "Incorrect answer",
|
||||||
|
code: "INVALID_CREDENTIALS"
|
||||||
|
};
|
||||||
|
return c.json<ErrorResponse<string>>(response, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = process.env["JWT_SECRET"];
|
||||||
|
if (!secret) {
|
||||||
|
const response: ErrorResponse<string> = {
|
||||||
|
message: "JWT_SECRET is not set",
|
||||||
|
code: "SERVER_ERROR"
|
||||||
|
};
|
||||||
|
return c.json<ErrorResponse<string>>(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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -66,7 +66,7 @@ export const registerHandler = createHandlers(async (c: ContextType) => {
|
|||||||
const response: ErrorResponse<string> = {
|
const response: ErrorResponse<string> = {
|
||||||
message: "Invalid JSON payload.",
|
message: "Invalid JSON payload.",
|
||||||
errors: [(e as Error).message],
|
errors: [(e as Error).message],
|
||||||
code: "UNKNOWN_ERR"
|
code: "UNKNOWN_ERROR"
|
||||||
};
|
};
|
||||||
return c.json<ErrorResponse<string>>(response, 500);
|
return c.json<ErrorResponse<string>>(response, 500);
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ export const getSnapshotsHanlder = createHandlers(async (c: ContextType) => {
|
|||||||
return c.json<ErrorResponse<string>>(response, 400);
|
return c.json<ErrorResponse<string>>(response, 400);
|
||||||
} else {
|
} else {
|
||||||
const response: ErrorResponse<unknown> = {
|
const response: ErrorResponse<unknown> = {
|
||||||
code: "UNKNOWN_ERR",
|
code: "UNKNOWN_ERROR",
|
||||||
message: "Unhandled error",
|
message: "Unhandled error",
|
||||||
errors: [e]
|
errors: [e]
|
||||||
};
|
};
|
||||||
|
11
packages/backend/src/schema.d.ts
vendored
11
packages/backend/src/schema.d.ts
vendored
@ -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<E> {
|
export interface ErrorResponse<E> {
|
||||||
code: ErrorCode;
|
code: ErrorCode;
|
||||||
|
Loading…
Reference in New Issue
Block a user