Skip to content

Commit b2c2101

Browse files
authored
Fix missing verifier from password reset flow (#781)
1 parent 9309420 commit b2c2101

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed

packages/auth-core/src/core.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,16 @@ export class Auth {
145145
}
146146

147147
async sendPasswordResetEmail(email: string, resetUrl: string) {
148-
return this._post<{ email_sent: string }>("send-reset-email", {
149-
provider: emailPasswordProviderName,
150-
email,
151-
reset_url: resetUrl,
152-
});
148+
const { challenge, verifier } = pkce.createVerifierChallengePair();
149+
return {
150+
verifier,
151+
...(await this._post<{ email_sent: string }>("send-reset-email", {
152+
provider: emailPasswordProviderName,
153+
challenge,
154+
email,
155+
reset_url: resetUrl,
156+
})),
157+
};
153158
}
154159

155160
static checkPasswordResetTokenValid(resetToken: string) {
@@ -169,19 +174,24 @@ export class Auth {
169174
}
170175
}
171176

172-
async resetPasswordWithResetToken(resetToken: string, password: string) {
173-
return this._post<TokenData>("reset-password", {
177+
async resetPasswordWithResetToken(
178+
resetToken: string,
179+
verifier: string,
180+
password: string
181+
) {
182+
const { code } = await this._post<{ code: string }>("reset-password", {
174183
provider: emailPasswordProviderName,
175184
reset_token: resetToken,
176185
password,
177186
});
187+
return this.getToken(code, verifier);
178188
}
179189

180190
async getProvidersInfo() {
181191
// TODO: cache this data when we have a way to invalidate on config update
182192
try {
183193
return await this.client.queryRequiredSingle<{
184-
oauth: { name: string; display_name: string }[];
194+
oauth: { name: BuiltinOAuthProviderNames; display_name: string }[];
185195
emailPassword: boolean;
186196
}>(`
187197
with

packages/auth-nextjs/src/app/index.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,15 @@ export class NextAppAuth extends NextAuth {
358358
["email"],
359359
"email missing from request body"
360360
);
361-
(await this.core).sendPasswordResetEmail(
362-
email,
363-
this.options.passwordResetUrl
364-
);
361+
const { verifier } = await (
362+
await this.core
363+
).sendPasswordResetEmail(email, this.options.passwordResetUrl);
364+
cookies().set({
365+
name: this.options.pkceVerifierCookieName,
366+
value: verifier,
367+
httpOnly: true,
368+
sameSite: "strict",
369+
});
365370
return new Response(null, { status: 204 });
366371
}
367372
case "emailpassword/reset-password": {
@@ -372,6 +377,14 @@ export class NextAppAuth extends NextAuth {
372377
}
373378
let tokenData: TokenData;
374379
try {
380+
const verifier = req.cookies.get(
381+
this.options.pkceVerifierCookieName
382+
)?.value;
383+
if (!verifier) {
384+
return onEmailPasswordReset({
385+
error: new Error("no pkce verifier cookie found"),
386+
});
387+
}
375388
const [resetToken, password] = _extractParams(
376389
await _getReqBody(req),
377390
["reset_token", "password"],
@@ -380,7 +393,7 @@ export class NextAppAuth extends NextAuth {
380393

381394
tokenData = await (
382395
await this.core
383-
).resetPasswordWithResetToken(resetToken, password);
396+
).resetPasswordWithResetToken(resetToken, verifier, password);
384397
} catch (err) {
385398
return onEmailPasswordReset({
386399
error: err instanceof Error ? err : new Error(String(err)),
@@ -392,6 +405,7 @@ export class NextAppAuth extends NextAuth {
392405
httpOnly: true,
393406
sameSite: "strict",
394407
});
408+
cookies().delete(this.options.pkceVerifierCookieName);
395409
return onEmailPasswordReset({ error: null, tokenData });
396410
}
397411
case "emailpassword/resend-verification-email": {
@@ -476,30 +490,43 @@ export class NextAppAuth extends NextAuth {
476490
throw new Error(`'passwordResetUrl' option not configured`);
477491
}
478492
const [email] = _extractParams(data, ["email"], "email missing");
479-
await (
493+
const { verifier } = await (
480494
await this.core
481495
).sendPasswordResetEmail(
482496
email,
483497
`${this.options.baseUrl}/${this.options.passwordResetUrl}`
484498
);
499+
cookies().set({
500+
name: this.options.pkceVerifierCookieName,
501+
value: verifier,
502+
httpOnly: true,
503+
sameSite: "strict",
504+
});
485505
},
486506
emailPasswordResetPassword: async (
487507
data: FormData | { resetToken: string; password: string }
488508
) => {
509+
const verifier = cookies().get(
510+
this.options.pkceVerifierCookieName
511+
)?.value;
512+
if (!verifier) {
513+
throw new Error("no pkce verifier cookie found");
514+
}
489515
const [resetToken, password] = _extractParams(
490516
data,
491517
["reset_token", "password"],
492518
"reset_token or password missing"
493519
);
494520
const tokenData = await (
495521
await this.core
496-
).resetPasswordWithResetToken(resetToken, password);
522+
).resetPasswordWithResetToken(resetToken, verifier, password);
497523
cookies().set({
498524
name: this.options.authCookieName,
499525
value: tokenData.auth_token,
500526
httpOnly: true,
501527
sameSite: "strict",
502528
});
529+
cookies().delete(this.options.pkceVerifierCookieName);
503530
return tokenData;
504531
},
505532
emailPasswordResendVerificationEmail: async (

0 commit comments

Comments
 (0)