Skip to content

Commit

Permalink
Fix Authentication Race Conditions Causing Premature Redirections (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
WillKirkmanM authored Nov 26, 2024
2 parents 4088654 + aa06a51 commit 97ea83a
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 75 deletions.
100 changes: 58 additions & 42 deletions apps/web/app/(unprotected)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,51 +35,67 @@ export default function MainPage() {
const [showServerURLInput, setShowServerURLInput] = useState(false);
const [showServerSelect, setShowServerSelect] = useState(false);
const { push } = useRouter();
const { session } = useSession()
const { session, isLoading } = useSession()

useEffect(() => {
if (process.env.LOCAL_APP) push("/home")

const checkServerUrl = async () => {
setLoading(true);

try {
const storedServer = localStorage.getItem("server");
const response = await fetch(
`${(storedServer && JSON.parse(storedServer).local_address) || window.location.origin}/api/s/server/info`
);
let serverInfo: ServerInfo = await response.json();

if (serverInfo.product_name && serverInfo.startup_wizard_completed) {
localStorage.setItem("server", JSON.stringify(serverInfo));

if (session) {
push("/home");
} else {
push("/login");
}
} else {
if (!serverInfo.startup_wizard_completed && session) {
push("/setup/library");
} else {
push("/setup");
}
}
} catch (error) {
console.error("Error checking server URL:", error);
const storedServer = localStorage.getItem("server");
if (storedServer) {
setShowServerSelect(true);
} else {
setShowServerURLInput(true);
}
} finally {
setLoading(false);
if (process.env.LOCAL_APP) {
push("/home");
return;
}
};

checkServerUrl();
}, [push, session]);

const checkServerUrl = async () => {
if (isLoading) return;

setLoading(true);

try {
if (session) {
const storedServer = localStorage.getItem("server");
if (storedServer) {
const serverInfo = JSON.parse(storedServer);
if (serverInfo.startup_wizard_completed) {
push("/home");
return;
}
}
}

const storedServer = localStorage.getItem("server");
const response = await fetch(
`${(storedServer && JSON.parse(storedServer).local_address) || window.location.origin}/api/s/server/info`
);
let serverInfo: ServerInfo = await response.json();

localStorage.setItem("server", JSON.stringify(serverInfo));

if (!isLoading) {
if (serverInfo.product_name && serverInfo.startup_wizard_completed) {
if (session) {
push("/home");
} else {
push("/login");
}
} else if (!serverInfo.startup_wizard_completed && session) {
push("/setup/library");
} else {
push("/setup");
}
}
} catch (error) {
console.error("Error checking server URL:", error);
const storedServer = localStorage.getItem("server");
if (storedServer) {
setShowServerSelect(true);
} else {
setShowServerURLInput(true);
}
} finally {
setLoading(false);
}
};

checkServerUrl();
}, [push, session, isLoading]);

const form = useForm<FormData>({
resolver: zodResolver(schema),
Expand Down
93 changes: 65 additions & 28 deletions apps/web/components/Layout/SplashScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client"
"use client";

import pl from '@/assets/pl-tp.png';
import { Loader2Icon } from 'lucide-react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
import { useSession } from '../Providers/AuthProvider';
import pl from "@/assets/pl-tp.png";
import { Loader2Icon } from "lucide-react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";
import { useSession } from "../Providers/AuthProvider";

interface SplashScreenProps {
children: React.ReactNode;
Expand All @@ -14,44 +14,81 @@ interface SplashScreenProps {
const SplashScreen: React.FC<SplashScreenProps> = ({ children }) => {
const [loading, setLoading] = useState(true);
const { push } = useRouter();
const { session } = useSession()
const { session, isLoading } = useSession();

useEffect(() => {
const checkServerUrl = async () => {
const storedServer = localStorage.getItem("server");
const response = await fetch(
`${storedServer && JSON.parse(storedServer).local_address || window.location.origin}/api/s/server/info`
);

if (response.ok) {
if (isLoading) return;

const currentPath = window.location.pathname;
if (
session?.username &&
currentPath !== "/" &&
currentPath !== "/login" &&
currentPath !== "/login/" &&
currentPath.startsWith("/home")
) {
setLoading(false);
return;
}

try {
setLoading(true);

const storedServer = localStorage.getItem("server");
const serverUrl = storedServer
? JSON.parse(storedServer).local_address
: window.location.origin;

const response = await fetch(`${serverUrl}/api/s/server/info`);

if (!response.ok) {
push("/");
return;
}

const serverInfo = await response.json();
if (!serverInfo.startup_wizard_completed) {
push("/setup");
return;
}

if (session?.username) {
const currentPath = window.location.pathname;
const queryParams = window.location.search;
if (currentPath === "/" || currentPath === "/login" || currentPath === "/login/") {
if (
currentPath === "/" ||
currentPath === "/login" ||
currentPath === "/login/"
) {
push("/home");
} else {
push(`${currentPath}${queryParams}`);
}
return;
}

setLoading(false);
} else {
if (currentPath !== "/login" && currentPath !== "/login/") {
push("/login");
setLoading(false);
}
} else {
push("/")
} catch (error) {
console.error("Server check failed:", error);
push("/");
} finally {
setLoading(false);
}
};

checkServerUrl();
}, [push, session]);
}, [push, session, isLoading]);

if (loading) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white">
<div className="flex items-center mb-4">
<Image src={pl} alt="ParsonLabs Logo" width={64} height={64} className="mr-4" />
<Image
src={pl}
alt="ParsonLabs Logo"
width={64}
height={64}
className="mr-4"
/>
<p className="text-6xl font-bold">ParsonLabs Music</p>
</div>
<Loader2Icon className="animate-spin w-12 h-12 mt-4" stroke="#4338ca" />
Expand All @@ -62,4 +99,4 @@ const SplashScreen: React.FC<SplashScreenProps> = ({ children }) => {
return <>{children}</>;
};

export default SplashScreen;
export default SplashScreen;
38 changes: 33 additions & 5 deletions apps/web/components/Providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,56 @@ import getSession, { type ExtendedJWTPayload } from '@/lib/Authentication/JWT/ge

interface AuthContextType {
session: ExtendedJWTPayload | null;
isLoading: boolean;
refreshSession: () => Promise<void>;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);
const AuthContext = createContext<AuthContextType>({
session: null,
isLoading: true,
refreshSession: async () => {}
});

export default function AuthProvider({ children }: { children: ReactNode }) {
const [session, setSession] = useState<ExtendedJWTPayload | null>(null);
const [isLoading, setIsLoading] = useState(true);

const refreshSession = async () => {
try {
setIsLoading(true);
const newSession = await Promise.resolve(getSession());
setSession(newSession);
} catch (error) {
console.error('Failed to refresh session:', error);
setSession(null);
} finally {
setIsLoading(false);
}
};

useEffect(() => {
const session = getSession();
setSession(session);
refreshSession();

const refreshInterval = setInterval(refreshSession, 5 * 60 * 1000);
return () => clearInterval(refreshInterval);
}, []);

return (
<AuthContext.Provider value={{ session }}>
<AuthContext.Provider
value={{
session,
isLoading,
refreshSession
}}
>
{children}
</AuthContext.Provider>
);
}

export function useSession() {
const context = useContext(AuthContext);
if (context === undefined) {
if (!context) {
throw new Error("useSession must be used within an AuthProvider");
}
return context;
Expand Down
1 change: 1 addition & 0 deletions apps/web/components/User/NavbarProfilePicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default function NavbarProfilePicture() {
}, [session])

const { push } = useRouter()

function signOut() {
deleteCookie("plm_accessToken")
deleteCookie("plm_refreshToken")
Expand Down

0 comments on commit 97ea83a

Please sign in to comment.