diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0559b26f..f258eb98 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,9 +2,9 @@ import { Navigate } from "react-router"; import { useUserContext } from "./context/user-context"; export const App = () => { - const { isLoggedIn } = useUserContext(); + const { auth } = useUserContext(); - if (isLoggedIn) { + if (auth.authenticated) { return ; } diff --git a/frontend/src/components/layout/layout.tsx b/frontend/src/components/layout/layout.tsx index a71a1aa9..d59aadf3 100644 --- a/frontend/src/components/layout/layout.tsx +++ b/frontend/src/components/layout/layout.tsx @@ -6,17 +6,17 @@ import { DomainWarning } from "../domain-warning/domain-warning"; import { ThemeToggle } from "../theme-toggle/theme-toggle"; const BaseLayout = ({ children }: { children: React.ReactNode }) => { - const { backgroundImage, title } = useAppContext(); + const { ui } = useAppContext(); useEffect(() => { - document.title = title; - }, [title]); + document.title = ui.title; + }, [ui.title]); return (
{ }; export const Layout = () => { - const { appUrl, warningsEnabled } = useAppContext(); + const { app, ui } = useAppContext(); const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => { return window.sessionStorage.getItem("ignoreDomainWarning") === "true"; }); @@ -42,11 +42,15 @@ export const Layout = () => { setIgnoreDomainWarning(true); }, [setIgnoreDomainWarning]); - if (!ignoreDomainWarning && warningsEnabled && appUrl !== currentUrl) { + if ( + !ignoreDomainWarning && + ui.warningsEnabled && + !app.trustedDomains.includes(currentUrl) + ) { return ( handleIgnore()} /> diff --git a/frontend/src/pages/authorize-page.tsx b/frontend/src/pages/authorize-page.tsx index f2b7c11d..91f8f9c9 100644 --- a/frontend/src/pages/authorize-page.tsx +++ b/frontend/src/pages/authorize-page.tsx @@ -77,7 +77,7 @@ const createScopeMap = (t: TFunction<"translation", undefined>): Scope[] => { }; export const AuthorizePage = () => { - const { isLoggedIn } = useUserContext(); + const { auth } = useUserContext(); const { search } = useLocation(); const { t } = useTranslation(); const navigate = useNavigate(); @@ -127,7 +127,7 @@ export const AuthorizePage = () => { ); } - if (!isLoggedIn) { + if (!auth.authenticated) { return ; } diff --git a/frontend/src/pages/continue-page.tsx b/frontend/src/pages/continue-page.tsx index b7cdd743..82846c64 100644 --- a/frontend/src/pages/continue-page.tsx +++ b/frontend/src/pages/continue-page.tsx @@ -14,8 +14,8 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useRedirectUri } from "@/lib/hooks/redirect-uri"; export const ContinuePage = () => { - const { cookieDomain, warningsEnabled } = useAppContext(); - const { isLoggedIn } = useUserContext(); + const { app, ui } = useAppContext(); + const { auth } = useUserContext(); const { search } = useLocation(); const { t } = useTranslation(); const navigate = useNavigate(); @@ -29,17 +29,18 @@ export const ContinuePage = () => { const { url, valid, trusted, allowedProto, httpsDowngrade } = useRedirectUri( redirectUri, - cookieDomain, + app.cookieDomain, ); const urlHref = url?.href; const hasValidRedirect = valid && allowedProto; - const showUntrustedWarning = hasValidRedirect && !trusted && warningsEnabled; + const showUntrustedWarning = + hasValidRedirect && !trusted && ui.warningsEnabled; const showInsecureWarning = - hasValidRedirect && httpsDowngrade && warningsEnabled; + hasValidRedirect && httpsDowngrade && ui.warningsEnabled; const shouldAutoRedirect = - isLoggedIn && + auth.authenticated && hasValidRedirect && !showUntrustedWarning && !showInsecureWarning; @@ -77,7 +78,7 @@ export const ContinuePage = () => { }; }, [shouldAutoRedirect, redirectToTarget]); - if (!isLoggedIn) { + if (!auth.authenticated) { return ( { components={{ code: , }} - values={{ cookieDomain }} + values={{ cookieDomain: app.cookieDomain }} shouldUnescape={true} /> diff --git a/frontend/src/pages/forgot-password-page.tsx b/frontend/src/pages/forgot-password-page.tsx index 7d47d02f..6438e353 100644 --- a/frontend/src/pages/forgot-password-page.tsx +++ b/frontend/src/pages/forgot-password-page.tsx @@ -13,7 +13,7 @@ import Markdown from "react-markdown"; import { useLocation } from "react-router"; export const ForgotPasswordPage = () => { - const { forgotPasswordMessage } = useAppContext(); + const { ui } = useAppContext(); const { t } = useTranslation(); const { search } = useLocation(); const searchParams = new URLSearchParams(search); @@ -26,8 +26,8 @@ export const ForgotPasswordPage = () => { - {forgotPasswordMessage !== "" - ? forgotPasswordMessage + {ui.forgotPasswordMessage !== "" + ? ui.forgotPasswordMessage : t("forgotPasswordMessage")} diff --git a/frontend/src/pages/login-page.tsx b/frontend/src/pages/login-page.tsx index c8c9f23d..91ad6411 100644 --- a/frontend/src/pages/login-page.tsx +++ b/frontend/src/pages/login-page.tsx @@ -36,13 +36,13 @@ const iconMap: Record = { }; export const LoginPage = () => { - const { isLoggedIn, tailscaleNodeName } = useUserContext(); - const { providers, title, oauthAutoRedirect } = useAppContext(); + const { auth, tailscale } = useUserContext(); + const { ui, oauth, auth: cauth } = useAppContext(); const { search } = useLocation(); const { t } = useTranslation(); const [showRedirectButton, setShowRedirectButton] = useState(false); - const [useTailscale, setUseTailscale] = useState(tailscaleNodeName !== ""); + const [useTailscale, setUseTailscale] = useState(tailscale.nodeName !== ""); const hasAutoRedirectedRef = useRef(false); @@ -56,15 +56,15 @@ export const LoginPage = () => { const oidcParams = useOIDCParams(searchParams); const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState( - providers.find((provider) => provider.id === oauthAutoRedirect) !== + cauth.providers.find((provider) => provider.id === oauth.autoRedirect) !== undefined && redirectUri !== undefined, ); - const oauthProviders = providers.filter( + const oauthProviders = cauth.providers.filter( (provider) => provider.id !== "local" && provider.id !== "ldap", ); const userAuthConfigured = - providers.find( + cauth.providers.find( (provider) => provider.id === "local" || provider.id === "ldap", ) !== undefined; @@ -177,19 +177,19 @@ export const LoginPage = () => { useEffect(() => { if ( - !isLoggedIn && + !auth.authenticated && isOauthAutoRedirect && !hasAutoRedirectedRef.current && redirectUri !== undefined ) { hasAutoRedirectedRef.current = true; - oauthMutate(oauthAutoRedirect); + oauthMutate(oauth.autoRedirect); } }, [ - isLoggedIn, + auth.authenticated, oauthMutate, hasAutoRedirectedRef, - oauthAutoRedirect, + oauth.autoRedirect, isOauthAutoRedirect, redirectUri, ]); @@ -206,11 +206,11 @@ export const LoginPage = () => { }; }, [redirectTimer, redirectButtonTimer]); - if (isLoggedIn && oidcParams.isOidc) { + if (auth.authenticated && oidcParams.isOidc) { return ; } - if (isLoggedIn && redirectUri !== undefined) { + if (auth.authenticated && redirectUri !== undefined) { return ( { ); } - if (isLoggedIn) { + if (auth.authenticated) { return ; } @@ -272,7 +272,7 @@ export const LoginPage = () => { credentials?
- Machine Name: {tailscaleNodeName} + Machine Name: {tailscale.nodeName}
@@ -299,8 +299,8 @@ export const LoginPage = () => { return ( - {title} - {providers.length > 0 && ( + {ui.title} + {cauth.providers.length > 0 && ( {oauthProviders.length !== 0 ? t("loginTitle") @@ -338,7 +338,7 @@ export const LoginPage = () => { })()} /> )} - {providers.length == 0 && ( + {cauth.providers.length == 0 && (
             {t("failedToFetchProvidersTitle")}
           
diff --git a/frontend/src/pages/logout-page.tsx b/frontend/src/pages/logout-page.tsx index f60bd656..7f500d7e 100644 --- a/frontend/src/pages/logout-page.tsx +++ b/frontend/src/pages/logout-page.tsx @@ -13,9 +13,11 @@ import { useEffect, useRef } from "react"; import { Trans, useTranslation } from "react-i18next"; import { Navigate } from "react-router"; import { toast } from "sonner"; +import { type UseMutationResult } from "@tanstack/react-query"; +import { type AxiosResponse } from "axios"; export const LogoutPage = () => { - const { provider, username, isLoggedIn, email, oauthName } = useUserContext(); + const { auth, oauth, tailscale } = useUserContext(); const { t } = useTranslation(); const redirectTimer = useRef(null); @@ -47,42 +49,74 @@ export const LogoutPage = () => { }; }, [redirectTimer]); - if (!isLoggedIn) { + if (!auth.authenticated) { return ; } + if (oauth.active) { + return ( + + , + }} + values={{ + username: auth.email, + provider: oauth.displayName, + }} + shouldUnescape={true} + /> + + ); + } + + if (auth.providerId === "tailscale") { + return ( + + You are currently logged in with the Tailscale integration identified by + the {tailscale.nodeName} node. Click the button below to + log out. + + ); + } + + return ( + + , + }} + values={{ + username: auth.username, + }} + shouldUnescape={true} + /> + + ); +}; + +interface LogoutLayoutProps { + children: React.ReactNode; + logoutMutation: UseMutationResult< + //eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-empty-object-type + AxiosResponse, + Error, + void, + unknown + >; +} + +function LogoutLayout({ children, logoutMutation }: LogoutLayoutProps) { + const { t } = useTranslation(); return ( {t("logoutTitle")} - - {provider !== "local" && provider !== "ldap" ? ( - , - }} - values={{ - username: email, - provider: oauthName, - }} - shouldUnescape={true} - /> - ) : ( - , - }} - values={{ - username, - }} - shouldUnescape={true} - /> - )} - + {children}