mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-31 22:25:43 +00:00 
			
		
		
		
	 5c866bad1a
			
		
	
	5c866bad1a
	
	
	
		
			
			* feat: add flag decoder (candidate) * refactor: finalize flags decoder * feat: add env decoder * feat: add oauth config parsing logic * feat: implement backend logic for multiple oauth providers * feat: implement multiple oauth providers in the frontend * feat: add some default icons * chore: add credits for parser * feat: style oauth auto redirect screen * fix: bot suggestions * refactor: rework decoders using simpler and more efficient pattern * refactor: rework oauth name database migration
		
			
				
	
	
		
			183 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { Button } from "@/components/ui/button";
 | |
| import {
 | |
|   Card,
 | |
|   CardDescription,
 | |
|   CardFooter,
 | |
|   CardHeader,
 | |
|   CardTitle,
 | |
| } from "@/components/ui/card";
 | |
| import { useAppContext } from "@/context/app-context";
 | |
| import { useUserContext } from "@/context/user-context";
 | |
| import { isValidUrl } from "@/lib/utils";
 | |
| import { Trans, useTranslation } from "react-i18next";
 | |
| import { Navigate, useLocation, useNavigate } from "react-router";
 | |
| import { useEffect, useState } from "react";
 | |
| 
 | |
| export const ContinuePage = () => {
 | |
|   const { cookieDomain } = 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(redirectUri) : false;
 | |
|   const redirectUriObj = isValidRedirectUri
 | |
|     ? new URL(redirectUri as string)
 | |
|     : null;
 | |
|   const isTrustedRedirectUri =
 | |
|     redirectUriObj !== null
 | |
|       ? redirectUriObj.hostname === cookieDomain ||
 | |
|         redirectUriObj.hostname.endsWith(`.${cookieDomain}`)
 | |
|       : false;
 | |
|   const isAllowedRedirectProto =
 | |
|     redirectUriObj !== null
 | |
|       ? redirectUriObj.protocol === "https:" ||
 | |
|         redirectUriObj.protocol === "http:"
 | |
|       : false;
 | |
|   const isHttpsDowngrade =
 | |
|     redirectUriObj !== null
 | |
|       ? redirectUriObj.protocol === "http:" &&
 | |
|         window.location.protocol === "https:"
 | |
|       : false;
 | |
| 
 | |
|   const handleRedirect = () => {
 | |
|     setLoading(true);
 | |
|     window.location.assign(redirectUriObj!.toString());
 | |
|   };
 | |
| 
 | |
|   useEffect(() => {
 | |
|     if (
 | |
|       !isLoggedIn ||
 | |
|       !isValidRedirectUri ||
 | |
|       !isTrustedRedirectUri ||
 | |
|       !isAllowedRedirectProto ||
 | |
|       isHttpsDowngrade
 | |
|     ) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     const auto = setTimeout(() => {
 | |
|       handleRedirect();
 | |
|     }, 100);
 | |
| 
 | |
|     const reveal = setTimeout(() => {
 | |
|       setLoading(false);
 | |
|       setShowRedirectButton(true);
 | |
|     }, 5000);
 | |
| 
 | |
|     return () => {
 | |
|       clearTimeout(auto);
 | |
|       clearTimeout(reveal);
 | |
|     };
 | |
|   }, []);
 | |
| 
 | |
|   if (!isLoggedIn) {
 | |
|     return (
 | |
|       <Navigate
 | |
|         to={`/login?redirect_uri=${encodeURIComponent(redirectUri || "")}`}
 | |
|         replace
 | |
|       />
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   if (!isValidRedirectUri || !isAllowedRedirectProto) {
 | |
|     return <Navigate to="/logout" replace />;
 | |
|   }
 | |
| 
 | |
|   if (!isTrustedRedirectUri) {
 | |
|     return (
 | |
|       <Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
 | |
|         <CardHeader>
 | |
|           <CardTitle className="text-3xl">
 | |
|             {t("continueUntrustedRedirectTitle")}
 | |
|           </CardTitle>
 | |
|           <CardDescription>
 | |
|             <Trans
 | |
|               i18nKey="continueUntrustedRedirectSubtitle"
 | |
|               t={t}
 | |
|               components={{
 | |
|                 code: <code />,
 | |
|               }}
 | |
|               values={{ cookieDomain }}
 | |
|             />
 | |
|           </CardDescription>
 | |
|         </CardHeader>
 | |
|         <CardFooter className="flex flex-col items-stretch gap-2">
 | |
|           <Button
 | |
|             onClick={handleRedirect}
 | |
|             loading={loading}
 | |
|             variant="destructive"
 | |
|           >
 | |
|             {t("continueTitle")}
 | |
|           </Button>
 | |
|           <Button
 | |
|             onClick={() => navigate("/logout")}
 | |
|             variant="outline"
 | |
|             disabled={loading}
 | |
|           >
 | |
|             {t("cancelTitle")}
 | |
|           </Button>
 | |
|         </CardFooter>
 | |
|       </Card>
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   if (isHttpsDowngrade) {
 | |
|     return (
 | |
|       <Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
 | |
|         <CardHeader>
 | |
|           <CardTitle className="text-3xl">
 | |
|             {t("continueInsecureRedirectTitle")}
 | |
|           </CardTitle>
 | |
|           <CardDescription>
 | |
|             <Trans
 | |
|               i18nKey="continueInsecureRedirectSubtitle"
 | |
|               t={t}
 | |
|               components={{
 | |
|                 code: <code />,
 | |
|               }}
 | |
|             />
 | |
|           </CardDescription>
 | |
|         </CardHeader>
 | |
|         <CardFooter className="flex flex-col items-stretch gap-2">
 | |
|           <Button onClick={handleRedirect} loading={loading} variant="warning">
 | |
|             {t("continueTitle")}
 | |
|           </Button>
 | |
|           <Button
 | |
|             onClick={() => navigate("/logout")}
 | |
|             variant="outline"
 | |
|             disabled={loading}
 | |
|           >
 | |
|             {t("cancelTitle")}
 | |
|           </Button>
 | |
|         </CardFooter>
 | |
|       </Card>
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <Card className="min-w-xs sm:min-w-sm">
 | |
|       <CardHeader>
 | |
|         <CardTitle className="text-3xl">
 | |
|           {t("continueRedirectingTitle")}
 | |
|         </CardTitle>
 | |
|         <CardDescription>{t("continueRedirectingSubtitle")}</CardDescription>
 | |
|       </CardHeader>
 | |
|       {showRedirectButton && (
 | |
|         <CardFooter className="flex flex-col items-stretch">
 | |
|           <Button onClick={handleRedirect}>
 | |
|             {t("continueRedirectManually")}
 | |
|           </Button>
 | |
|         </CardFooter>
 | |
|       )}
 | |
|     </Card>
 | |
|   );
 | |
| };
 |