Skip to content

Commit

Permalink
Fix missing verifier from password reset flow
Browse files Browse the repository at this point in the history
  • Loading branch information
scotttrinh committed Nov 14, 2023
1 parent 71c8b88 commit d1c7b8a
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 15 deletions.
26 changes: 18 additions & 8 deletions packages/auth-core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,16 @@ export class Auth {
}

async sendPasswordResetEmail(email: string, resetUrl: string) {
return this._post<{ email_sent: string }>("send-reset-email", {
provider: emailPasswordProviderName,
email,
reset_url: resetUrl,
});
const { challenge, verifier } = pkce.createVerifierChallengePair();
return {
verifier,
...(await this._post<{ email_sent: string }>("send-reset-email", {
provider: emailPasswordProviderName,
challenge,
email,
reset_url: resetUrl,
})),
};
}

static checkPasswordResetTokenValid(resetToken: string) {
Expand All @@ -165,19 +170,24 @@ export class Auth {
}
}

async resetPasswordWithResetToken(resetToken: string, password: string) {
return this._post<TokenData>("reset-password", {
async resetPasswordWithResetToken(
resetToken: string,
verifier: string,
password: string
) {
const { code } = await this._post<{ code: string }>("reset-password", {
provider: emailPasswordProviderName,
reset_token: resetToken,
password,
});
return this.getToken(code, verifier);
}

async getProvidersInfo() {
// TODO: cache this data when we have a way to invalidate on config update
try {
return await this.client.queryRequiredSingle<{
oauth: { name: string; display_name: string }[];
oauth: { name: BuiltinOAuthProviderNames; display_name: string }[];
emailPassword: boolean;
}>(`
with
Expand Down
41 changes: 34 additions & 7 deletions packages/auth-nextjs/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,15 @@ export class NextAppAuth extends NextAuth {
["email"],
"email missing from request body"
);
(await this.core).sendPasswordResetEmail(
email,
this.options.passwordResetUrl
);
const { verifier } = await (
await this.core
).sendPasswordResetEmail(email, this.options.passwordResetUrl);
cookies().set({
name: this.options.pkceVerifierCookieName,
value: verifier,
httpOnly: true,
sameSite: "strict",
});
return new Response(null, { status: 204 });
}
case "emailpassword/reset-password": {
Expand All @@ -368,6 +373,14 @@ export class NextAppAuth extends NextAuth {
}
let tokenData: TokenData;
try {
const verifier = req.cookies.get(
this.options.pkceVerifierCookieName
)?.value;
if (!verifier) {
return onEmailPasswordReset({
error: new Error("no pkce verifier cookie found"),
});
}
const [resetToken, password] = _extractParams(
await _getReqBody(req),
["reset_token", "password"],
Expand All @@ -376,7 +389,7 @@ export class NextAppAuth extends NextAuth {

tokenData = await (
await this.core
).resetPasswordWithResetToken(resetToken, password);
).resetPasswordWithResetToken(resetToken, verifier, password);
} catch (err) {
return onEmailPasswordReset({
error: err instanceof Error ? err : new Error(String(err)),
Expand All @@ -388,6 +401,7 @@ export class NextAppAuth extends NextAuth {
httpOnly: true,
sameSite: "strict",
});
cookies().delete(this.options.pkceVerifierCookieName);
return onEmailPasswordReset({ error: null, tokenData });
}
case "emailpassword/resend-verification-email": {
Expand Down Expand Up @@ -472,30 +486,43 @@ export class NextAppAuth extends NextAuth {
throw new Error(`'passwordResetUrl' option not configured`);
}
const [email] = _extractParams(data, ["email"], "email missing");
await (
const { verifier } = await (
await this.core
).sendPasswordResetEmail(
email,
`${this.options.baseUrl}/${this.options.passwordResetUrl}`
);
cookies().set({
name: this.options.pkceVerifierCookieName,
value: verifier,
httpOnly: true,
sameSite: "strict",
});
},
emailPasswordResetPassword: async (
data: FormData | { resetToken: string; password: string }
) => {
const verifier = cookies().get(
this.options.pkceVerifierCookieName
)?.value;
if (!verifier) {
throw new Error("no pkce verifier cookie found");
}
const [resetToken, password] = _extractParams(
data,
["reset_token", "password"],
"reset_token or password missing"
);
const tokenData = await (
await this.core
).resetPasswordWithResetToken(resetToken, password);
).resetPasswordWithResetToken(resetToken, verifier, password);
cookies().set({
name: this.options.authCookieName,
value: tokenData.auth_token,
httpOnly: true,
sameSite: "strict",
});
cookies().delete(this.options.pkceVerifierCookieName);
return tokenData;
},
emailPasswordResendVerificationEmail: async (
Expand Down

0 comments on commit d1c7b8a

Please sign in to comment.