Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6e14116
add auto login check on shareable playgrpund
Cristhianzl Apr 6, 2026
13b2e88
add session management on shareable playground for logged suers
Cristhianzl Apr 6, 2026
5ae538c
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 6, 2026
975797c
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Apr 6, 2026
07bb8f4
add token usage and eta on shareable playground
Cristhianzl Apr 6, 2026
42eb933
Merge branch 'cz/fix-login-shareable-playground' of github.com:langfl…
Cristhianzl Apr 6, 2026
f0fe6cf
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 6, 2026
3398fa4
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Apr 6, 2026
86a9375
ruff style fixes and jest tests
Cristhianzl Apr 6, 2026
7e64bdf
Merge branch 'cz/fix-login-shareable-playground' of github.com:langfl…
Cristhianzl Apr 6, 2026
e12b032
fix shareable playground on auto login true
Cristhianzl Apr 6, 2026
b2cfe04
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 6, 2026
f81fc12
change to use graph td
Cristhianzl Apr 6, 2026
01f2e48
add e2e tests to validate
Cristhianzl Apr 6, 2026
f2dbe3b
Merge branch 'cz/fix-login-shareable-playground' of github.com:langfl…
Cristhianzl Apr 6, 2026
3d4de98
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 6, 2026
1da08c8
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Apr 6, 2026
79d8df4
fix tooltip
Cristhianzl Apr 7, 2026
7755989
Merge branch 'cz/fix-login-shareable-playground' of github.com:langfl…
Cristhianzl Apr 7, 2026
6c2ae40
fix sun color icon when is selected
Cristhianzl Apr 7, 2026
fb25a8c
Merge branch 'release-1.9.0' into cz/fix-login-shareable-playground
Cristhianzl Apr 7, 2026
dd6c48f
fix test playwright
Cristhianzl Apr 7, 2026
1bad524
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CustomNavigate } from "@/customization/components/custom-navigate";
import { consumeRedirectUrl } from "@/hooks/use-sanitize-redirect-url";
import useAuthStore from "@/stores/authStore";

