add zod schema and validation to server

This commit is contained in:
2026-06-12 10:58:59 +02:00
parent f475732715
commit 0b32e77d7e
+44 -30
View File
@@ -6,9 +6,30 @@ import {
getCookie, getCookie,
setCookie, setCookie,
} from "@tanstack/react-start/server"; } from "@tanstack/react-start/server";
import { z } from "zod";
import { env } from "#/env.ts"; import { env } from "#/env.ts";
import { base64UrlEncode, sha256 } from "#/lib/encodeDecode.ts"; import { base64UrlEncode, sha256 } from "#/lib/encodeDecode.ts";
export const SessionSchema = z.object({
username: z.string(),
id: z.string(),
token: z.string(),
});
const TokenResponseSchema = z.object({
access_token: z.string(),
expires_in: z.number().optional(),
scope: z.string().optional(),
token_type: z.string().optional(),
});
const LichessAccountSchema = z.object({
id: z.string(),
username: z.string(),
});
// export type Session = z.infer<typeof SessionSchema>;
const createVerifier = () => base64UrlEncode(randomBytes(32)); const createVerifier = () => base64UrlEncode(randomBytes(32));
const createChallenge = (verifier: string) => base64UrlEncode(sha256(verifier)); const createChallenge = (verifier: string) => base64UrlEncode(sha256(verifier));
@@ -22,11 +43,8 @@ export const getSession = createServerFn({ method: "GET" }).handler(
const session = getCookie("lichess-session"); const session = getCookie("lichess-session");
if (!session) return null; if (!session) return null;
try { try {
return JSON.parse(session) as { const parsed = SessionSchema.safeParse(JSON.parse(session));
username: string; return parsed.success ? parsed.data : null;
id: string;
token: string;
};
} catch { } catch {
return null; return null;
} }
@@ -34,25 +52,15 @@ export const getSession = createServerFn({ method: "GET" }).handler(
); );
export const setSession = createServerFn({ method: "POST" }) export const setSession = createServerFn({ method: "POST" })
.inputValidator( .inputValidator(SessionSchema)
(data: { username: string; id: string; token: string }) => data,
)
.handler(async ({ data }) => { .handler(async ({ data }) => {
setCookie( setCookie("lichess-session", JSON.stringify(data), {
"lichess-session", path: "/",
JSON.stringify({ httpOnly: true,
username: data.username, secure: process.env.NODE_ENV === "production",
id: data.id, sameSite: "lax",
token: data.token, maxAge: 60 * 60 * 24 * 7,
}), });
{
path: "/",
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7,
},
);
setCookie("lichess-oauth-verifier", "", { setCookie("lichess-oauth-verifier", "", {
path: "/", path: "/",
@@ -66,12 +74,13 @@ export const setSession = createServerFn({ method: "POST" })
}); });
export const getToken = createServerFn({ method: "POST" }) export const getToken = createServerFn({ method: "POST" })
.inputValidator((data: { code: string }) => data) .inputValidator(z.object({ code: z.string() }))
.handler(async ({ data }) => { .handler(async ({ data }) => {
const verifier = getCookie("lichess-oauth-verifier"); const verifier = getCookie("lichess-oauth-verifier");
if (!verifier) if (!verifier) {
throw new Error("OAuth session expired. Please login again."); throw new Error("OAuth session expired. Please login again.");
}
const response = await fetch("https://lichess.org/api/token", { const response = await fetch("https://lichess.org/api/token", {
method: "POST", method: "POST",
@@ -87,14 +96,16 @@ export const getToken = createServerFn({ method: "POST" })
}), }),
}); });
if (!response.ok) if (!response.ok) {
throw new Error(`Token exchange failed: ${await response.text()}`); throw new Error(`Token exchange failed: ${await response.text()}`);
}
return response.json(); const json = await response.json();
return TokenResponseSchema.parse(json);
}); });
export const getUser = createServerFn({ method: "GET" }) export const getUser = createServerFn({ method: "POST" })
.inputValidator((data: { access_token: string }) => data) .inputValidator(z.object({ access_token: z.string() }))
.handler(async ({ data }) => { .handler(async ({ data }) => {
const response = await fetch("https://lichess.org/api/account", { const response = await fetch("https://lichess.org/api/account", {
headers: { headers: {
@@ -102,7 +113,10 @@ export const getUser = createServerFn({ method: "GET" })
}, },
}); });
return response.json(); if (!response.ok) throw new Error("Failed to fetch Lichess account");
const json = await response.json();
return LichessAccountSchema.parse(json);
}); });
const deleteAccessToken = createServerFn().handler(async () => { const deleteAccessToken = createServerFn().handler(async () => {