diff --git a/frontend/src/components/layout/layout.tsx b/frontend/src/components/layout/layout.tsx index 0ed0b39..3461000 100644 --- a/frontend/src/components/layout/layout.tsx +++ b/frontend/src/components/layout/layout.tsx @@ -1,7 +1,7 @@ import { useAppContext } from "@/context/app-context"; import { LanguageSelector } from "../language/language"; import { Outlet } from "react-router"; -import { useState } from "react"; +import { useCallback, useState } from "react"; import { DomainWarning } from "../domain-warning/domain-warning"; const BaseLayout = ({ children }: { children: React.ReactNode }) => { @@ -24,20 +24,17 @@ const BaseLayout = ({ children }: { children: React.ReactNode }) => { export const Layout = () => { const { appUrl } = useAppContext(); - const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(false); + const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => { + return window.sessionStorage.getItem("ignoreDomainWarning") === "true"; + }); const currentUrl = window.location.origin; - const sessionIgnore = window.sessionStorage.getItem("ignoreDomainWarning"); - const handleIgnore = () => { + const handleIgnore = useCallback(() => { window.sessionStorage.setItem("ignoreDomainWarning", "true"); setIgnoreDomainWarning(true); - }; + }, [setIgnoreDomainWarning]); - if ( - !ignoreDomainWarning && - appUrl !== currentUrl && - sessionIgnore !== "true" - ) { + if (!ignoreDomainWarning && appUrl !== currentUrl) { return ( { + const { rootDomain } = useAppContext(); const { isLoggedIn } = useUserContext(); + const { search } = useLocation(); + const { t } = useTranslation(); + const navigate = useNavigate(); + + const [loading, setLoading] = useState(false); + const [showRedirectButton, setShowRedirectButton] = useState(false); + + const searchParams = new URLSearchParams(search); + const redirectUri = searchParams.get("redirect_uri"); + + const isValidRedirectUri = + redirectUri !== null ? isValidUrl(DOMPurify.sanitize(redirectUri)) : false; + const redirectUriObj = isValidRedirectUri + ? new URL(redirectUri as string) + : null; + const isTrustedRedirectUri = + redirectUriObj !== null + ? redirectUriObj.hostname === rootDomain || + redirectUriObj.hostname.endsWith(`.${rootDomain}`) + : false; + const isHttpsDowngrade = + redirectUriObj !== null + ? redirectUriObj.protocol === "http:" && + window.location.protocol === "https:" + : false; + + const handleRedirect = () => { + setLoading(true); + window.location.replace(DOMPurify.sanitize(redirectUriObj!.toString())); + }; + + useEffect(() => { + if ( + !isLoggedIn || + !isValidRedirectUri || + !isTrustedRedirectUri || + isHttpsDowngrade + ) { + return; + } + + setTimeout(() => { + handleRedirect(); + }, 100); + + setTimeout(() => { + setLoading(false); + setShowRedirectButton(true); + }, 1000); + }, []); if (!isLoggedIn) { return ; } - const { rootDomain } = useAppContext(); - const { search } = useLocation(); - const [loading, setLoading] = useState(false); - const [showRedirectButton, setShowRedirectButton] = useState(false); - - const searchParams = new URLSearchParams(search); - const redirectURI = searchParams.get("redirect_uri"); - - if (!redirectURI) { + if (!isValidRedirectUri) { return ; } - if (!isValidUrl(DOMPurify.sanitize(redirectURI))) { - return ; - } - - const { t } = useTranslation(); - const navigate = useNavigate(); - - const handleRedirect = () => { - setLoading(true); - window.location.href = DOMPurify.sanitize(redirectURI); - }; - - const redirectURLObj = new URL(redirectURI); - - if ( - !(redirectURLObj.hostname == rootDomain) && - !redirectURLObj.hostname.endsWith(`.${rootDomain}`) - ) { + if (!isTrustedRedirectUri) { return ( @@ -88,10 +114,7 @@ export const ContinuePage = () => { ); } - if ( - redirectURLObj.protocol === "http:" && - window.location.protocol === "https:" - ) { + if (isHttpsDowngrade) { return ( @@ -124,16 +147,6 @@ export const ContinuePage = () => { ); } - useEffect(() => { - setTimeout(() => { - handleRedirect(); - }, 100); - setTimeout(() => { - setLoading(false); - setShowRedirectButton(true); - }, 1000); - }, []); - return ( diff --git a/frontend/src/pages/login-page.tsx b/frontend/src/pages/login-page.tsx index 53f183f..64d8b3c 100644 --- a/frontend/src/pages/login-page.tsx +++ b/frontend/src/pages/login-page.tsx @@ -24,12 +24,8 @@ import { toast } from "sonner"; export const LoginPage = () => { const { isLoggedIn } = useUserContext(); - - if (isLoggedIn) { - return ; - } - - const { configuredProviders, title, oauthAutoRedirect, genericName } = useAppContext(); + const { configuredProviders, title, oauthAutoRedirect, genericName } = + useAppContext(); const { search } = useLocation(); const { t } = useTranslation(); const isMounted = useIsMounted(); @@ -54,7 +50,7 @@ export const LoginPage = () => { }); setTimeout(() => { - window.location.href = data.data.url; + window.location.replace(data.data.url); }, 500); }, onError: () => { @@ -100,6 +96,7 @@ export const LoginPage = () => { if ( oauthConfigured && configuredProviders.includes(oauthAutoRedirect) && + !isLoggedIn && redirectUri ) { oauthMutation.mutate(oauthAutoRedirect); @@ -107,6 +104,10 @@ export const LoginPage = () => { } }, []); + if (isLoggedIn) { + return ; + } + return ( @@ -126,7 +127,10 @@ export const LoginPage = () => { icon={} className="w-full" onClick={() => oauthMutation.mutate("google")} - loading={oauthMutation.isPending && oauthMutation.variables === "google"} + loading={ + oauthMutation.isPending && + oauthMutation.variables === "google" + } disabled={oauthMutation.isPending || loginMutation.isPending} /> )} @@ -136,7 +140,10 @@ export const LoginPage = () => { icon={} className="w-full" onClick={() => oauthMutation.mutate("github")} - loading={oauthMutation.isPending && oauthMutation.variables === "github"} + loading={ + oauthMutation.isPending && + oauthMutation.variables === "github" + } disabled={oauthMutation.isPending || loginMutation.isPending} /> )} @@ -146,7 +153,10 @@ export const LoginPage = () => { icon={} className="w-full" onClick={() => oauthMutation.mutate("generic")} - loading={oauthMutation.isPending && oauthMutation.variables === "generic"} + loading={ + oauthMutation.isPending && + oauthMutation.variables === "generic" + } disabled={oauthMutation.isPending || loginMutation.isPending} /> )} diff --git a/frontend/src/pages/logout-page.tsx b/frontend/src/pages/logout-page.tsx index 30b2af8..e453db7 100644 --- a/frontend/src/pages/logout-page.tsx +++ b/frontend/src/pages/logout-page.tsx @@ -17,11 +17,6 @@ import { toast } from "sonner"; export const LogoutPage = () => { const { provider, username, isLoggedIn, email } = useUserContext(); - - if (!isLoggedIn) { - return ; - } - const { genericName } = useAppContext(); const { t } = useTranslation(); @@ -33,7 +28,7 @@ export const LogoutPage = () => { description: t("logoutSuccessSubtitle"), }); - setTimeout(async () => { + setTimeout(() => { window.location.replace("/login"); }, 500); }, @@ -44,6 +39,10 @@ export const LogoutPage = () => { }, }); + if (!isLoggedIn) { + return ; + } + return ( diff --git a/frontend/src/pages/totp-page.tsx b/frontend/src/pages/totp-page.tsx index 7d4ebad..8a07e6d 100644 --- a/frontend/src/pages/totp-page.tsx +++ b/frontend/src/pages/totp-page.tsx @@ -19,11 +19,6 @@ import { toast } from "sonner"; export const TotpPage = () => { const { totpPending } = useUserContext(); - - if (!totpPending) { - return ; - } - const { t } = useTranslation(); const { search } = useLocation(); const formId = useId(); @@ -52,6 +47,10 @@ export const TotpPage = () => { }, }); + if (!totpPending) { + return ; + } + return ( diff --git a/frontend/src/pages/unauthorized-page.tsx b/frontend/src/pages/unauthorized-page.tsx index e0bd6ca..007e01c 100644 --- a/frontend/src/pages/unauthorized-page.tsx +++ b/frontend/src/pages/unauthorized-page.tsx @@ -12,6 +12,10 @@ import { Navigate, useLocation, useNavigate } from "react-router"; export const UnauthorizedPage = () => { const { search } = useLocation(); + const { t } = useTranslation(); + const navigate = useNavigate(); + + const [loading, setLoading] = useState(false); const searchParams = new URLSearchParams(search); const username = searchParams.get("username"); @@ -19,19 +23,15 @@ export const UnauthorizedPage = () => { const groupErr = searchParams.get("groupErr"); const ip = searchParams.get("ip"); - if (!username && !ip) { - return ; - } - - const { t } = useTranslation(); - const navigate = useNavigate(); - const [loading, setLoading] = useState(false); - const handleRedirect = () => { setLoading(true); navigate("/login"); }; + if (!username && !ip) { + return ; + } + let i18nKey = "unauthorizedLoginSubtitle"; if (resource) {