export const ProtectedLoginRoute = ({ children }) => {
Expand All @@ -7,7 +8,7 @@ export const ProtectedLoginRoute = ({ children }) => {

if (autoLogin === true || isAuthenticated) {
const urlParams = new URLSearchParams(window.location.search);
const redirectPath = urlParams.get("redirect");
const redirectPath = urlParams.get("redirect") || consumeRedirectUrl();

if (redirectPath) {
return <CustomNavigate to={redirectPath} replace />;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/**
* Tests for PlaygroundAuthGate Decision Logic
*
* The PlaygroundAuthGate protects the /playground/:id/ route by:
* - Allowing access when auto-login is enabled (autoLogin === true)
* - Allowing access when user is already authenticated
* - Redirecting to /login when auto-login is disabled and user is not authenticated
* - Showing a loading state while auth checks are in progress
*/

import { renderHook } from "@testing-library/react";

type PlaygroundAuthState = "loading" | "authenticated" | "redirect";

function computePlaygroundAuthState(
autoLogin: boolean | null,
isAuthenticated: boolean,
isAutoLoginFetched: boolean,
isSessionFetched: boolean,
): PlaygroundAuthState {
const isAuthCheckComplete =
(isAutoLoginFetched || isAuthenticated) && isSessionFetched;

if (!isAuthCheckComplete) return "loading";
if (autoLogin === true || isAuthenticated) return "authenticated";
if (autoLogin === false && !isAuthenticated) return "redirect";
return "loading";
}

function usePlaygroundAuthState(
autoLogin: boolean | null,
isAuthenticated: boolean,
isAutoLoginFetched: boolean,
isSessionFetched: boolean,
): PlaygroundAuthState {
return computePlaygroundAuthState(
autoLogin,
isAuthenticated,
isAutoLoginFetched,
isSessionFetched,
);
}

describe("PlaygroundAuthGate - Auth Decision Logic", () => {
describe("Auto-Login Enabled (autoLogin === true)", () => {
it("should_return_authenticated_when_autoLogin_is_true_and_fetched", () => {
const result = computePlaygroundAuthState(true, false, true, true);
expect(result).toBe("authenticated");
});

it("should_return_authenticated_when_autoLogin_true_regardless_of_isAuthenticated", () => {
expect(computePlaygroundAuthState(true, true, true, true)).toBe(
"authenticated",
);
expect(computePlaygroundAuthState(true, false, true, true)).toBe(
"authenticated",
);
});
});

describe("User Already Authenticated (autoLogin === false)", () => {
it("should_return_authenticated_when_user_is_authenticated_and_autoLogin_false", () => {
const result = computePlaygroundAuthState(false, true, true, true);
expect(result).toBe("authenticated");
});

it("should_return_authenticated_when_user_is_authenticated_even_before_autoLogin_fetched", () => {
// isAuthenticated=true makes isAuthCheckComplete=true (skips autoLogin fetch)
const result = computePlaygroundAuthState(false, true, false, true);
expect(result).toBe("authenticated");
});
});

describe("Not Authenticated with Auto-Login Disabled", () => {
it("should_return_redirect_when_autoLogin_false_and_not_authenticated", () => {
const result = computePlaygroundAuthState(false, false, true, true);
expect(result).toBe("redirect");
});
});

describe("Loading States", () => {
it("should_return_loading_when_autoLogin_is_null_and_not_authenticated", () => {
const result = computePlaygroundAuthState(null, false, false, false);
expect(result).toBe("loading");
});

it("should_return_loading_when_session_not_yet_fetched", () => {
const result = computePlaygroundAuthState(false, false, true, false);
expect(result).toBe("loading");
});

it("should_return_loading_when_autoLogin_not_yet_fetched_and_not_authenticated", () => {
const result = computePlaygroundAuthState(null, false, false, true);
expect(result).toBe("loading");
});
});

describe("State Transitions", () => {
it("should_transition_from_loading_to_authenticated_when_autoLogin_becomes_true", () => {
const { result, rerender } = renderHook(
({
autoLogin,
isAuthenticated,
isAutoLoginFetched,
isSessionFetched,
}) =>
usePlaygroundAuthState(
autoLogin,
isAuthenticated,
isAutoLoginFetched,
isSessionFetched,
),
{
initialProps: {
autoLogin: null as boolean | null,
isAuthenticated: false,
isAutoLoginFetched: false,
isSessionFetched: false,
},
},
);

expect(result.current).toBe("loading");

rerender({
autoLogin: true,
isAuthenticated: true,
isAutoLoginFetched: true,
isSessionFetched: true,
});

expect(result.current).toBe("authenticated");
});

it("should_transition_from_loading_to_redirect_when_autoLogin_becomes_false_with_no_auth", () => {
const { result, rerender } = renderHook(
({
autoLogin,
isAuthenticated,
isAutoLoginFetched,
isSessionFetched,
}) =>
usePlaygroundAuthState(
autoLogin,
isAuthenticated,
isAutoLoginFetched,
isSessionFetched,
),
{
initialProps: {
autoLogin: null as boolean | null,
isAuthenticated: false,
isAutoLoginFetched: false,
isSessionFetched: false,
},
},
);

expect(result.current).toBe("loading");

rerender({
autoLogin: false,
isAuthenticated: false,
isAutoLoginFetched: true,
isSessionFetched: true,
});

expect(result.current).toBe("redirect");
});

it("should_transition_from_loading_to_authenticated_when_session_restores_auth", () => {
const { result, rerender } = renderHook(
({
autoLogin,
isAuthenticated,
isAutoLoginFetched,
isSessionFetched,
}) =>
usePlaygroundAuthState(
autoLogin,
isAuthenticated,
isAutoLoginFetched,
isSessionFetched,
),
{
initialProps: {
autoLogin: false as boolean | null,
isAuthenticated: false,
isAutoLoginFetched: true,
isSessionFetched: false,
},
},
);

expect(result.current).toBe("loading");

// Session check completes and restores auth (user had valid cookie)
rerender({
autoLogin: false,
isAuthenticated: true,
isAutoLoginFetched: true,
isSessionFetched: true,
});

expect(result.current).toBe("authenticated");
});
});

describe("Edge Cases", () => {
it("should_handle_all_false_states_as_loading", () => {
const result = computePlaygroundAuthState(null, false, false, false);
expect(result).toBe("loading");
});

it("should_prioritize_isAuthenticated_over_autoLogin_false", () => {
// User manually logged in even though auto-login is disabled
const result = computePlaygroundAuthState(false, true, true, true);
expect(result).toBe("authenticated");
});

it("should_not_redirect_while_session_check_is_pending", () => {
// autoLogin=false but session not yet checked - user might have valid cookie
const result = computePlaygroundAuthState(false, false, true, false);
expect(result).toBe("loading");
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useContext, useEffect, useState } from "react";
import { Navigate, useParams } from "react-router-dom";
import { AuthContext } from "@/contexts/authContext";
import {
useGetAuthSession,
useGetAutoLogin,
} from "@/controllers/API/queries/auth";
import useAuthStore from "@/stores/authStore";
import { LoadingPage } from "@/pages/LoadingPage";

export function PlaygroundAuthGate({
children,
}: {
children: React.ReactNode;
}) {
const { id } = useParams();
const { setUserData, storeApiKey } = useContext(AuthContext);
const setIsAuthenticated = useAuthStore((state) => state.setIsAuthenticated);
const setIsAdmin = useAuthStore((state) => state.setIsAdmin);
const autoLogin = useAuthStore((state) => state.autoLogin);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);

const [sessionProcessed, setSessionProcessed] = useState(false);

const { data: sessionData, isFetched: isSessionFetched } =
useGetAuthSession();
const { isFetched: isAutoLoginFetched } = useGetAutoLogin();

useEffect(() => {
if (!isSessionFetched) return;

if (sessionData?.authenticated && sessionData.user) {
setUserData(sessionData.user);
setIsAuthenticated(true);
setIsAdmin(sessionData.user.is_superuser || false);
if (sessionData.store_api_key) {
storeApiKey(sessionData.store_api_key);
}
} else if (sessionData && !sessionData.authenticated) {
setIsAuthenticated(false);
}
setSessionProcessed(true);
}, [sessionData, isSessionFetched]);

const isAuthCheckComplete =
(isAutoLoginFetched || isAuthenticated) && sessionProcessed;

if (!isAuthCheckComplete) {
return <LoadingPage />;
}

if (autoLogin === false && !isAuthenticated) {
const currentPath = `/playground/${id}/`;
return <Navigate to={`/login?redirect=${currentPath}`} replace />;
}

return <>{children}</>;
}
Loading
Loading