>;
+}) => {
+ const redirectParam = (await searchParams)?.[ADMIN_REDIRECT_PARAM];
+
return (
-
+
diff --git a/packages/vitnode/src/views/auth/sign-in/form/form.tsx b/packages/vitnode/src/views/auth/sign-in/form/form.tsx
index 7cae30a4a..f640e2a59 100644
--- a/packages/vitnode/src/views/auth/sign-in/form/form.tsx
+++ b/packages/vitnode/src/views/auth/sign-in/form/form.tsx
@@ -15,13 +15,19 @@ import { useFormSignIn } from "./use-form";
export const FormSignIn = ({
isAdmin,
isEmail,
+ redirectTo,
}: {
isAdmin?: boolean;
isEmail: boolean;
+ /** AdminCP path to return to after signing in, from `?redirect=`. */
+ redirectTo?: string;
}) => {
const t = useTranslations("core.auth.sign_in");
const shouldReduceMotion = useReducedMotion();
- const { onSubmit, error, formSchema } = useFormSignIn({ isAdmin });
+ const { onSubmit, error, formSchema } = useFormSignIn({
+ isAdmin,
+ redirectTo,
+ });
return (
diff --git a/packages/vitnode/src/views/auth/sign-in/form/mutation-api.server.ts b/packages/vitnode/src/views/auth/sign-in/form/mutation-api.server.ts
index ffcf66c8f..08126eb59 100644
--- a/packages/vitnode/src/views/auth/sign-in/form/mutation-api.server.ts
+++ b/packages/vitnode/src/views/auth/sign-in/form/mutation-api.server.ts
@@ -7,14 +7,17 @@ import { revalidatePath } from "next/cache";
import type { zodSignInSchema } from "@/api/modules/users/routes/sign-in.route";
import { usersModule } from "@/api/modules/users/users.module";
+import { ADMIN_HOME_PATH, sanitizeAdminRedirect } from "@/lib/admin-redirect";
import { fetcher } from "@/lib/fetcher";
import { redirect } from "@/lib/navigation";
-export const mutationApi = async (
- input: z.infer & {
- isAdmin?: boolean;
- },
-) => {
+export const mutationApi = async ({
+ redirectTo,
+ ...input
+}: z.infer & {
+ isAdmin?: boolean;
+ redirectTo?: string;
+}) => {
const res = await fetcher(usersModule, {
path: "/sign_in",
method: "post",
@@ -35,7 +38,9 @@ export const mutationApi = async (
if (input.isAdmin) {
revalidatePath("/[locale]/admin", "layout");
- await redirect("/admin/core");
+ // `redirectTo` crossed the network as part of the form payload, so it is
+ // sanitised again here rather than trusted from the page that rendered it.
+ await redirect(sanitizeAdminRedirect(redirectTo) ?? ADMIN_HOME_PATH);
return;
}
diff --git a/packages/vitnode/src/views/auth/sign-in/form/use-form.ts b/packages/vitnode/src/views/auth/sign-in/form/use-form.ts
index 6d6d67a03..b18944a9b 100644
--- a/packages/vitnode/src/views/auth/sign-in/form/use-form.ts
+++ b/packages/vitnode/src/views/auth/sign-in/form/use-form.ts
@@ -7,7 +7,13 @@ import type { AutoFormOnSubmit } from "@/components/form/auto-form";
import { mutationApi } from "./mutation-api.server";
-export const useFormSignIn = ({ isAdmin = false }: { isAdmin?: boolean }) => {
+export const useFormSignIn = ({
+ isAdmin = false,
+ redirectTo,
+}: {
+ isAdmin?: boolean;
+ redirectTo?: string;
+}) => {
const [error, setError] = React.useState<"" | "access_denied">("");
const t = useTranslations<"core.auth.sign_in">("core.auth.sign_in");
const tErrors = useTranslations("core.global.errors");
@@ -21,7 +27,7 @@ export const useFormSignIn = ({ isAdmin = false }: { isAdmin?: boolean }) => {
const onSubmit: AutoFormOnSubmit = async values => {
setError("");
- const mutation = await mutationApi({ ...values, isAdmin });
+ const mutation = await mutationApi({ ...values, isAdmin, redirectTo });
if (!mutation?.message) return;
if (mutation?.message !== "Internal Server Error") {