check callback from lichess server

This commit is contained in:
2026-06-07 22:44:31 +02:00
parent 585bb5549f
commit 0888df153f
9 changed files with 154 additions and 9 deletions
+5 -5
View File
@@ -1,12 +1,12 @@
interface IProps {
onClickHandler: () => void;
}
import { useLichess } from "#/hooks/useLichess.ts";
const LichessButton = () => {
const { lichessLogin, lichessLogout } = useLichess();
const LichessButton = ({ onClickHandler }: IProps) => {
return (
<button
className="text-(--accent) border rounded-xl px-6 py-3 cursor-pointer flex items-center gap-2 p-2 hover:bg-(--accent) hover:text-(--header-bg) transition-colors duration-300 ease-in-out"
onClick={onClickHandler}
onClick={lichessLogin}
type="button"
>
<div className="w-6 h-6">
+31
View File
@@ -0,0 +1,31 @@
import { useServerFn } from "@tanstack/react-start";
import { useState } from "react";
import { getToken, login } from "#/server/lichess.ts";
export const useLichess = () => {
const [lichessUser, setLichessUser] = useState();
const triggerLogin = useServerFn(login);
const trigggerGetToken = useServerFn(getToken);
console.log("Lichess User:", lichessUser);
const lichessTokenVerification = async ({ code }) => {
await trigggerGetToken();
};
const lichessLogin = async () => {
// TODO add try/catch with toast
await triggerLogin();
};
const lichessLogout = () => {
console.log("Lichess Logout");
};
return {
lichessLogin,
lichessLogout,
lichessTokenVerification,
};
};
+17
View File
@@ -0,0 +1,17 @@
import { useEffect } from "react";
import { useLichess } from "#/hooks/useLichess.ts";
interface IProps {
code: string;
}
export const useLichessCallback = ({ code }: IProps) => {
const { lichessTokenVerification } = useLichess();
useEffect(() => {
if (!code) return;
const response = lichessTokenVerification({ code });
console.log("Response:", response);
}, [code, lichessTokenVerification]);
};
+12
View File
@@ -0,0 +1,12 @@
import { createHash } from "node:crypto";
export const base64UrlEncode = (str: Buffer) => {
return str
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
};
export const sha256 = (buffer: string) =>
createHash("sha256").update(buffer).digest();
+13
View File
@@ -0,0 +1,13 @@
import { getRouteApi } from "@tanstack/react-router";
import { useLichessCallback } from "#/hooks/useLichessCallback.ts";
const routeApi = getRouteApi("/callback");
const Callback = () => {
const { code } = routeApi.useSearch();
useLichessCallback({ code });
return <div>{code}</div>;
};
export default Callback;
+1 -1
View File
@@ -36,7 +36,7 @@ const Chessboard = () => {
return (
<main className="px-4 pb-8 place-self-center min-h-[calc(100vh-226px)]">
<Section title="Convert PGN to PDF">
<LichessButton onClickHandler={() => console.log("Lichess Login")} /> or{" "}
<LichessButton /> or{" "}
<input
type="file"
id="file-input"
+21 -3
View File
@@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as ContactRouteImport } from './routes/contact'
import { Route as ChessboardRouteImport } from './routes/chessboard'
import { Route as CallbackRouteImport } from './routes/callback'
import { Route as IndexRouteImport } from './routes/index'
const ContactRoute = ContactRouteImport.update({
@@ -23,6 +24,11 @@ const ChessboardRoute = ChessboardRouteImport.update({
path: '/chessboard',
getParentRoute: () => rootRouteImport,
} as any)
const CallbackRoute = CallbackRouteImport.update({
id: '/callback',
path: '/callback',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
@@ -31,30 +37,34 @@ const IndexRoute = IndexRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/callback': typeof CallbackRoute
'/chessboard': typeof ChessboardRoute
'/contact': typeof ContactRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/callback': typeof CallbackRoute
'/chessboard': typeof ChessboardRoute
'/contact': typeof ContactRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/callback': typeof CallbackRoute
'/chessboard': typeof ChessboardRoute
'/contact': typeof ContactRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/chessboard' | '/contact'
fullPaths: '/' | '/callback' | '/chessboard' | '/contact'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/chessboard' | '/contact'
id: '__root__' | '/' | '/chessboard' | '/contact'
to: '/' | '/callback' | '/chessboard' | '/contact'
id: '__root__' | '/' | '/callback' | '/chessboard' | '/contact'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
CallbackRoute: typeof CallbackRoute
ChessboardRoute: typeof ChessboardRoute
ContactRoute: typeof ContactRoute
}
@@ -75,6 +85,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ChessboardRouteImport
parentRoute: typeof rootRouteImport
}
'/callback': {
id: '/callback'
path: '/callback'
fullPath: '/callback'
preLoaderRoute: typeof CallbackRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
@@ -87,6 +104,7 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
CallbackRoute: CallbackRoute,
ChessboardRoute: ChessboardRoute,
ContactRoute: ContactRoute,
}
+19
View File
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";
import Callback from "#/pages/Callback.tsx";
const callbackSchema = z.object({
code: z.string(),
state: z.string(),
});
// type CallbackProps = z.infer<typeof callbackSchema>;
export const Route = createFileRoute("/callback")({
validateSearch: (search) => callbackSchema.parse(search),
component: RouteComponent,
});
function RouteComponent() {
return <Callback />;
}
+35
View File
@@ -0,0 +1,35 @@
import { randomBytes } from "node:crypto";
import { redirect } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { env } from "#/env.ts";
import { base64UrlEncode, sha256 } from "#/lib/encodeDecode.ts";
export const getToken = createServerFn({ method: "POST" }).handler(async () => {
console.log("getTokens");
});
export const login = createServerFn({ method: "POST" }).handler(async () => {
const createVerifier = () => base64UrlEncode(randomBytes(32));
const createChallenge = (verifier: string) =>
base64UrlEncode(sha256(verifier));
const verifier = createVerifier();
const challenge = createChallenge(verifier);
const params = new URLSearchParams({
response_type: "code",
client_id: env.LICHESS_CLIENT_ID || "",
redirect_uri: `${process.env.NODE_ENV === "production" ? `https://${env.LICHESS_CLIENT_ID}/callback` : "http://localhost:3000/callback"}`,
scope: "study:read",
code_challenge_method: "S256",
code_challenge: challenge,
});
throw redirect({
href: `https://lichess.org/oauth?${params.toString()}`,
statusCode: 302,
headers: {
"Set-Cookie": `lichess_oauth_verifier=${verifier}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=600`, // Expires in 10 mins
},
});
});