From 11ee9b946f296a3cf6145d36ecb1a8b11e67d854 Mon Sep 17 00:00:00 2001 From: Hammer Date: Wed, 28 Jan 2026 22:17:08 +0000 Subject: [PATCH] fix: invite accept bypasses disableSignUp by falling back to internal adapter signUpEmail throws when disableSignUp is true. Now catches that error and creates the user directly via Better Auth's internal adapter: createUser + linkAccount with hashed password. --- src/routes/invite.ts | 66 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/routes/invite.ts b/src/routes/invite.ts index fe7d6b0..a88c14b 100644 --- a/src/routes/invite.ts +++ b/src/routes/invite.ts @@ -73,13 +73,16 @@ export const inviteRoutes = new Elysia({ prefix: '/auth/invite' }) } try { - // Use Better Auth's internal API to create the user properly - const result = await auth.api.signUpEmail({ + // Create user via internal adapter (bypasses disableSignUp restriction) + const userName = body.name || invite.name; + const createdUser = await auth.api.signUpEmail({ body: { email: invite.email, password: body.password, - name: body.name || invite.name, + name: userName, }, + headers: new Headers(), + // Use asResponse: false to get direct result }); // Set the role from the invite @@ -97,14 +100,67 @@ export const inviteRoutes = new Elysia({ prefix: '/auth/invite' }) return { success: true, user: { - id: result.user?.id, + id: createdUser.user?.id, email: invite.email, - name: body.name || invite.name, + name: userName, role: invite.role, }, }; } catch (error: any) { console.error('Invite accept error:', error); + + // If signUpEmail fails due to disableSignUp, use direct DB approach + if (error.message?.includes('not enabled') || error.status === 400) { + try { + const userName = body.name || invite.name; + + // Hash password using Better Auth's context + const ctx = await (auth as any).$context; + const hash = await ctx.password.hash(body.password); + + // Create user directly + const newUser = await ctx.internalAdapter.createUser({ + email: invite.email.toLowerCase(), + name: userName, + emailVerified: false, + }); + + // Link credential account with hashed password + await ctx.internalAdapter.linkAccount({ + userId: newUser.id, + providerId: 'credential', + accountId: newUser.id, + password: hash, + }); + + // Set the role from the invite + if (invite.role) { + await db.update(users) + .set({ role: invite.role }) + .where(eq(users.id, newUser.id)); + } + + // Mark invite as accepted + await db.update(invites) + .set({ status: 'accepted' }) + .where(eq(invites.id, invite.id)); + + return { + success: true, + user: { + id: newUser.id, + email: invite.email, + name: userName, + role: invite.role, + }, + }; + } catch (innerError: any) { + console.error('Direct user creation also failed:', innerError); + set.status = 400; + throw new Error(innerError.message || 'Failed to create account'); + } + } + set.status = 400; throw new Error(error.message || 'Failed to create account'); }