From 9dd06fa7bcb033e01a8b1775f8aadd52deed2da1 Mon Sep 17 00:00:00 2001 From: alikia2x Date: Sun, 1 Jun 2025 16:18:01 +0800 Subject: [PATCH] add: set cookie after signing up --- packages/backend/middleware/index.ts | 17 +++++++++-- packages/backend/routes/user/register.ts | 28 ++++++++++++++----- packages/backend/src/schema.d.ts | 5 ++++ .../next/app/[locale]/signup/SignUpForm.tsx | 6 +++- packages/next/app/[locale]/signup/request.tsx | 18 ++++++++++-- packages/next/components/hooks/useCaptcha.ts | 4 +-- 6 files changed, 61 insertions(+), 17 deletions(-) diff --git a/packages/backend/middleware/index.ts b/packages/backend/middleware/index.ts index 1aa05ca..0cd2a9d 100644 --- a/packages/backend/middleware/index.ts +++ b/packages/backend/middleware/index.ts @@ -1,4 +1,4 @@ -import { Hono } from "hono"; +import { Context, Hono } from "hono"; import { Variables } from "hono/types"; import { bodyLimitForPing } from "./bodyLimits.ts"; import { pingHandler } from "routes/ping"; @@ -8,10 +8,21 @@ import { logger } from "./logger.ts"; import { timing } from "hono/timing"; import { contentType } from "./contentType.ts"; import { captchaMiddleware } from "./captcha.ts"; -import { cors } from 'hono/cors'; +import { cors } from "hono/cors"; export function configureMiddleWares(app: Hono<{ Variables: Variables }>) { - app.all("*", cors()); + app.use("*", async (c, next) => { + if (c.req.path === "/user") { + const corsMiddlewareHandler = cors({ + origin: c.req.header("Origin"), + credentials: true + }); + return corsMiddlewareHandler(c, next); + } + const corsMiddlewareHandler = cors(); + return corsMiddlewareHandler(c, next); + }); + app.use("*", contentType); app.use(timing()); app.use("*", preetifyResponse); diff --git a/packages/backend/routes/user/register.ts b/packages/backend/routes/user/register.ts index ccab0af..0a6a080 100644 --- a/packages/backend/routes/user/register.ts +++ b/packages/backend/routes/user/register.ts @@ -4,9 +4,10 @@ import { object, string, ValidationError } from "yup"; import type { Context } from "hono"; import type { Bindings, BlankEnv, BlankInput } from "hono/types"; import { sqlCred } from "@core/db/dbNew.ts"; -import { ErrorResponse, StatusResponse } from "src/schema"; +import { ErrorResponse, SignUpResponse } from "src/schema"; import { generateRandomId } from "@core/lib/randomID"; import { getUserIP } from "@/middleware/rateLimiters"; +import { setCookie } from "hono/cookie"; const RegistrationBodySchema = object({ username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"), @@ -25,14 +26,15 @@ export const userExists = async (username: string) => { return result.length > 0; }; -const createLoginSession = async (uid: number, ua?: string, ip?: string) => { +const createLoginSession = async (uid: number, ua?: string, ip?: string): Promise => { const ip_address = ip || null; const user_agent = ua || null; - const id = generateRandomId(16); + const id = generateRandomId(24); await sqlCred` INSERT INTO login_sessions (id, uid, expire_at, ip_address, user_agent) VALUES (${id}, ${uid}, CURRENT_TIMESTAMP + INTERVAL '1 year', ${ip_address}, ${user_agent}) `; + return id; }; const getUserIDByName = async (username: string) => { @@ -91,13 +93,25 @@ export const registerHandler = createHandlers(async (c: ContextType) => { return c.json>(response, 500); } - createLoginSession(uid, c.req.header("User-Agent"), getUserIP(c)); + const sessionID = await createLoginSession(uid, c.req.header("User-Agent"), getUserIP(c)); - const response: StatusResponse = { - message: `User '${username}' registered successfully.` + const response: SignUpResponse = { + username: username, + token: sessionID }; - return c.json(response, 201); + const A_YEAR = 365 * 86400; + const isDev = process.env.NODE_ENV === "development"; + + setCookie(c, "session_id", sessionID, { + path: "/", + maxAge: A_YEAR, + domain: process.env.DOMAIN, + secure: isDev ? false : true, + sameSite: "Lax" + }); + + return c.json(response, 201); } catch (e) { if (e instanceof ValidationError) { const response: ErrorResponse = { diff --git a/packages/backend/src/schema.d.ts b/packages/backend/src/schema.d.ts index 62ad606..a1a1220 100644 --- a/packages/backend/src/schema.d.ts +++ b/packages/backend/src/schema.d.ts @@ -38,6 +38,11 @@ interface CaptchaSessionRawResponse { t: number; } +export interface SignUpResponse { + username: string; + token: string; +} + export type CaptchaVerificationRawResponse = { token: string; } diff --git a/packages/next/app/[locale]/signup/SignUpForm.tsx b/packages/next/app/[locale]/signup/SignUpForm.tsx index 23497f5..69d09c0 100644 --- a/packages/next/app/[locale]/signup/SignUpForm.tsx +++ b/packages/next/app/[locale]/signup/SignUpForm.tsx @@ -13,6 +13,7 @@ import { requestSignUp } from "./request"; import { FilledButton } from "@/components/ui/Buttons/FilledButton"; import { ErrorDialog } from "./ErrorDialog"; import { ApiRequestError } from "@/lib/net"; +import { useRouter } from "next/navigation"; setLocale({ mixed: { @@ -49,6 +50,7 @@ const SignUpForm: React.FC = ({ backendURL }) => { route: "POST-/user" }); const { trigger } = useSWRMutation(`${backendURL}/user`, requestSignUp); + const router = useRouter(); const translateErrorMessage = (item: LocalizedMessage | string, path?: string) => { if (typeof item === "string") { @@ -63,7 +65,7 @@ const SignUpForm: React.FC = ({ backendURL }) => { await startCaptcha(); } - trigger({ + await trigger({ data: { username: usernameInput, password: passwordInput, @@ -76,6 +78,8 @@ const SignUpForm: React.FC = ({ backendURL }) => { setDialogContent, t }); + + router.push("/"); } finally { setLoading(false); } diff --git a/packages/next/app/[locale]/signup/request.tsx b/packages/next/app/[locale]/signup/request.tsx index e7b2613..468d035 100644 --- a/packages/next/app/[locale]/signup/request.tsx +++ b/packages/next/app/[locale]/signup/request.tsx @@ -1,6 +1,6 @@ import { Dispatch, JSX, SetStateAction } from "react"; import { ApiRequestError, fetcher } from "@/lib/net"; -import type { CaptchaVerificationRawResponse, ErrorResponse } from "@backend/src/schema"; +import type { CaptchaVerificationRawResponse, ErrorResponse, SignUpResponse } from "@backend/src/schema"; import Link from "next/link"; import { LocalizedMessage } from "./SignUpForm"; import { ErrorDialog } from "./ErrorDialog"; @@ -91,8 +91,9 @@ export const requestSignUp = async (url: string, { arg }: { arg: RequestSignUpAr throw err; } setCaptchaUsedState(true); - const registrationResponse = await fetcher(url, { + const registrationResponse = await fetcher(url, { method: "POST", + withCredentials: true, headers: { "Content-Type": "application/json", Authorization: `Bearer ${captchaResult!.token}` @@ -105,7 +106,7 @@ export const requestSignUp = async (url: string, { arg }: { arg: RequestSignUpAr }); return registrationResponse; } catch (error) { - if (error instanceof ApiRequestError) { + if (error instanceof ApiRequestError && error.response) { const res = error.response as ErrorResponse; setShowDialog(true); setDialogContent( @@ -134,6 +135,17 @@ export const requestSignUp = async (url: string, { arg }: { arg: RequestSignUpAr

); + } else { + setShowDialog(true); + setDialogContent( + setShowDialog(false)} errorCode="UNKNOWN_ERROR"> +

无法为你注册账户。

+

+ 错误信息:
+

{JSON.stringify(error)}
+

+
+ ); } } }; diff --git a/packages/next/components/hooks/useCaptcha.ts b/packages/next/components/hooks/useCaptcha.ts index 60e5986..c9f99cc 100644 --- a/packages/next/components/hooks/useCaptcha.ts +++ b/packages/next/components/hooks/useCaptcha.ts @@ -16,8 +16,6 @@ export function useCaptcha({ backendURL, route }: UseCaptchaOptions) { const { trigger, data, isMutating, error } = useSWRMutation( fullUrl, async (url: string) => { - setIsUsed(false); - const sessionRes = await fetcher(url, { method: "POST", headers: { @@ -37,7 +35,7 @@ export function useCaptcha({ backendURL, route }: UseCaptchaOptions) { resultUrl.searchParams.set("ans", ans.result.toString()); const result = await fetcher(resultUrl.toString()); - + setIsUsed(false); return result; } );