Files
tinyauth/frontend/src/pages/continue-page.tsx
Stavros 5c866bad1a feat: multiple oauth providers (#355)
* 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
2025-09-16 13:28:28 +03:00

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>
);
};