Skip to content

Commit

Permalink
feat: Fix sign in callbackUrl bug and prevent signOut when verifying …
Browse files Browse the repository at this point in the history
…email (#81)
  • Loading branch information
RudraPatel2003 authored Dec 26, 2024
1 parent 8fbe3a2 commit f8d5c6c
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 30 deletions.
15 changes: 14 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import withBundleAnalyzer from "@next/bundle-analyzer";

/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
/**
* The router cache is responsible for caching pages.
* This causes issues with your authentication state changes (like when you sign in).
* To fix this, we disable the router cache for dynamic routes.
* https://github.com/vercel/next.js/discussions/65487
* https://nextjs.org/docs/14/app/api-reference/next-config-js/staleTimes
*/
experimental: {
staleTimes: {
dynamic: 0,
},
},
};

const bundleAnalyzerConfig = withBundleAnalyzer({
enabled: process.env.ANALYZE === "true",
Expand Down
9 changes: 7 additions & 2 deletions src/app/api/auth/[...nextauth]/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,18 @@ const authOptions: NextAuthOptions = {
}),
],
callbacks: {
async jwt({ token, user }) {
async jwt({ token, user, trigger, session }) {
// the token gets the User object from the CredentialsProvider's authorize method
// but it only gets it the first time; every time after it is undefined
if (user) {
token.user = user;
}

// if you call the "update" function, refresh the user to reflect changes server-side
if (trigger === "update") {
token.user = session.user;
}

return token;
},
async session({ session, token }) {
Expand All @@ -60,7 +66,6 @@ const authOptions: NextAuthOptions = {
},
pages: {
signIn: "/auth/login",
error: "/auth/login", // The login page will parse the error query parameter passed in
},
secret: process.env.NEXTAUTH_SECRET,
};
Expand Down
4 changes: 2 additions & 2 deletions src/app/test/client/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Box } from "@mui/material";
import { Box, Typography } from "@mui/material";

export default function ClientComponentTestPage() {
return (
Expand All @@ -14,7 +14,7 @@ export default function ClientComponentTestPage() {
width: "100vw",
}}
>
<p>Client component test page</p>
<Typography>Client component test page</Typography>
</Box>
);
}
4 changes: 2 additions & 2 deletions src/app/test/server/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box } from "@mui/material";
import { Box, Typography } from "@mui/material";

export default function ServerComponentTestPage() {
return (
Expand All @@ -11,7 +11,7 @@ export default function ServerComponentTestPage() {
alignItems: "center",
}}
>
<p>Server component test page</p>
<Typography>Server component test page</Typography>
</Box>
);
}
38 changes: 28 additions & 10 deletions src/components/LoginForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { zodResolver } from "@hookform/resolvers/zod";
import LoadingButton from "@mui/lab/LoadingButton";
import { Box, Skeleton, Typography } from "@mui/material";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { signIn } from "next-auth/react";
import { Suspense, useEffect, useState } from "react";
import { Suspense, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";

Expand All @@ -28,6 +28,8 @@ type LoginFormValues = z.infer<typeof loginFormSchema>;
function LoginFormFields() {
const [isLoading, setIsLoading] = useState(false);

const router = useRouter();

const {
control,
handleSubmit,
Expand All @@ -40,17 +42,33 @@ function LoginFormFields() {

const searchParams = useSearchParams();

useEffect(() => {
const error = searchParams.get("error");
if (error) {
setError("root", { message: error });
}
}, [searchParams, setError]);

const onSubmit = async (data: LoginFormValues) => {
setIsLoading(true);
setError("root", { message: "" });
await signIn("credentials", { ...data, callbackUrl: "/dashboard" });

const callbackUrl = searchParams.get("callbackUrl") ?? "/dashboard";

// redirect must be false, otherwise the callbackUrl will be lost
// if the user puts it invalid credentials
const signInResponse = await signIn("credentials", {
...data,
callbackUrl: callbackUrl,
redirect: false,
});

if (!signInResponse) {
setError("root", { message: "An unknown error occurred" });
setIsLoading(false);
return;
}

if (signInResponse.error) {
setError("root", { message: signInResponse.error });
setIsLoading(false);
return;
}

router.push(callbackUrl);
};

return (
Expand Down
41 changes: 28 additions & 13 deletions src/components/VerifyEmail/VerifyEmailSuccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import { Snackbar, Typography } from "@mui/material";
import { useRouter } from "next/navigation";
import { signOut } from "next-auth/react";
import { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { useState } from "react";

import { verifyEmailWithToken } from "@/server/api/users/public-mutations";
import { EmailVerificationToken } from "@/types";
Expand All @@ -18,19 +18,34 @@ export default function VerifyEmailSuccess({
}: VerifyEmailSuccessProps) {
const [snackbarOpen, setSnackbarOpen] = useState(false);
const router = useRouter();
const { data: session, update } = useSession();

useEffect(() => {
const verifyEmail = async () => {
await verifyEmailWithToken(emailVerificationToken.token);
setSnackbarOpen(true);
setTimeout(async () => {
await signOut({ redirect: false, callbackUrl: "/" });
router.push("/");
}, 1000);
};
const verifyEmail = async () => {
if (!session || session.user.isEmailVerified) {
return;
}

await verifyEmailWithToken(emailVerificationToken.token);
setSnackbarOpen(true);

// Update session to reflect database changes
await update({
...session,
user: {
...session?.user,
isEmailVerified: true,
},
});

setTimeout(() => {
router.push("/");
}, 2000);
};

// Call verifyEmail directly when session becomes available
if (session) {
void verifyEmail();
}, []);
}

return (
<>
Expand All @@ -40,7 +55,7 @@ export default function VerifyEmailSuccess({
onClose={() => setSnackbarOpen(false)}
message="Email verified"
/>
<Typography variant="body1">Verifying email...</Typography>;
<Typography variant="body1">Verifying email...</Typography>
</>
);
}

0 comments on commit f8d5c6c

Please sign in to comment.