diff --git a/packages/better-auth/src/api/routes/email-verification.ts b/packages/better-auth/src/api/routes/email-verification.ts index 615d9b10e4..eb38f246c5 100644 --- a/packages/better-auth/src/api/routes/email-verification.ts +++ b/packages/better-auth/src/api/routes/email-verification.ts @@ -309,152 +309,165 @@ export const verifyEmail = createAuthEndpoint( return redirectOnError(BASE_ERROR_CODES.USER_NOT_FOUND); } if (parsed.updateTo) { - let session = await getSessionFromCtx(ctx); + const session = await getSessionFromCtx(ctx); if (session && session.user.email !== parsed.email) { return redirectOnError(BASE_ERROR_CODES.INVALID_USER); } - if (parsed.requestType === "change-email-confirmation") { - const newToken = await createEmailVerificationToken( - ctx.context.secret, - parsed.email, - parsed.updateTo, - ctx.context.options.emailVerification?.expiresIn, - { - requestType: "change-email-verification", - }, - ); - const updateCallbackURL = ctx.query.callbackURL - ? encodeURIComponent(ctx.query.callbackURL) - : encodeURIComponent("/"); - const url = `${ctx.context.baseURL}/verify-email?token=${newToken}&callbackURL=${updateCallbackURL}`; - if (ctx.context.options.emailVerification?.sendVerificationEmail) { - await ctx.context.runInBackgroundOrAwait( - ctx.context.options.emailVerification.sendVerificationEmail( - { - user: { - ...user.user, - email: parsed.updateTo, + switch (parsed.requestType) { + /** + * User clicks confirmation -> sends verification to new email + */ + case "change-email-confirmation": { + const newToken = await createEmailVerificationToken( + ctx.context.secret, + parsed.email, + parsed.updateTo, + ctx.context.options.emailVerification?.expiresIn, + { requestType: "change-email-verification" }, + ); + const updateCallbackURL = ctx.query.callbackURL + ? encodeURIComponent(ctx.query.callbackURL) + : encodeURIComponent("/"); + const url = `${ctx.context.baseURL}/verify-email?token=${newToken}&callbackURL=${updateCallbackURL}`; + if (ctx.context.options.emailVerification?.sendVerificationEmail) { + await ctx.context.runInBackgroundOrAwait( + ctx.context.options.emailVerification.sendVerificationEmail( + { + user: { ...user.user, email: parsed.updateTo }, + url, + token: newToken, }, - url, - token: newToken, - }, + ctx.request, + ), + ); + } + if (ctx.query.callbackURL) { + throw ctx.redirect(ctx.query.callbackURL); + } + return ctx.json({ status: true }); + } + /** + * User clicks verification -> updates email + */ + case "change-email-verification": { + let activeSession = session; + if (!activeSession) { + const newSession = await ctx.context.internalAdapter.createSession( + user.user.id, + ); + if (!newSession) { + throw APIError.from( + "INTERNAL_SERVER_ERROR", + BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION, + ); + } + activeSession = { + session: newSession, + user: user.user, + }; + } + if (ctx.context.options.emailVerification?.onEmailVerification) { + await ctx.context.options.emailVerification.onEmailVerification( + user.user, ctx.request, - ), - ); - } - if (ctx.query.callbackURL) { - throw ctx.redirect(ctx.query.callbackURL); - } - return ctx.json({ - status: true, - }); - } - if (!session) { - const newSession = await ctx.context.internalAdapter.createSession( - user.user.id, - ); - if (!newSession) { - throw APIError.from( - "INTERNAL_SERVER_ERROR", - BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION, - ); - } - session = { - session: newSession, - user: user.user, - }; - } - if (parsed.requestType === "change-email-verification") { - if (ctx.context.options.emailVerification?.onEmailVerification) { - await ctx.context.options.emailVerification.onEmailVerification( - user.user, - ctx.request, - ); - } - const updatedUser = await ctx.context.internalAdapter.updateUserByEmail( - parsed.email, - { - email: parsed.updateTo, - emailVerified: true, - }, - ); - if (ctx.context.options.emailVerification?.afterEmailVerification) { - await ctx.context.options.emailVerification.afterEmailVerification( - updatedUser, - ctx.request, - ); - } - await setSessionCookie(ctx, { - session: session.session, - user: { - ...session.user, - email: parsed.updateTo, - emailVerified: true, - }, - }); - if (ctx.query.callbackURL) { - throw ctx.redirect(ctx.query.callbackURL); - } - return ctx.json({ - status: true, - user: updatedUser, - }); - } - - const updatedUser = await ctx.context.internalAdapter.updateUserByEmail( - parsed.email, - { - email: parsed.updateTo, - emailVerified: false, - }, - ); - - const newToken = await createEmailVerificationToken( - ctx.context.secret, - parsed.updateTo, - ); - - //send verification email to the new email - const updateCallbackURL = ctx.query.callbackURL - ? encodeURIComponent(ctx.query.callbackURL) - : encodeURIComponent("/"); - if (ctx.context.options.emailVerification?.sendVerificationEmail) { - await ctx.context.runInBackgroundOrAwait( - ctx.context.options.emailVerification.sendVerificationEmail( - { - user: updatedUser, - url: `${ctx.context.baseURL}/verify-email?token=${newToken}&callbackURL=${updateCallbackURL}`, - token: newToken, + ); + } + const updatedUser = + await ctx.context.internalAdapter.updateUserByEmail(parsed.email, { + email: parsed.updateTo, + emailVerified: true, + }); + if (ctx.context.options.emailVerification?.afterEmailVerification) { + await ctx.context.options.emailVerification.afterEmailVerification( + updatedUser, + ctx.request, + ); + } + await setSessionCookie(ctx, { + session: activeSession.session, + user: { + ...activeSession.user, + email: parsed.updateTo, + emailVerified: true, }, - ctx.request, - ), - ); + }); + if (ctx.query.callbackURL) { + throw ctx.redirect(ctx.query.callbackURL); + } + return ctx.json({ status: true, user: updatedUser }); + } + /** + * Legacy flow + * + * - skips two-step verification + * - updates email immediately + */ + default: { + let activeSession = session; + if (!activeSession) { + const newSession = await ctx.context.internalAdapter.createSession( + user.user.id, + ); + if (!newSession) { + throw APIError.from( + "INTERNAL_SERVER_ERROR", + BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION, + ); + } + activeSession = { + session: newSession, + user: user.user, + }; + } + const updatedUser = + await ctx.context.internalAdapter.updateUserByEmail(parsed.email, { + email: parsed.updateTo, + emailVerified: false, + }); + const newToken = await createEmailVerificationToken( + ctx.context.secret, + parsed.updateTo, + ); + const updateCallbackURL = ctx.query.callbackURL + ? encodeURIComponent(ctx.query.callbackURL) + : encodeURIComponent("/"); + if (ctx.context.options.emailVerification?.sendVerificationEmail) { + await ctx.context.runInBackgroundOrAwait( + ctx.context.options.emailVerification.sendVerificationEmail( + { + user: updatedUser, + url: `${ctx.context.baseURL}/verify-email?token=${newToken}&callbackURL=${updateCallbackURL}`, + token: newToken, + }, + ctx.request, + ), + ); + } + await setSessionCookie(ctx, { + session: activeSession.session, + user: { + ...activeSession.user, + email: parsed.updateTo, + emailVerified: false, + }, + }); + if (ctx.query.callbackURL) { + throw ctx.redirect(ctx.query.callbackURL); + } + return ctx.json({ + status: true, + user: { + id: updatedUser.id, + email: updatedUser.email, + name: updatedUser.name, + image: updatedUser.image, + emailVerified: updatedUser.emailVerified, + createdAt: updatedUser.createdAt, + updatedAt: updatedUser.updatedAt, + }, + }); + } } - - await setSessionCookie(ctx, { - session: session.session, - user: { - ...session.user, - email: parsed.updateTo, - emailVerified: false, - }, - }); - - if (ctx.query.callbackURL) { - throw ctx.redirect(ctx.query.callbackURL); - } - return ctx.json({ - status: true, - user: { - id: updatedUser.id, - email: updatedUser.email, - name: updatedUser.name, - image: updatedUser.image, - emailVerified: updatedUser.emailVerified, - createdAt: updatedUser.createdAt, - updatedAt: updatedUser.updatedAt, - }, - }); } if (user.user.emailVerified) { if (ctx.query.callbackURL) {