Merge branch 'main' into feat/oidc-preserve-consent

This commit is contained in:
Stavros
2026-06-21 12:53:07 +03:00
54 changed files with 2348 additions and 839 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ type ScreenParams = {
oidc_ticket?: string;
oidc_scope?: string;
oidc_name?: string;
oidc_show_consent?: boolean;
oidc_prompt?: "none" | "login";
};
const zodScreenParams = z.object({
@@ -15,7 +15,7 @@ const zodScreenParams = z.object({
oidc_ticket: z.string().optional(),
oidc_scope: z.string().optional(),
oidc_name: z.string().optional(),
oidc_show_consent: z.stringbool().optional(),
oidc_prompt: z.enum(["none", "login"]).optional(),
});
export function useScreenParams(params: URLSearchParams): ScreenParams {
+37 -59
View File
@@ -91,54 +91,41 @@ export const AuthorizePage = () => {
const isOidc = screenParams.login_for === "oidc";
const compiledParams = recompileScreenParams(screenParams);
const { mutate: authorizeMutate, isPending: authorizeIsPending } =
useMutation({
mutationFn: () => {
return axios.post("/api/oidc/authorize-complete", {
ticket: screenParams.oidc_ticket,
});
},
mutationKey: ["authorize", screenParams.oidc_ticket],
onSuccess: (data) => {
toast.info(t("authorizeSuccessTitle"), {
description: t("authorizeSuccessSubtitle"),
});
window.location.replace(data.data.redirect_uri);
},
onError: (error) => {
window.location.replace(
`/error?error=${encodeURIComponent(error.message)}`,
);
},
});
// TODO: maybe a better way to do this
const shouldAutoAuthorize =
auth.authenticated &&
isOidc &&
screenParams.oidc_ticket !== undefined &&
screenParams.oidc_scope !== undefined &&
screenParams.oidc_prompt === "none";
const { mutate: authorizeMutate, isPending: authorizePending } = useMutation({
mutationFn: () => {
return axios.post("/api/oidc/authorize-complete", {
ticket: screenParams.oidc_ticket,
});
},
mutationKey: ["authorize", screenParams.oidc_ticket],
onSuccess: (data) => {
toast.info(t("authorizeSuccessTitle"), {
description: t("authorizeSuccessSubtitle"),
});
window.location.replace(data.data.redirect_uri);
},
onError: (error) => {
window.location.replace(
`/error?error=${encodeURIComponent(error.message)}`,
);
},
});
useEffect(() => {
if (
!isOidc ||
screenParams.oidc_ticket === undefined ||
screenParams.oidc_scope === undefined ||
!auth.authenticated
) {
return;
}
if (screenParams.oidc_show_consent === false) {
if (shouldAutoAuthorize) {
authorizeMutate();
}
}, [
isOidc,
screenParams.oidc_ticket,
screenParams.oidc_scope,
screenParams.oidc_show_consent,
auth.authenticated,
authorizeMutate,
]);
}, [shouldAutoAuthorize, authorizeMutate]);
if (
!isOidc ||
screenParams.oidc_ticket === undefined ||
screenParams.oidc_scope === undefined
) {
if (!isOidc || !screenParams.oidc_ticket || !screenParams.oidc_scope) {
return (
<Navigate
to={`/error?error=${encodeURIComponent(t("authorizeErrorInvalidParams"))}`}
@@ -147,26 +134,13 @@ export const AuthorizePage = () => {
);
}
if (!auth.authenticated) {
if (!auth.authenticated || screenParams.oidc_prompt === "login") {
return <Navigate to={`/login${compiledParams}`} replace />;
}
const scopes =
screenParams.oidc_scope.split(" ").filter((s) => s.trim() !== "") || [];
if (screenParams.oidc_show_consent === false) {
return (
<Card>
<CardHeader className="gap-1.5">
<CardTitle className="text-xl">Authorizing</CardTitle>
<CardDescription>
You will soon be redirected to your application...
</CardDescription>
</CardHeader>
</Card>
);
}
return (
<Card>
<CardHeader className="mb-2">
@@ -208,12 +182,16 @@ export const AuthorizePage = () => {
</CardContent>
)}
<CardFooter className="flex flex-col items-stretch gap-3">
<Button onClick={() => authorizeMutate()} loading={authorizeIsPending}>
<Button
onClick={() => authorizeMutate()}
loading={authorizePending}
disabled={shouldAutoAuthorize}
>
{t("authorizeTitle")}
</Button>
<Button
onClick={() => navigate(`/logout${compiledParams}`)}
disabled={authorizeIsPending}
disabled={authorizePending || shouldAutoAuthorize}
variant="outline"
>
{t("cancelTitle")}
+1 -1
View File
@@ -11,7 +11,7 @@ export const ErrorPage = () => {
const { t } = useTranslation();
const { search } = useLocation();
const searchParams = new URLSearchParams(search);
const error = searchParams.get("error") ?? "";
const error = searchParams.get("error") || "";
return (
<Card>
+8 -3
View File
@@ -63,7 +63,10 @@ export const LoginPage = () => {
const searchParams = new URLSearchParams(search);
const screenParams = useScreenParams(searchParams);
const compiledParams = recompileScreenParams(screenParams);
const compiledParams = recompileScreenParams({
...screenParams,
oidc_prompt: undefined,
});
const loginForUrl = useLoginFor({
login_for: screenParams.login_for,
compiledParams,
@@ -168,7 +171,8 @@ export const LoginPage = () => {
!auth.authenticated &&
isOauthAutoRedirect &&
!hasAutoRedirectedRef.current &&
screenParams.login_for !== undefined
screenParams.redirect_uri &&
screenParams.login_for
) {
hasAutoRedirectedRef.current = true;
oauthMutate(oauth.autoRedirect);
@@ -180,6 +184,7 @@ export const LoginPage = () => {
oauth.autoRedirect,
isOauthAutoRedirect,
screenParams.login_for,
screenParams.redirect_uri,
]);
useEffect(() => {
@@ -194,7 +199,7 @@ export const LoginPage = () => {
};
}, [redirectTimer, redirectButtonTimer]);
if (auth.authenticated) {
if (auth.authenticated && screenParams.oidc_prompt !== "login") {
return <Navigate to={loginForUrl} replace />;
}