diff options
author | polwex <polwex@sortug.com> | 2025-05-29 14:52:38 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-05-29 14:52:38 +0700 |
commit | 490388360a0852bcf8ee054e96fa90e166df5792 (patch) | |
tree | 3940097c5505ff1bb09875dddb7ee0e4881beb77 /src | |
parent | f243847216279cbd43879de8b5ef6dcceb3a2f1d (diff) |
fucker actually solved the cookies, love ya man
Diffstat (limited to 'src')
-rw-r--r-- | src/actions/deck.ts | 3 | ||||
-rw-r--r-- | src/actions/login.ts | 31 | ||||
-rw-r--r-- | src/components/Login2.tsx | 20 | ||||
-rw-r--r-- | src/lib/db/seed.ts | 1 | ||||
-rw-r--r-- | src/lib/hooks/useCookie.ts | 33 | ||||
-rw-r--r-- | src/lib/server/cookie.ts | 47 | ||||
-rw-r--r-- | src/lib/server/cookiebridge.ts | 56 | ||||
-rw-r--r-- | src/lib/server/setcookie.ts | 11 | ||||
-rw-r--r-- | src/pages.gen.ts | 50 | ||||
-rw-r--r-- | src/pages/study.tsx | 53 |
10 files changed, 145 insertions, 160 deletions
diff --git a/src/actions/deck.ts b/src/actions/deck.ts index e501f23..5812715 100644 --- a/src/actions/deck.ts +++ b/src/actions/deck.ts @@ -1,7 +1,6 @@ "use server"; -import ServerWord from "@/zoom/ServerWord"; -import { analyzeTHWord, segmentateThai } from "@/pages/api/nlp"; import db from "../lib/db"; +import { analyzeTHWord, segmentateThai } from "@/lib/calls/nlp"; export function shuffleDeck(userId: number, lessonId: number) { const res = db.fetchLesson({ userId, lessonId, random: true }); diff --git a/src/actions/login.ts b/src/actions/login.ts index ee15fe6..ed96f54 100644 --- a/src/actions/login.ts +++ b/src/actions/login.ts @@ -50,20 +50,35 @@ export async function postLogin( console.log({ res }); if ("error" in res) return { error: res.error }; else { - setCookie(res.ok as number); - return { success: true }; + // Set the cookie + await setCookie(res.ok as number); + + // Return success for client-side handling + return { + success: true, + userId: res.ok, + redirect: "/" + }; } } async function setCookie(userId: number) { + // Set cookie expiry for 30 days const COOKIE_EXPIRY = Date.now() + 1000 * 60 * 60 * 24 * 30; - const COOKIE_OPTS = { expires: new Date(COOKIE_EXPIRY) }; - + + // Generate a secure random token for the cookie const { randomBytes } = await import("node:crypto"); - const cokistring = randomBytes(32).toBase64(); - const res = db.setCookie(cokistring, userId, COOKIE_EXPIRY); + const cookieToken = randomBytes(32).toString("base64"); + + // Store the cookie in the database + const res = db.setCookie(cookieToken, userId, COOKIE_EXPIRY); + + // Set the cookie in the response const { setCookie } = useCookies(); - setCookie(cokistring); - // unstable_redirect("/"); + setCookie(cookieToken); + + console.log("Cookie set for user ID:", userId); + + // Redirect is managed by client after successful login } // export async function postLogout(prev: number) { diff --git a/src/components/Login2.tsx b/src/components/Login2.tsx index 6c26efc..7adf09c 100644 --- a/src/components/Login2.tsx +++ b/src/components/Login2.tsx @@ -42,17 +42,18 @@ function OOldform({ isReg, toggle }: { isReg: boolean; toggle: () => void }) { else setStrings(logstrings); }, [isReg]); - // const [state, formAction, isPending] = useActionState<FormState, FormData>( - // isReg ? postRegister : postLogin, - // { error: "" }, - // "/login", - // ); const [state, formAction, isPending] = useActionState<FormState, FormData>( - postLogin, + isReg ? postRegister : postLogin, { error: "" }, "/login", ); console.log({ state }); + + // Handle redirect after successful login + if (state.success && state.redirect) { + window.location.href = state.redirect; + } + return ( <form action={formAction}> <div className="flex flex-col gap-6"> @@ -87,8 +88,8 @@ function OOldform({ isReg, toggle }: { isReg: boolean; toggle: () => void }) { {state.password && <p>{state.password}</p>} </Label> </div> - <Button type="submit" className="w-full"> - {strings.button} + <Button type="submit" className="w-full" disabled={isPending}> + {isPending ? "Loading..." : strings.button} </Button> <div className="text-center text-sm"> {strings.toggle} @@ -101,7 +102,8 @@ function OOldform({ isReg, toggle }: { isReg: boolean; toggle: () => void }) { </a> </div> </div> - {state.error && <p className="text-red">{state.error}</p>} + {state.error && <p className="text-red-500">{state.error}</p>} + {state.success && <p className="text-green-500">Login successful! Redirecting...</p>} </CardContent> </Card> <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-primary "> diff --git a/src/lib/db/seed.ts b/src/lib/db/seed.ts index a196b6a..6c2a9f7 100644 --- a/src/lib/db/seed.ts +++ b/src/lib/db/seed.ts @@ -3,7 +3,6 @@ import { getStressedSyllable, getSyllableCount } from "../utils"; import useful from "@/lib/useful_thai.json"; import db from "."; import pdb from "./prosodydb"; -import * as Sorsyl from "sorsyl"; import { findLemma } from "../calls/nlp"; const SYMBOL_REGEX = new RegExp(/[\W\d]/); diff --git a/src/lib/hooks/useCookie.ts b/src/lib/hooks/useCookie.ts index 904738a..cb3a019 100644 --- a/src/lib/hooks/useCookie.ts +++ b/src/lib/hooks/useCookie.ts @@ -1,35 +1,12 @@ import { getContext } from "waku/middleware/context"; -import { mergeSetCookies } from "./setcookie"; + +// This file appears to be unused - all cookie functionality is now in cookiebridge.ts +// Left as a placeholder in case it's referenced elsewhere const useCookies = () => { const ctx = getContext(); - console.log(ctx.req, "cookie bridge"); - const headers = ctx.req.headers; - console.log({ headers }); - return "hi"; - - // const headerObj = ctx.headers || {}; - // headerObj["set-cookie"] = mergeSetCookies( - // headerObj["set-cookie"] || [], - // (ctx.cookies || []) as ResponseCookie[], - // ); - // const headers = new Headers(headerObj as Record<string, string>); - // const reqCookies = new RequestCookies(headers); - // const resCookies = new ResponseCookies(headers); - - // const getCookie: ResponseCookies["get"] = (...args) => - // resCookies.get(...args) || reqCookies.get(...args); - // const setCookie: ResponseCookies["set"] = (...args) => { - // const updated = resCookies.set(...args); - // ctx.cookies = updated.getAll(); - // return updated; - // }; - // const delCookie: ResponseCookies["delete"] = (...args) => { - // const updated = resCookies.delete(...args); - // ctx.cookies = updated.getAll(); - // return updated; - // }; - // return { getCookie, setCookie, delCookie }; + console.log("WARNING: useCookie.ts is deprecated, use cookiebridge.ts instead"); + return "DEPRECATED"; }; export { useCookies }; diff --git a/src/lib/server/cookie.ts b/src/lib/server/cookie.ts index 9a7e632..32894b9 100644 --- a/src/lib/server/cookie.ts +++ b/src/lib/server/cookie.ts @@ -1,41 +1,36 @@ -import { getHonoContext } from "waku/unstable_hono"; import cookie from "cookie"; import db from "../db"; - import type { Middleware } from "waku/config"; const cookieMiddleware: Middleware = () => { - console.log("cookieMiddleware executed"); return async (ctx, next) => { + // Parse incoming cookies const cookies = cookie.parse(ctx.req.headers.cookie || ""); const coki = cookies.sorlang; - // if (!coki) { - // if (ctx.req.url.pathname === "/login") return await next(); - // ctx.res.status = 301; - // ctx.res.headers = { - // Location: "/login", - // }; - // } + + // If cookie exists, fetch user data and set in context if (coki) { const userRow = db.fetchCookie(coki); - // console.log({ userRow }); - if (userRow) ctx.data.user = { id: userRow.id, name: userRow.name }; - // else { - // if (ctx.req.url.pathname === "/login") return await next(); - // ctx.res.status = 301; - // ctx.res.headers = { - // Location: "/login", - // }; - // } + if (userRow) { + ctx.data.user = { id: userRow.id, name: userRow.name }; + console.log("User authenticated:", userRow.name); + } + } + + // Uncomment to enable redirection for unauthenticated users + /* + if (!ctx.data.user && ctx.req.url.pathname !== "/login") { + ctx.res.status = 302; + ctx.res.headers = { + Location: "/login", + }; + return; } + */ + await next(); - const hctx: any = getHonoContext(); - // console.log("hono", hctx.lol); - // console.log("ctx coki", ctx.data.cookie); - ctx.res.headers ||= {}; - if (ctx.data.cookie) - ctx.res.headers["set-cookie"] = ctx.data.cookie as string; - ctx.res.headers["set-lmao"] = "wtf man"; + + // Cookie setting is now handled by setCookieMiddleware }; }; diff --git a/src/lib/server/cookiebridge.ts b/src/lib/server/cookiebridge.ts index 778fc2c..613479b 100644 --- a/src/lib/server/cookiebridge.ts +++ b/src/lib/server/cookiebridge.ts @@ -1,52 +1,32 @@ import { getContext, getContextData } from "waku/middleware/context"; +import cookie from "cookie"; const useCookies = () => { const ctx = getContext(); const headers = ctx.req.headers; - console.log(headers.cookie); - - const getCookie = (s: string) => { - const coki = headers.cookie; - if (!coki) return {}; - const cokiMap = parseCoki(coki); - return cokiMap; + + const getCookie = (name: string) => { + const cookieHeader = headers.cookie; + if (!cookieHeader) return null; + + const cookies = cookie.parse(cookieHeader); + return cookies[name]; }; - const setCookie = (s: string) => { + + const setCookie = (value: string) => { const ctxdata = getContextData(); - // (ctxdata.cokimap as Record<string, string>).sorlang = s; - // ctxdata.cookie = `sorlang=${s}; Secure` - ctxdata.cookie = `sorlang=${s};`; + // Set cookie with proper attributes for security and functionality + ctxdata.cookie = `sorlang=${value}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`; + console.log("Cookie value being set:", value); }; - const delCookie = (s: string) => { + + const delCookie = (name: string) => { const ctxdata = getContextData(); - delete (ctxdata.cokimap as Record<string, string>).sorlang; + // Set an expired cookie to delete it + ctxdata.cookie = `${name}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT`; }; + return { getCookie, setCookie, delCookie }; }; export { useCookies }; - -function parseCoki(s: string) { - return s - .split(";") - .map((v) => v.split("=")) - .reduce((acc: Record<string, string>, v: any) => { - acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim()); - return acc; - }, {}); -} -function parseSetCoki(s: string) { - return s - .split(";") - .map((v) => v.split("=")) - .reduce((acc: Record<string, string>, v: any) => { - acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim()); - return acc; - }, {}); -} -function cokiToString(m: Record<string, string>): string { - return Object.entries(m).reduce((acc: string, item: [string, string]) => { - const [key, val] = item; - return `${acc} ${key}=${val};`; - }, ""); -} diff --git a/src/lib/server/setcookie.ts b/src/lib/server/setcookie.ts index 61da128..10ca489 100644 --- a/src/lib/server/setcookie.ts +++ b/src/lib/server/setcookie.ts @@ -3,8 +3,17 @@ import type { Middleware } from "waku/config"; const setCookieMiddleware: Middleware = () => { return async (ctx, next) => { await next(); + + // Ensure headers object exists ctx.res.headers ||= {}; - ctx.res.headers["set-cookie"] = ctx.data.cookie as string; + + // Only set the cookie header if we have a cookie to set + if (ctx.data.cookie) { + ctx.res.headers["set-cookie"] = ctx.data.cookie as string; + + // Debugging + console.log("Setting cookie header:", ctx.data.cookie); + } }; }; diff --git a/src/pages.gen.ts b/src/pages.gen.ts index b4a4bd7..d8c992f 100644 --- a/src/pages.gen.ts +++ b/src/pages.gen.ts @@ -4,49 +4,51 @@ import type { PathsForPages, GetConfigResponse } from 'waku/router'; // prettier-ignore -import type { getConfig as Zoom_getConfig } from './pages/zoom'; +import type { getConfig as File_Zoom_getConfig } from './pages/zoom'; // prettier-ignore -import type { getConfig as LangSlug_getConfig } from './pages/lang/[slug]'; +import type { getConfig as File_LangSlug_getConfig } from './pages/lang/[slug]'; // prettier-ignore -import type { getConfig as LessonSlug_getConfig } from './pages/lesson/[slug]'; +import type { getConfig as File_LessonSlug_getConfig } from './pages/lesson/[slug]'; // prettier-ignore -import type { getConfig as Login_getConfig } from './pages/login'; +import type { getConfig as File_Login_getConfig } from './pages/login'; // prettier-ignore -import type { getConfig as Parse_getConfig } from './pages/parse'; +import type { getConfig as File_Parse_getConfig } from './pages/parse'; // prettier-ignore -import type { getConfig as Db_getConfig } from './pages/db'; +import type { getConfig as File_Db_getConfig } from './pages/db'; // prettier-ignore -import type { getConfig as Form_getConfig } from './pages/form'; +import type { getConfig as File_Form_getConfig } from './pages/form'; // prettier-ignore -import type { getConfig as Tones_getConfig } from './pages/tones'; +import type { getConfig as File_Tones_getConfig } from './pages/tones'; // prettier-ignore -import type { getConfig as Picker_getConfig } from './pages/picker'; +import type { getConfig as File_Picker_getConfig } from './pages/picker'; // prettier-ignore -import type { getConfig as About_getConfig } from './pages/about'; +import type { getConfig as File_About_getConfig } from './pages/about'; // prettier-ignore -import type { getConfig as LogintestIndex_getConfig } from './pages/logintest/index'; +import type { getConfig as File_LogintestIndex_getConfig } from './pages/logintest/index'; // prettier-ignore -import type { getConfig as Index_getConfig } from './pages/index'; +import type { getConfig as File_Index_getConfig } from './pages/index'; // prettier-ignore type Page = -| ({ path: '/zoom' } & GetConfigResponse<typeof Zoom_getConfig>) -| ({ path: '/lang/[slug]' } & GetConfigResponse<typeof LangSlug_getConfig>) -| ({ path: '/lesson/[slug]' } & GetConfigResponse<typeof LessonSlug_getConfig>) -| ({ path: '/login' } & GetConfigResponse<typeof Login_getConfig>) -| ({ path: '/parse' } & GetConfigResponse<typeof Parse_getConfig>) -| ({ path: '/db' } & GetConfigResponse<typeof Db_getConfig>) +| ({ path: '/zoom' } & GetConfigResponse<typeof File_Zoom_getConfig>) +| ({ path: '/lang/[slug]' } & GetConfigResponse<typeof File_LangSlug_getConfig>) +| ({ path: '/lesson/[slug]' } & GetConfigResponse<typeof File_LessonSlug_getConfig>) +| ({ path: '/login' } & GetConfigResponse<typeof File_Login_getConfig>) +| ({ path: '/parse' } & GetConfigResponse<typeof File_Parse_getConfig>) +| ({ path: '/db' } & GetConfigResponse<typeof File_Db_getConfig>) +| { path: '/study'; render: 'dynamic' } | { path: '/test/client-modal'; render: 'dynamic' } | { path: '/test/trigger-modal-button'; render: 'dynamic' } | { path: '/test'; render: 'dynamic' } -| ({ path: '/form' } & GetConfigResponse<typeof Form_getConfig>) -| ({ path: '/tones' } & GetConfigResponse<typeof Tones_getConfig>) -| ({ path: '/picker' } & GetConfigResponse<typeof Picker_getConfig>) -| ({ path: '/about' } & GetConfigResponse<typeof About_getConfig>) +| ({ path: '/form' } & GetConfigResponse<typeof File_Form_getConfig>) +| ({ path: '/tones' } & GetConfigResponse<typeof File_Tones_getConfig>) +| ({ path: '/picker' } & GetConfigResponse<typeof File_Picker_getConfig>) +| { path: '/lessons'; render: 'dynamic' } +| ({ path: '/about' } & GetConfigResponse<typeof File_About_getConfig>) | { path: '/logintest/Form'; render: 'dynamic' } -| ({ path: '/logintest' } & GetConfigResponse<typeof LogintestIndex_getConfig>) +| ({ path: '/logintest' } & GetConfigResponse<typeof File_LogintestIndex_getConfig>) | { path: '/logintest/ServerForm'; render: 'dynamic' } -| ({ path: '/' } & GetConfigResponse<typeof Index_getConfig>); +| ({ path: '/' } & GetConfigResponse<typeof File_Index_getConfig>); // prettier-ignore declare module 'waku/router' { diff --git a/src/pages/study.tsx b/src/pages/study.tsx index db7dde7..f818b4b 100644 --- a/src/pages/study.tsx +++ b/src/pages/study.tsx @@ -1,3 +1,4 @@ +import { getContextData } from "waku/middleware/context"; import { useState } from "react"; import { getState } from "@/lib/db"; import { startStudySession } from "@/actions/srs"; @@ -8,17 +9,24 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; // This is a server component that gets the initial data -export default async function StudyPage({ searchParams }: { searchParams: { lessonId?: string } }) { - const state = getState(null); - const userId = state.user?.id; - +export default async function StudyPage({ + searchParams, +}: { + searchParams: { lessonId?: string }; +}) { + const { user } = getContextData() as any; + // const state = getState(null); + const userId = user?.id; + // If not logged in, show login required message if (!userId) { return ( <div className="container mx-auto py-8"> <Card className="p-6 text-center"> <h1 className="text-2xl font-bold mb-4">Login Required</h1> - <p className="mb-4">You need to be logged in to use the study session feature.</p> + <p className="mb-4"> + You need to be logged in to use the study session feature. + </p> <Button asChild> <a href="/login">Login</a> </Button> @@ -26,14 +34,16 @@ export default async function StudyPage({ searchParams }: { searchParams: { less </div> ); } - - const lessonId = searchParams.lessonId ? parseInt(searchParams.lessonId, 10) : null; - + + const lessonId = searchParams.lessonId + ? parseInt(searchParams.lessonId, 10) + : null; + // If no lesson ID provided, show lesson selector if (!lessonId) { return <LessonSelector userId={userId} />; } - + // Get initial data for the study session let initialData; try { @@ -41,13 +51,15 @@ export default async function StudyPage({ searchParams }: { searchParams: { less } catch (error) { console.error("Error starting study session:", error); } - + return ( <div className="container mx-auto py-8"> <StudySession userId={userId} lessonId={lessonId} - initialData={initialData && !('error' in initialData) ? initialData : undefined} + initialData={ + initialData && !("error" in initialData) ? initialData : undefined + } /> </div> ); @@ -56,12 +68,12 @@ export default async function StudyPage({ searchParams }: { searchParams: { less // Client component for selecting a lesson function LessonSelector({ userId }: { userId: number }) { const [lessonId, setLessonId] = useState<string>(""); - + return ( <div className="container mx-auto py-8"> <Card className="p-6 max-w-md mx-auto"> <h1 className="text-2xl font-bold mb-6">Start Study Session</h1> - + <form action={`/study?lessonId=${lessonId}`}> <div className="space-y-4"> <div> @@ -75,13 +87,13 @@ function LessonSelector({ userId }: { userId: number }) { required /> </div> - + <Button type="submit" className="w-full"> Start Study Session </Button> </div> </form> - + <div className="mt-6 pt-6 border-t border-gray-200"> <h2 className="text-lg font-medium mb-3">Available Lessons</h2> <p className="text-sm text-gray-500 mb-4"> @@ -110,14 +122,9 @@ function LessonSelector({ userId }: { userId: number }) { Lesson 5 </Button> </div> - + <div className="mt-4"> - <Button - variant="ghost" - size="sm" - asChild - className="text-blue-500" - > + <Button variant="ghost" size="sm" asChild className="text-blue-500"> <a href="/">Back to Home</a> </Button> </div> @@ -125,4 +132,4 @@ function LessonSelector({ userId }: { userId: number }) { </Card> </div> ); -}
\ No newline at end of file +} |