mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-06-03 01:50:14 +00:00
refactor: use ticket approach for oidc flow
This commit is contained in:
@@ -1,76 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const oidcParamsSchema = z.object({
|
|
||||||
scope: z.string().min(1),
|
|
||||||
response_type: z.string().min(1),
|
|
||||||
client_id: z.string().min(1),
|
|
||||||
redirect_uri: z.string().min(1),
|
|
||||||
state: z.string().optional(),
|
|
||||||
nonce: z.string().optional(),
|
|
||||||
code_challenge: z.string().optional(),
|
|
||||||
code_challenge_method: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
function b64urlDecode(s: string): string {
|
|
||||||
const base64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
||||||
return atob(base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "="));
|
|
||||||
}
|
|
||||||
|
|
||||||
function decodeRequestObject(jwt: string): Record<string, string> {
|
|
||||||
try {
|
|
||||||
// Must have exactly 3 parts: header, payload, signature
|
|
||||||
const parts = jwt.split(".");
|
|
||||||
if (parts.length !== 3) return {};
|
|
||||||
|
|
||||||
// Header must specify "alg": "none" and signature must be empty string
|
|
||||||
const header = JSON.parse(b64urlDecode(parts[0]));
|
|
||||||
if (!header || typeof header !== "object" || header.alg !== "none" || parts[2] !== "") return {};
|
|
||||||
|
|
||||||
const payload = JSON.parse(b64urlDecode(parts[1]));
|
|
||||||
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
|
|
||||||
const result: Record<string, string> = {};
|
|
||||||
for (const [k, v] of Object.entries(payload)) {
|
|
||||||
if (typeof v === "string") result[k] = v;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useOIDCParams = (
|
|
||||||
params: URLSearchParams,
|
|
||||||
): {
|
|
||||||
values: z.infer<typeof oidcParamsSchema>;
|
|
||||||
issues: string[];
|
|
||||||
isOidc: boolean;
|
|
||||||
compiled: string;
|
|
||||||
} => {
|
|
||||||
const obj = Object.fromEntries(params.entries());
|
|
||||||
|
|
||||||
// RFC 9101 / OIDC Core 6.1: if `request` param present, decode JWT payload
|
|
||||||
// and merge claims over top-level params (JWT claims take precedence)
|
|
||||||
const requestJwt = params.get("request");
|
|
||||||
if (requestJwt) {
|
|
||||||
const claims = decodeRequestObject(requestJwt);
|
|
||||||
Object.assign(obj, claims);
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = oidcParamsSchema.safeParse(obj);
|
|
||||||
|
|
||||||
if (parsed.success) {
|
|
||||||
return {
|
|
||||||
values: parsed.data,
|
|
||||||
issues: [],
|
|
||||||
isOidc: true,
|
|
||||||
compiled: new URLSearchParams(parsed.data).toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
issues: parsed.error.issues.map((issue) => issue.path.toString()),
|
|
||||||
values: {} as z.infer<typeof oidcParamsSchema>,
|
|
||||||
isOidc: false,
|
|
||||||
compiled: "",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
type ScreenParams = {
|
||||||
|
login_for?: "oidc" | "app";
|
||||||
|
redirect_url?: string;
|
||||||
|
oidc_ticket?: string;
|
||||||
|
oidc_scope?: string;
|
||||||
|
oidc_name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zodScreenParams = z.object({
|
||||||
|
login_for: z.enum(["oidc", "app"]).optional(),
|
||||||
|
redirect_url: z.string().optional(),
|
||||||
|
oidc_ticket: z.string().optional(),
|
||||||
|
oidc_scope: z.string().optional(),
|
||||||
|
oidc_name: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useScreenParams(params: URLSearchParams): ScreenParams {
|
||||||
|
const paramsObj = Object.fromEntries(params.entries());
|
||||||
|
const parsed = zodScreenParams.safeParse(paramsObj);
|
||||||
|
if (!parsed.success) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return parsed.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recompileScreenParams(params: ScreenParams): string {
|
||||||
|
const p = new URLSearchParams(
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(params).filter(([, v]) => v !== null),
|
||||||
|
) as Record<string, string>,
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
if (p.length > 0) {
|
||||||
|
return "?" + p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
@@ -35,7 +35,10 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<Route element={<Layout />} errorElement={<ErrorPage />}>
|
<Route element={<Layout />} errorElement={<ErrorPage />}>
|
||||||
<Route path="/" element={<App />} />
|
<Route path="/" element={<App />} />
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/authorize" element={<AuthorizePage />} />
|
<Route
|
||||||
|
path="/oidc/authorize"
|
||||||
|
element={<AuthorizePage />}
|
||||||
|
/>
|
||||||
<Route path="/logout" element={<LogoutPage />} />
|
<Route path="/logout" element={<LogoutPage />} />
|
||||||
<Route path="/continue" element={<ContinuePage />} />
|
<Route path="/continue" element={<ContinuePage />} />
|
||||||
<Route path="/totp" element={<TotpPage />} />
|
<Route path="/totp" element={<TotpPage />} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useUserContext } from "@/context/user-context";
|
import { useUserContext } from "@/context/user-context";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { Navigate, useNavigate } from "react-router";
|
import { Navigate, useNavigate } from "react-router";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
import {
|
import {
|
||||||
@@ -10,11 +10,9 @@ import {
|
|||||||
CardFooter,
|
CardFooter,
|
||||||
CardContent,
|
CardContent,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { getOidcClientInfoSchema } from "@/schemas/oidc-schemas";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useOIDCParams } from "@/lib/hooks/oidc";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import { Mail, MapPin, Phone, Shield, User, Users } from "lucide-react";
|
import { Mail, MapPin, Phone, Shield, User, Users } from "lucide-react";
|
||||||
@@ -23,6 +21,10 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import {
|
||||||
|
recompileScreenParams,
|
||||||
|
useScreenParams,
|
||||||
|
} from "@/lib/hooks/screen-params";
|
||||||
|
|
||||||
type Scope = {
|
type Scope = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -84,27 +86,17 @@ export const AuthorizePage = () => {
|
|||||||
const scopeMap = createScopeMap(t);
|
const scopeMap = createScopeMap(t);
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const oidcParams = useOIDCParams(searchParams);
|
const screenParams = useScreenParams(searchParams);
|
||||||
|
const isOidc = screenParams.login_for === "oidc";
|
||||||
const getClientInfo = useQuery({
|
const compiledParams = recompileScreenParams(screenParams);
|
||||||
queryKey: ["client", oidcParams.values.client_id],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await fetch(
|
|
||||||
`/api/oidc/clients/${encodeURIComponent(oidcParams.values.client_id)}`,
|
|
||||||
);
|
|
||||||
const data = await getOidcClientInfoSchema.parseAsync(await res.json());
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
enabled: oidcParams.isOidc,
|
|
||||||
});
|
|
||||||
|
|
||||||
const authorizeMutation = useMutation({
|
const authorizeMutation = useMutation({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
return axios.post("/api/oidc/authorize", {
|
return axios.post("/api/oidc/authorize-complete", {
|
||||||
...oidcParams.values,
|
ticket: screenParams.oidc_ticket,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
mutationKey: ["authorize", oidcParams.values.client_id],
|
mutationKey: ["authorize", screenParams.oidc_ticket],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
toast.info(t("authorizeSuccessTitle"), {
|
toast.info(t("authorizeSuccessTitle"), {
|
||||||
description: t("authorizeSuccessSubtitle"),
|
description: t("authorizeSuccessSubtitle"),
|
||||||
@@ -118,56 +110,38 @@ export const AuthorizePage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (oidcParams.issues.length > 0) {
|
if (
|
||||||
|
!isOidc ||
|
||||||
|
screenParams.oidc_ticket === undefined ||
|
||||||
|
screenParams.oidc_scope === undefined
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={`/error?error=${encodeURIComponent(t("authorizeErrorMissingParams", { missingParams: oidcParams.issues.join(", ") }))}`}
|
to={`/error?error=${encodeURIComponent(t("authorizeErrorInvalidParams"))}`}
|
||||||
replace
|
replace
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!auth.authenticated) {
|
if (!auth.authenticated) {
|
||||||
return <Navigate to={`/login?${oidcParams.compiled}`} replace />;
|
return <Navigate to={`/login${compiledParams}`} replace />;
|
||||||
}
|
|
||||||
|
|
||||||
if (getClientInfo.isLoading) {
|
|
||||||
return (
|
|
||||||
<Card className="gap-0">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-xl">
|
|
||||||
{t("authorizeLoadingTitle")}
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<CardDescription>{t("authorizeLoadingSubtitle")}</CardDescription>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getClientInfo.isError) {
|
|
||||||
return (
|
|
||||||
<Navigate
|
|
||||||
to={`/error?error=${encodeURIComponent(t("authorizeErrorClientInfo"))}`}
|
|
||||||
replace
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopes =
|
const scopes =
|
||||||
oidcParams.values.scope.split(" ").filter((s) => s.trim() !== "") || [];
|
screenParams.oidc_scope.split(" ").filter((s) => s.trim() !== "") || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="mb-2">
|
<CardHeader className="mb-2">
|
||||||
<div className="flex flex-col gap-3 items-center justify-center text-center">
|
<div className="flex flex-col gap-3 items-center justify-center text-center">
|
||||||
<div className="bg-accent-foreground box-content text-muted text-xl font-bold font-sans rounded-lg size-8 p-2 flex items-center justify-center">
|
<div className="bg-accent-foreground box-content text-muted text-xl font-bold font-sans rounded-lg size-8 p-2 flex items-center justify-center">
|
||||||
{getClientInfo.data?.name.slice(0, 1) || "U"}
|
{screenParams.oidc_name !== undefined
|
||||||
|
? screenParams.oidc_name.slice(0, 1)
|
||||||
|
: "U"}
|
||||||
</div>
|
</div>
|
||||||
<CardTitle className="text-xl">
|
<CardTitle className="text-xl">
|
||||||
{t("authorizeCardTitle", {
|
{t("authorizeCardTitle", {
|
||||||
app: getClientInfo.data?.name || "Unknown",
|
app: screenParams.oidc_name || "Unknown",
|
||||||
})}
|
})}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-sm max-w-sm">
|
<CardDescription className="text-sm max-w-sm">
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { OAuthButton } from "@/components/ui/oauth-button";
|
|||||||
import { SeperatorWithChildren } from "@/components/ui/separator";
|
import { SeperatorWithChildren } from "@/components/ui/separator";
|
||||||
import { useAppContext } from "@/context/app-context";
|
import { useAppContext } from "@/context/app-context";
|
||||||
import { useUserContext } from "@/context/user-context";
|
import { useUserContext } from "@/context/user-context";
|
||||||
import { useOIDCParams } from "@/lib/hooks/oidc";
|
|
||||||
import { LoginSchema } from "@/schemas/login-schema";
|
import { LoginSchema } from "@/schemas/login-schema";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import axios, { AxiosError } from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
@@ -26,6 +25,10 @@ import { useEffect, useId, useRef, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Navigate, useLocation } from "react-router";
|
import { Navigate, useLocation } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import {
|
||||||
|
recompileScreenParams,
|
||||||
|
useScreenParams,
|
||||||
|
} from "@/lib/hooks/screen-params";
|
||||||
|
|
||||||
const iconMap: Record<string, React.ReactNode> = {
|
const iconMap: Record<string, React.ReactNode> = {
|
||||||
google: <GoogleIcon />,
|
google: <GoogleIcon />,
|
||||||
@@ -46,7 +49,9 @@ export const LoginPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
const [showRedirectButton, setShowRedirectButton] = useState(false);
|
||||||
const [useTailscale, setUseTailscale] = useState(tailscale.nodeName !== undefined);
|
const [useTailscale, setUseTailscale] = useState(
|
||||||
|
tailscale.nodeName !== undefined,
|
||||||
|
);
|
||||||
|
|
||||||
const hasAutoRedirectedRef = useRef(false);
|
const hasAutoRedirectedRef = useRef(false);
|
||||||
|
|
||||||
@@ -56,17 +61,19 @@ export const LoginPage = () => {
|
|||||||
const formId = useId();
|
const formId = useId();
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const redirectUri = searchParams.get("redirect_uri") || undefined;
|
const screenParams = useScreenParams(searchParams);
|
||||||
const oidcParams = useOIDCParams(searchParams);
|
const isOidc = screenParams.login_for === "oidc";
|
||||||
|
const compiledParams = recompileScreenParams(screenParams);
|
||||||
|
|
||||||
const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
|
const [isOauthAutoRedirect, setIsOauthAutoRedirect] = useState(
|
||||||
providers.find((provider) => provider.id === oauth.autoRedirect) !==
|
providers.find((provider) => provider.id === oauth.autoRedirect) !==
|
||||||
undefined && redirectUri !== undefined,
|
undefined && screenParams.redirect_url !== undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const oauthProviders = providers.filter(
|
const oauthProviders = providers.filter(
|
||||||
(provider) => provider.id !== "local" && provider.id !== "ldap",
|
(provider) => provider.id !== "local" && provider.id !== "ldap",
|
||||||
);
|
);
|
||||||
|
|
||||||
const userAuthConfigured =
|
const userAuthConfigured =
|
||||||
providers.find(
|
providers.find(
|
||||||
(provider) => provider.id === "local" || provider.id === "ldap",
|
(provider) => provider.id === "local" || provider.id === "ldap",
|
||||||
@@ -79,16 +86,7 @@ export const LoginPage = () => {
|
|||||||
variables: oauthVariables,
|
variables: oauthVariables,
|
||||||
} = useMutation({
|
} = useMutation({
|
||||||
mutationFn: (provider: string) => {
|
mutationFn: (provider: string) => {
|
||||||
const getParams = function (): string {
|
return axios.get(`/api/oauth/url/${provider}${compiledParams}`);
|
||||||
if (oidcParams.isOidc) {
|
|
||||||
return `?${oidcParams.compiled}`;
|
|
||||||
}
|
|
||||||
if (redirectUri) {
|
|
||||||
return `?redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
return axios.get(`/api/oauth/url/${provider}${getParams()}`);
|
|
||||||
},
|
},
|
||||||
mutationKey: ["oauth"],
|
mutationKey: ["oauth"],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@@ -119,13 +117,7 @@ export const LoginPage = () => {
|
|||||||
mutationKey: ["login"],
|
mutationKey: ["login"],
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.data.totpPending) {
|
if (data.data.totpPending) {
|
||||||
if (oidcParams.isOidc) {
|
window.location.replace(`/totp${compiledParams}`);
|
||||||
window.location.replace(`/totp?${oidcParams.compiled}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.location.replace(
|
|
||||||
`/totp${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,13 +126,7 @@ export const LoginPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
if (oidcParams.isOidc) {
|
window.location.replace(`/continue${compiledParams}`);
|
||||||
window.location.replace(`/authorize?${oidcParams.compiled}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.location.replace(
|
|
||||||
`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
|
||||||
);
|
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
onError: (error: AxiosError) => {
|
onError: (error: AxiosError) => {
|
||||||
@@ -163,13 +149,7 @@ export const LoginPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
if (oidcParams.isOidc) {
|
window.location.replace(`/continue${compiledParams}`);
|
||||||
window.location.replace(`/authorize?${oidcParams.compiled}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.location.replace(
|
|
||||||
`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
|
||||||
);
|
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@@ -184,7 +164,7 @@ export const LoginPage = () => {
|
|||||||
!auth.authenticated &&
|
!auth.authenticated &&
|
||||||
isOauthAutoRedirect &&
|
isOauthAutoRedirect &&
|
||||||
!hasAutoRedirectedRef.current &&
|
!hasAutoRedirectedRef.current &&
|
||||||
redirectUri !== undefined
|
screenParams.redirect_url !== undefined
|
||||||
) {
|
) {
|
||||||
hasAutoRedirectedRef.current = true;
|
hasAutoRedirectedRef.current = true;
|
||||||
oauthMutate(oauth.autoRedirect);
|
oauthMutate(oauth.autoRedirect);
|
||||||
@@ -195,7 +175,7 @@ export const LoginPage = () => {
|
|||||||
hasAutoRedirectedRef,
|
hasAutoRedirectedRef,
|
||||||
oauth.autoRedirect,
|
oauth.autoRedirect,
|
||||||
isOauthAutoRedirect,
|
isOauthAutoRedirect,
|
||||||
redirectUri,
|
screenParams.redirect_url,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -210,17 +190,12 @@ export const LoginPage = () => {
|
|||||||
};
|
};
|
||||||
}, [redirectTimer, redirectButtonTimer]);
|
}, [redirectTimer, redirectButtonTimer]);
|
||||||
|
|
||||||
if (auth.authenticated && oidcParams.isOidc) {
|
if (auth.authenticated && isOidc) {
|
||||||
return <Navigate to={`/authorize?${oidcParams.compiled}`} replace />;
|
return <Navigate to={`/authorize${compiledParams}`} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.authenticated && redirectUri !== undefined) {
|
if (auth.authenticated && screenParams.redirect_url !== undefined) {
|
||||||
return (
|
return <Navigate to={`/continue${compiledParams}`} replace />;
|
||||||
<Navigate
|
|
||||||
to={`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`}
|
|
||||||
replace
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.authenticated) {
|
if (auth.authenticated) {
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ import { useEffect, useId, useRef } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Navigate, useLocation } from "react-router";
|
import { Navigate, useLocation } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useOIDCParams } from "@/lib/hooks/oidc";
|
import {
|
||||||
|
recompileScreenParams,
|
||||||
|
useScreenParams,
|
||||||
|
} from "@/lib/hooks/screen-params";
|
||||||
|
|
||||||
export const TotpPage = () => {
|
export const TotpPage = () => {
|
||||||
const { totp } = useUserContext();
|
const { totp } = useUserContext();
|
||||||
@@ -27,8 +30,8 @@ export const TotpPage = () => {
|
|||||||
const redirectTimer = useRef<number | null>(null);
|
const redirectTimer = useRef<number | null>(null);
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
const searchParams = new URLSearchParams(search);
|
||||||
const redirectUri = searchParams.get("redirect_uri") || undefined;
|
const screenParams = useScreenParams(searchParams);
|
||||||
const oidcParams = useOIDCParams(searchParams);
|
const compiledParams = recompileScreenParams(screenParams);
|
||||||
|
|
||||||
const totpMutation = useMutation({
|
const totpMutation = useMutation({
|
||||||
mutationFn: (values: TotpSchema) => axios.post("/api/user/totp", values),
|
mutationFn: (values: TotpSchema) => axios.post("/api/user/totp", values),
|
||||||
@@ -39,14 +42,7 @@ export const TotpPage = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
redirectTimer.current = window.setTimeout(() => {
|
redirectTimer.current = window.setTimeout(() => {
|
||||||
if (oidcParams.isOidc) {
|
window.location.replace(`/continue${compiledParams}`);
|
||||||
window.location.replace(`/authorize?${oidcParams.compiled}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.replace(
|
|
||||||
`/continue${redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : ""}`,
|
|
||||||
);
|
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const getOidcClientInfoSchema = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
@@ -59,7 +59,7 @@ func (app *BootstrapApp) setupRouter() error {
|
|||||||
|
|
||||||
controller.NewContextController(app.log, app.config, app.runtime, apiRouter)
|
controller.NewContextController(app.log, app.config, app.runtime, apiRouter)
|
||||||
controller.NewOAuthController(app.log, app.config, app.runtime, apiRouter, app.services.authService)
|
controller.NewOAuthController(app.log, app.config, app.runtime, apiRouter, app.services.authService)
|
||||||
controller.NewOIDCController(app.log, app.services.oidcService, app.runtime, apiRouter, &app.router.RouterGroup)
|
controller.NewOIDCController(app.log, app.services.oidcService, app.runtime, apiRouter, &engine.RouterGroup)
|
||||||
controller.NewProxyController(app.log, app.runtime, apiRouter, app.services.accessControlService, app.services.authService, app.services.policyEngine)
|
controller.NewProxyController(app.log, app.runtime, apiRouter, app.services.accessControlService, app.services.authService, app.services.policyEngine)
|
||||||
controller.NewUserController(app.log, app.runtime, apiRouter, app.services.authService)
|
controller.NewUserController(app.log, app.runtime, apiRouter, app.services.authService)
|
||||||
controller.NewResourcesController(app.config, &engine.RouterGroup)
|
controller.NewResourcesController(app.config, &engine.RouterGroup)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ type AuthorizeScreenParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizeCompleteRequest struct {
|
type AuthorizeCompleteRequest struct {
|
||||||
Ticket string `json:"oidc_ticket" binding:"required"`
|
Ticket string `json:"ticket" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOIDCController(
|
func NewOIDCController(
|
||||||
@@ -166,7 +166,7 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
|||||||
ticket := controller.oidc.CreateAuthorizeRequestTicket(req)
|
ticket := controller.oidc.CreateAuthorizeRequestTicket(req)
|
||||||
|
|
||||||
queries, err := query.Values(AuthorizeScreenParams{
|
queries, err := query.Values(AuthorizeScreenParams{
|
||||||
LoginFor: req.ClientID,
|
LoginFor: "oidc",
|
||||||
OIDCTicket: ticket,
|
OIDCTicket: ticket,
|
||||||
OIDCScope: req.Scope,
|
OIDCScope: req.Scope,
|
||||||
OIDCName: client.Name,
|
OIDCName: client.Name,
|
||||||
|
|||||||
Reference in New Issue
Block a user