Dorji Tshering
Dorji Tshering5mo ago

Password reset not working.

I have set up a password reset flow just like in the doc with resend api. Below is my resend config:
import Resend from "@auth/core/providers/resend";
import { alphabet, generateRandomString } from "oslo/crypto";
import { Resend as ResendAPI } from "resend";

export const ResendOTPPasswordReset = Resend({
id: "resend-otp",
apiKey: process.env.AUTH_RESEND_KEY,
async generateVerificationToken() {
return generateRandomString(8, alphabet("0-9"));
},
async sendVerificationRequest({ identifier: email, provider, token }) {
const resend = new ResendAPI(provider.apiKey);
const { error } = await resend.emails.send({
from: "Lhakhang Service <onboarding@resend.dev>",
to: [email],
subject: `Reset your password in Lhakhang Service`,
text: "Your password reset code is " + token,
});

if (error) {
throw new Error("Could not send");
}
},
});
import Resend from "@auth/core/providers/resend";
import { alphabet, generateRandomString } from "oslo/crypto";
import { Resend as ResendAPI } from "resend";

export const ResendOTPPasswordReset = Resend({
id: "resend-otp",
apiKey: process.env.AUTH_RESEND_KEY,
async generateVerificationToken() {
return generateRandomString(8, alphabet("0-9"));
},
async sendVerificationRequest({ identifier: email, provider, token }) {
const resend = new ResendAPI(provider.apiKey);
const { error } = await resend.emails.send({
from: "Lhakhang Service <onboarding@resend.dev>",
to: [email],
subject: `Reset your password in Lhakhang Service`,
text: "Your password reset code is " + token,
});

if (error) {
throw new Error("Could not send");
}
},
});
.
9 Replies
Dorji Tshering
Dorji TsheringOP5mo ago
Below is my auth.ts file.
import { Password } from "@convex-dev/auth/providers/Password";
import { convexAuth } from "@convex-dev/auth/server";
import { GenericMutationCtx } from "convex/server";

import { DataModel } from "./_generated/dataModel";
import { ResendOTPPasswordReset } from "./services/passwordReset";

export const { auth, signIn, signOut, store } = convexAuth({
providers: [Password({ reset: ResendOTPPasswordReset })],
callbacks: {
async createOrUpdateUser(ctx: GenericMutationCtx<DataModel>, args) {
if (args.existingUserId) {
return args.existingUserId;
}
const existingUser = await ctx.db
.query("users")
.withIndex("email", (q) => q.eq("email", args.profile.email))
.first();

if (existingUser) {
return existingUser._id;
}
return ctx.db.insert("users", {
email: args.profile.email,
});
},
},
});
import { Password } from "@convex-dev/auth/providers/Password";
import { convexAuth } from "@convex-dev/auth/server";
import { GenericMutationCtx } from "convex/server";

import { DataModel } from "./_generated/dataModel";
import { ResendOTPPasswordReset } from "./services/passwordReset";

