#Convex Auth: user duplicates

4 messages · Page 1 of 1 (latest)

hazy mortar
#

Using Tanstack Start with Convex Auth and Resend as provider with Magic Link.

The issue is that I already had existing users signing in with Clerk.

How can I avoid duplication of users by same email without having to use createOrUpdateUser callback?
Or should I use the callback while every user signs in at least once with Convex Auth and then remove the callback?

Setup:

auth.ts

export const { auth, signIn, signOut, store } = convexAuth({
  providers: [
    ResendProvider({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
      async sendVerificationRequest({ identifier: to, provider, url }) {
        if (provider.apiKey === undefined) {
          console.error("Set up `AUTH_RESEND_KEY` to send auth emails");
          return;
        }
        try {
          const resend = new Resend(process.env.AUTH_RESEND_KEY!);

          const res = await resend.emails.send({
            from: "Company <[email protected]>",
            to,
            subject: `Iniciar sesión en Fentexhaus.mx`,
            react: FxSignInEmail({ url }),
            text: `Iniciar sesión en fentexhaus.mx: ${url}`,
          });

          if (res.error)
            throw new Error("Resend error: " + JSON.stringify(res.error));
        } catch (error) {
          console.error("Failed to send verification email:", error);
          throw error;
        }
      },
    }),
  ],
  session: {
    totalDurationMs: 1000 * 60 * 60 * 24 * 30, // 30 days
    inactiveDurationMs: 1000 * 60 * 60 * 24 * 30, // 30 days
  },
});

schema.ts

 users: defineTable({
    bz_distributorId: v.optional(v.string()),
    email: v.optional(v.string()),
    emailVerificationTime: v.optional(v.number()),
    image: v.optional(v.string()),
    isAnonymous: v.optional(v.boolean()),
    name: v.optional(v.string()),
    phone: v.optional(v.string()),
    phoneVerificationTime: v.optional(v.number()),
  }).index("email", ["email"]),
thick cosmosBOT
#

Hi @hazy mortar. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

To address your issue of avoiding user duplication by email when transitioning from Clerk to Convex Auth with Resend as the provider for Magic Link, there are a couple of approaches you can consider:

  1. Using the createOrUpdateUser callback:
    This is the most straightforward way to handle the situation, but you mentioned wanting to avoid this if possible. However, it might be the most reliable method to ensure no duplicates are created.

  2. Implementing a custom check before sign-in:
    You can create a custom mutation that checks for existing users before initiating the sign-in process. This approach doesn't require modifying the createOrUpdateUser callback but adds an extra step in your sign-in flow.

Here's how you could implement the second approach:

  1. Create a new mutation function in your Convex backend:
// convex/checkExistingUser.ts
import { mutation } from "./_generated/server";

export const checkExistingUser = mutation(
  async ({ db }, { email }: { email: string }) => {
    const existingUser = await db
      .query("users")
      .withIndex("email", (q) => q.eq("email", email))
      .first();
    
    return !!existingUser;
  }
);
  1. Modify your sign-in flow in the frontend to first check for existing users:
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

// In your sign-in component
const checkExistingUser = useMutation(api.checkExistingUser);
const handleSignIn = async (email: string) => {
  const userExists = await checkExistingUser({ email });
  if (userExists) {
    // Proceed with sign-in
    await signIn("resend", { email });
  } else {
    // Handle new user (e.g., show a registration form or error message)
    console.log("User does not exist. Please register first.");
  }
};
#

This approach allows you to check for existing users before initiating the sign-in process, preventing the creation of duplicate accounts. However, it's worth noting that this method adds an extra network request to your sign-in flow and may not be as robust as using the createOrUpdateUser callback.

If you want to ensure absolute consistency and handle edge cases (like race conditions), using the createOrUpdateUser callback as suggested in the Convex Auth Advanced documentation (https://labs.convex.dev/auth/advanced#controlling-account-linking-behavior) might be the best approach. You could use it temporarily while transitioning users and then remove it once all users have signed in at least once with Convex Auth, as you suggested.

Remember that when using the createOrUpdateUser callback, you'll need to implement all the necessary logic for user creation and updating, including handling the authAccounts table and other related tables as mentioned in the Discord discussion (https://discord.com/channels/1019350475847499849/1285986930479071314).

Ultimately, the choice depends on your specific requirements and the level of control you need over the user creation process.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: