re-send email verification endpoint

Hi guys, what would be the best way to achieve 'send verification email' in t3? I couldn't get my head around exposing 'resendEmailVerification' endpoint to client using trpc. I want users to be able to receive Verification Email inside my profile page.
14 Replies
Maj
Maj17mo ago
just make a protected procedure? im not sure what do you think about the last sentence. The best way it would be to dynamically generate a random string or a code whatever and then u send the link with the query string through email. When the user clicks on the link it will redirect to https://website.com/emailVerification?key=whateverstring u can check for the string in the database and u can make it expire in some time What would be the point of the user receiving the verification email inside their profile page. its useless then if you are asking the to confirm their email u should require them to confirm it though the email and the link u send them.
dearmannerism
dearmannerism17mo ago
Sorry for confusion. Im using next-auth for sign in and sign out. How can I possible expose next auth’s sendVerification() to my client with a protectedProcedure??
JulieCezar
JulieCezar17mo ago
You still have to somehow send that email through next-auth right? Just expose a trpc endpoint which will call that same logic for sending the email
Maj
Maj17mo ago
if u configure nextauth with trpc u will be able to call protected procedures only if the client is logged in u can probably make a protected procedure with mutation but once the user is verified just disable the tab or button so the user cant do it again
dearmannerism
dearmannerism17mo ago
This is exactly what I want to do How can I do that?? It seems like that very logic of sending email verification using next-auth is hidden inside its ‘signIn(“email”, {email})’ function.
resendVerificationEmail: protectedProcedure
.input(z.object({ email: z.string().email() }))
.mutation(async ({ ctx, input: { email } }) => {
const verificationToken = await ctx.prisma.verificationToken.create({
data: {
identifier: email,
token: crypto.randomBytes(32).toString("hex"),
expires: new Date(Date.now() + 1000 * 60 * 60 * 24), // expires in 24 hours
},
});

const url = `http://localhost:3000/api/auth/callback/email?callbackUrl=${encodeURIComponent(
"http://localhost:3000/profile"
)}&token=${verificationToken.token}&email=${encodeURIComponent(
verificationToken.identifier
)}`;

const server = {
host: env.EMAIL_SERVER_HOST,
port: parseInt(env.EMAIL_SERVER_PORT),
auth: {
user: env.EMAIL_SERVER_USER,
pass: env.EMAIL_SERVER_PASSWORD,
},
};
const { host } = new URL(url);
const transport = createTransport(server);
try {
const result = await transport.sendMail({
to: email,
from: env.EMAIL_FROM,
subject: `Sign in to ${host}`,
text: text({ url, host }),
html: html({ url, host }),
});

const failed = result.rejected.concat(result.pending).filter(Boolean);
if (failed.length) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Email(s) (${failed.join(", ")}) could not be sent`,
});
}
return { message: "Verification email sent." };
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to send verification email.",
});
}
}),
resendVerificationEmail: protectedProcedure
.input(z.object({ email: z.string().email() }))
.mutation(async ({ ctx, input: { email } }) => {
const verificationToken = await ctx.prisma.verificationToken.create({
data: {
identifier: email,
token: crypto.randomBytes(32).toString("hex"),
expires: new Date(Date.now() + 1000 * 60 * 60 * 24), // expires in 24 hours
},
});

const url = `http://localhost:3000/api/auth/callback/email?callbackUrl=${encodeURIComponent(
"http://localhost:3000/profile"
)}&token=${verificationToken.token}&email=${encodeURIComponent(
verificationToken.identifier
)}`;

const server = {
host: env.EMAIL_SERVER_HOST,
port: parseInt(env.EMAIL_SERVER_PORT),
auth: {
user: env.EMAIL_SERVER_USER,
pass: env.EMAIL_SERVER_PASSWORD,
},
};
const { host } = new URL(url);
const transport = createTransport(server);
try {
const result = await transport.sendMail({
to: email,
from: env.EMAIL_FROM,
subject: `Sign in to ${host}`,
text: text({ url, host }),
html: html({ url, host }),
});

const failed = result.rejected.concat(result.pending).filter(Boolean);
if (failed.length) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Email(s) (${failed.join(", ")}) could not be sent`,
});
}
return { message: "Verification email sent." };
} catch (error) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Failed to send verification email.",
});
}
}),
this is what I got so far. I was able to send custom email, but once I click on the redirect URL I get redirected to this error page
dearmannerism
dearmannerism17mo ago
dearmannerism
dearmannerism17mo ago
This is my overall question which I asked in next-auth community as well. https://github.com/nextauthjs/next-auth/discussions/7860
GitHub
What does actually happen between when user clicking on email `redi...
I am creating a custom endpoint for resendEmailVerification that can be called in a client side. 1. This is my code on customresendEmailVerification endpoint: { const verificationTokenData = await ...
Maj
Maj17mo ago
i dont know man but iwould use a custom endpoint outside the nextauth that takes in the token and on render it should check it in db. an endpoint with a dynamic route
Lopen
Lopen17mo ago
Try jwt
JulieCezar
JulieCezar17mo ago
Is this from next auth or did you make this custom by yourself? If it's the latter here is how I did it. - When the user registers I create a new account in db and have a field e.g. status which can be true/false and means if the user activated their account - Whenever the user logs in I check if they activated their account, if not I redirect them to my custom page where they have a resend email button - If they click that button you activate make a call to that trpc endpoint to regenerate that email (normal trpc mutation) - When they actually click on that link you redirect them to an endpoint (idk if this is nextauth or your own) where you set that new state in db - now they activated the account and can log in normally
dearmannerism
dearmannerism17mo ago
Once the user clicks on the link it is processed by next auth
Maj
Maj17mo ago
maybe use clerk
JulieCezar
JulieCezar17mo ago
You can override which pages are opened when something happens, meaning you can create a custom page that will show all this logic instead of nextauth... Also, I've looked ar your issue... you showed the endpoint for sending emails... but how does decrypting look like? Or does all of that get handled by next auth?
dearmannerism
dearmannerism17mo ago
All of it is handled by next auth. So I should create a custom page instead huh
Want results from more Discord servers?
Add your server