From 2bb7d7cdf92071db55b6361a12dcb9412111e17d Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:29:44 -0500 Subject: [PATCH 1/3] Fix infinite redirect loop in dev when resetting db --- apps/web/app/(landing)/logout/page.tsx | 18 ++++++++++++++++++ .../app/(landing)/welcome-redirect/page.tsx | 3 ++- version.txt | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 apps/web/app/(landing)/logout/page.tsx diff --git a/apps/web/app/(landing)/logout/page.tsx b/apps/web/app/(landing)/logout/page.tsx new file mode 100644 index 0000000000..a61e45add1 --- /dev/null +++ b/apps/web/app/(landing)/logout/page.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { useEffect } from "react"; +import { logOut } from "@/utils/user"; +import { Loading } from "@/components/Loading"; +import { BasicLayout } from "@/components/layouts/BasicLayout"; + +export default function LogoutPage() { + useEffect(() => { + logOut("/login"); + }, []); + + return ( + + + + ); +} diff --git a/apps/web/app/(landing)/welcome-redirect/page.tsx b/apps/web/app/(landing)/welcome-redirect/page.tsx index dfa4429796..0a9914ad84 100644 --- a/apps/web/app/(landing)/welcome-redirect/page.tsx +++ b/apps/web/app/(landing)/welcome-redirect/page.tsx @@ -15,7 +15,8 @@ export default async function WelcomeRedirectPage(props: { select: { completedOnboardingAt: true, utms: true }, }); - if (!user) redirect("/login"); + // Session exists but user doesn't - invalid state, log out + if (!user) redirect("/logout"); if (searchParams.force) redirect("/onboarding"); if (user.completedOnboardingAt) redirect("/setup"); redirect("/onboarding"); diff --git a/version.txt b/version.txt index 0aa64700d6..354aaded94 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.21.52 +v2.21.53 From c64d3789f8fcbcc122117fd378870c341ee1cb17 Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:02:30 -0500 Subject: [PATCH 2/3] Fix post sign up handling --- apps/web/utils/auth.ts | 198 +++++++++++++++++++++++------------------ version.txt | 2 +- 2 files changed, 111 insertions(+), 89 deletions(-) diff --git a/apps/web/utils/auth.ts b/apps/web/utils/auth.ts index 2fd256c9e7..b1e8c8096a 100644 --- a/apps/web/utils/auth.ts +++ b/apps/web/utils/auth.ts @@ -3,9 +3,10 @@ import { sso } from "@better-auth/sso"; import { createContact as createLoopsContact } from "@inboxzero/loops"; import { createContact as createResendContact } from "@inboxzero/resend"; -import type { Account, AuthContext, User } from "better-auth"; +import type { Account, AuthContext } from "better-auth"; import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; +import { createAuthMiddleware } from "better-auth/api"; import { nextCookies } from "better-auth/next-js"; import { cookies, headers } from "next/headers"; import { env } from "@/env"; @@ -110,9 +111,6 @@ export const betterAuthConfig = betterAuth({ disableIdTokenSignIn: true, }, }, - events: { - signIn: handleSignIn, - }, databaseHooks: { account: { create: { @@ -134,108 +132,132 @@ export const betterAuthConfig = betterAuth({ }, errorURL: "/login/error", }, + hooks: { + after: createAuthMiddleware(async (ctx) => { + // Handle sign-up: call postSignUp when a new user is created + if (ctx.path.startsWith("/sign-up")) { + const newSession = ctx.context.newSession; + if (newSession) { + await postSignUp({ + id: newSession.user.id, + email: newSession.user.email, + name: newSession.user.name, + image: newSession.user.image, + }); + } + } + }), + }, }); -async function handleSignIn({ - user, - isNewUser, +async function postSignUp({ + id: userId, + email, + name, + image, }: { - user: User; - isNewUser: boolean; + id: string; + email: string; + name?: string | null; + image?: string | null; }) { - if (isNewUser && user.email) { - const loops = async () => { - const account = await prisma.account - .findFirst({ - where: { userId: user.id }, - select: { provider: true }, - }) - .catch((error) => { - logger.error("Error finding account", { - userId: user.id, - error, - }); - captureException(error, undefined, user.email); + const loops = async () => { + const account = await prisma.account + .findFirst({ + where: { userId }, + select: { provider: true }, + }) + .catch((error) => { + logger.error("Error finding account", { + userId, + error, }); - - await createLoopsContact( - user.email, - user.name?.split(" ")?.[0], - account?.provider, - ).catch((error) => { - const alreadyExists = - error instanceof Error && error.message.includes("409"); - if (!alreadyExists) { - logger.error("Error creating Loops contact", { - email: user.email, - error, - }); - captureException(error, undefined, user.email); - } + captureException(error, undefined, email); }); - }; - const resend = createResendContact({ email: user.email }).catch((error) => { - logger.error("Error creating Resend contact", { - email: user.email, - error, - }); - captureException(error, undefined, user.email); + await createLoopsContact( + email, + name?.split(" ")?.[0], + account?.provider, + ).catch((error) => { + const alreadyExists = + error instanceof Error && error.message.includes("409"); + if (!alreadyExists) { + logger.error("Error creating Loops contact", { + email, + error, + }); + captureException(error, undefined, email); + } }); + }; - const dub = trackDubSignUp(user).catch((error) => { + const resend = createResendContact({ email }).catch((error) => { + logger.error("Error creating Resend contact", { + email, + error, + }); + captureException(error, undefined, email); + }); + + const dub = trackDubSignUp({ id: userId, email, name, image }).catch( + (error) => { logger.error("Error tracking Dub sign up", { - email: user.email, + email, error, }); - captureException(error, undefined, user.email); - }); - - await Promise.all([loops(), resend, dub]); - } - - if (isNewUser && user.email && user.id) { - await Promise.all([ - handlePendingPremiumInvite({ email: user.email }), - handleReferralOnSignUp({ - userId: user.id, - email: user.email, - }), - ]); - } + captureException(error, undefined, email); + }, + ); + + await Promise.all([ + loops(), + resend, + dub, + handlePendingPremiumInvite({ email }), + handleReferralOnSignUp({ userId, email }), + ]); } + async function handlePendingPremiumInvite({ email }: { email: string }) { - logger.info("Handling pending premium invite", { email }); - - // Check for pending invite - const premium = await prisma.premium.findFirst({ - where: { pendingInvites: { has: email } }, - select: { - id: true, - pendingInvites: true, - lemonSqueezySubscriptionItemId: true, - stripeSubscriptionId: true, - _count: { select: { users: true } }, - }, - }); + try { + logger.info("Handling pending premium invite", { email }); + + // Check for pending invite + const premium = await prisma.premium.findFirst({ + where: { pendingInvites: { has: email } }, + select: { + id: true, + pendingInvites: true, + lemonSqueezySubscriptionItemId: true, + stripeSubscriptionId: true, + _count: { select: { users: true } }, + }, + }); - if ( - premium?.lemonSqueezySubscriptionItemId || - premium?.stripeSubscriptionId - ) { - // Add user to premium and remove from pending invites - await prisma.premium.update({ - where: { id: premium.id }, - data: { - users: { connect: { email } }, - pendingInvites: { - set: premium.pendingInvites.filter((e: string) => e !== email), + if ( + premium?.lemonSqueezySubscriptionItemId || + premium?.stripeSubscriptionId + ) { + // Add user to premium and remove from pending invites + await prisma.premium.update({ + where: { id: premium.id }, + data: { + users: { connect: { email } }, + pendingInvites: { + set: premium.pendingInvites.filter((e: string) => e !== email), + }, }, - }, + }); + + logger.info("Added user to premium from invite", { email }); + } + } catch (error) { + logger.error("Error handling pending premium invite", { error, email }); + captureException(error, { + extra: { email, location: "handlePendingPremiumInvite" }, }); } - - logger.info("Added user to premium from invite", { email }); } export async function handleReferralOnSignUp({ diff --git a/version.txt b/version.txt index 354aaded94..d4063d8109 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.21.53 +v2.21.54 From f10d30549d6c69d61b740ce78f00d30859f9636b Mon Sep 17 00:00:00 2001 From: Eliezer Steinbock <3090527+elie222@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:43:38 -0500 Subject: [PATCH 3/3] adjust hook --- apps/web/utils/auth.ts | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/apps/web/utils/auth.ts b/apps/web/utils/auth.ts index b1e8c8096a..9643a53722 100644 --- a/apps/web/utils/auth.ts +++ b/apps/web/utils/auth.ts @@ -6,7 +6,6 @@ import { createContact as createResendContact } from "@inboxzero/resend"; import type { Account, AuthContext } from "better-auth"; import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; -import { createAuthMiddleware } from "better-auth/api"; import { nextCookies } from "better-auth/next-js"; import { cookies, headers } from "next/headers"; import { env } from "@/env"; @@ -112,6 +111,21 @@ export const betterAuthConfig = betterAuth({ }, }, databaseHooks: { + user: { + create: { + after: async (user) => { + await postSignUp({ + id: user.id, + email: user.email, + name: user.name, + image: user.image, + }).catch((error) => { + logger.error("Error posting sign up", { error, user }); + captureException(error, { extra: { user } }); + }); + }, + }, + }, account: { create: { after: async (account: Account) => { @@ -132,22 +146,6 @@ export const betterAuthConfig = betterAuth({ }, errorURL: "/login/error", }, - hooks: { - after: createAuthMiddleware(async (ctx) => { - // Handle sign-up: call postSignUp when a new user is created - if (ctx.path.startsWith("/sign-up")) { - const newSession = ctx.context.newSession; - if (newSession) { - await postSignUp({ - id: newSession.user.id, - email: newSession.user.email, - name: newSession.user.name, - image: newSession.user.image, - }); - } - } - }), - }, }); async function postSignUp({