export const { auth, signIn, signOut, store } = convexAuth({
providers: [Password({ reset: ResendOTPPasswordReset })],
callbacks: {
async createOrUpdateUser(ctx: GenericMutationCtx<DataModel>, args) {
if (args.existingUserId) {
return args.existingUserId;
}
const existingUser = await ctx.db
.query("users")
.withIndex("email", (q) => q.eq("email", args.profile.email))
.first();

if (existingUser) {
return existingUser._id;
}
return ctx.db.insert("users", {
email: args.profile.email,
});
},
},
});
And finally my client form handler:
const sendResetPasswordEmail = async (
formValues: z.infer<typeof forgotPasswordSchema>,
) => {
await signIn("password", {
flow: "reset",
email: formValues.email,
})
.then(() =>
toast({
title: "Success",
description: `An email has been sent to ${formValues.email} with a password reset link`,
}),
)
.catch(() =>
toast({
title: "Failure",
variant: "destructive",
description:
"Failed to send password reset link. Make sure account is associated with the given email",
}),
);
};
const sendResetPasswordEmail = async (
formValues: z.infer<typeof forgotPasswordSchema>,
) => {
await signIn("password", {
flow: "reset",
email: formValues.email,
})
.then(() =>
toast({
title: "Success",
description: `An email has been sent to ${formValues.email} with a password reset link`,
}),
)
.catch(() =>
toast({
title: "Failure",
variant: "destructive",
description:
"Failed to send password reset link. Make sure account is associated with the given email",
}),
);
};
. Initially there was an error from resend that the test emails must be the email associated with the resend account. Tried that email but got the error Uncaught Error: InvalidAccountId at retrieveAccount. Then i created an account with that email and i can see that user both on users table and it's id associated with an account under authAccounts table. Now when i try again with that email, I am getting the error Uncaught Error: InvalidAccountId at retrieveAccount. What could be the reason?
Michal Srb
Michal Srb5mo ago
Error: InvalidAccountId at retrieveAccount would suggest that the email doesn't exist for the provider. Do you have in the authAccounts table a document with providerAccountId being the email and provider being "password"?
Dorji Tshering
Dorji TsheringOP5mo ago
@Michal Srb Yes Michal, I can see the document there with povider as password and the email as the value for providerAccountId field
Michal Srb
Michal Srb5mo ago
1. Check your client is correct: formValues.email is actually the email string 2. Try again just to confirm (I have verified this flow works fine in our demos)
Dorji Tshering
Dorji TsheringOP5mo ago
Yup the value is coming correct. Could you give me a link to that demo, so i can try it locally? I think that will help in narrow down the issue.
Michal Srb
Michal Srb5mo ago
It's hosted here and there's a link to GitHub: https://labs.convex.dev/auth-example If you put together a repo with instructions to reproduce I can also have a look at your code
Dorji Tshering
Dorji TsheringOP5mo ago
@Michal Srb Sure will do that and let you know. Hi @Michal Srb, here is the link to minimal reproduction of the issue. https://github.com/dorji-dev/convex-auth. Funnily enough, if i manually pass the email that has account, like this in the resend config, it works.
export const ResendOTPPasswordReset = Resend({
id: "resend-otp",
apiKey: process.env.AUTH_RESEND_KEY,
async generateVerificationToken() {
return generateRandomString(8, alphabet("0-9"));
},
async sendVerificationRequest({ identifier: email, provider, token }) {
const resend = new ResendAPI(provider.apiKey);
const { error } = await resend.emails.send({
from: "Lhakhang Service <kk@resend.dev>",
to: ["dorji.codes@gmail.com"],
subject: `Reset your password in Lhakhang Service`,
text: "Your password reset code is " + token,
});

if (error) {
throw new Error("Could not send");
}
},
});
export const ResendOTPPasswordReset = Resend({
id: "resend-otp",
apiKey: process.env.AUTH_RESEND_KEY,
async generateVerificationToken() {
return generateRandomString(8, alphabet("0-9"));
},
async sendVerificationRequest({ identifier: email, provider, token }) {
const resend = new ResendAPI(provider.apiKey);
const { error } = await resend.emails.send({
from: "Lhakhang Service <kk@resend.dev>",
to: ["dorji.codes@gmail.com"],
subject: `Reset your password in Lhakhang Service`,
text: "Your password reset code is " + token,
});

if (error) {
throw new Error("Could not send");
}
},
});
. I can confirm that the incoming email is coming as string after logging. Let me know if you have any other related questions.
Michal Srb
Michal Srb5mo ago
I cannot reproduce the issue. I added a sign up form:
<form
onSubmit={async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
await signIn("password", {
flow: "signUp",
email: formData.get("email") as string,
password: formData.get("password") as string,
});
}}
className="flex flex-col space-y-[10px]"
>
<input
type="email"
name="email"
placeholder="Enter email"
className="p-[5px]"
/>
<input
type="password"
name="password"
placeholder="Enter passwrod"
className="p-[5px]"
/>
<button type="submit">Send reset link</button>
</form>
<form
onSubmit={async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
await signIn("password", {
flow: "signUp",
email: formData.get("email") as string,
password: formData.get("password") as string,
});
}}
className="flex flex-col space-y-[10px]"
>
<input
type="email"
name="email"
placeholder="Enter email"
className="p-[5px]"
/>
<input
type="password"
name="password"
placeholder="Enter passwrod"
className="p-[5px]"
/>
<button type="submit">Send reset link</button>
</form>
used it, and used the reset form, and it worked. Try clearing all your tables and retry.
Dorji Tshering
Dorji TsheringOP5mo ago
@Michal Srb Tried right now again and it's working as expected. May be it just needed a server restart or something. Anyway thanks for your time!! Appreciated.

Did you find this page helpful?