fix(auth): insert invited users into public.users on acceptInvite

Previously, acceptInvite only wrote to the tenant schema, causing invited
users to be invisible to the login() flow which queries public.users for
cross-tenant email/phone lookup. Now inserts into both public.users and
the tenant schema within the same transaction, matching registerWithNewTenant behavior.

Also tightens duplicate check to cross-tenant uniqueness (public.users)
instead of per-tenant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-07 05:31:50 -08:00
parent 2813c6a1bf
commit 915bd400c1
1 changed files with 14 additions and 7 deletions

View File

@ -412,17 +412,24 @@ export class AuthService {
await qr.connect();
try {
await qr.startTransaction();
await qr.query(`SET LOCAL search_path TO "${schemaName}", public`);
// Check if email already exists in this tenant
const existingRows = await qr.query(
`SELECT id FROM users WHERE email = $1 LIMIT 1`,
// Check if email already exists in public.users (cross-tenant uniqueness)
const existingPublic = await qr.query(
`SELECT id FROM public.users WHERE email = $1 LIMIT 1`,
[invite.email],
);
if (existingRows.length > 0) {
throw new ConflictException('Email already registered in this organization');
if (existingPublic.length > 0) {
throw new ConflictException('Email already registered');
}
// a. Insert into public.users — enables login via email lookup
await qr.query(
`INSERT INTO public.users (id, tenant_id, email, password_hash, name, roles, is_active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
[userId, tenant.slug, invite.email, passwordHash, name, [invite.role], true, now, now],
);
// b. Insert into tenant schema for tenant-context management
await qr.query(`SET LOCAL search_path TO "${schemaName}", public`);
await qr.query(
`INSERT INTO users (id, tenant_id, email, password_hash, name, roles, is_active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,