mirror of
https://github.com/TheRealOwenRees/chess-scribe-website.git
synced 2026-07-23 01:46:57 +00:00
add zod schema and validation to server
This commit is contained in:
+44
-30
@@ -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 () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user