feat: HL tenant setup + email domain auto-assignment
- Create pre-configured Hogan Lovells tenant with demo flag and auto_assign_domains: ["hoganlovells.com"] - Add POST /api/tenants/auto-assign endpoint: checks email domain against tenant settings, auto-assigns user as associate if match - Add AutoAssignByDomain to TenantService - Update registration flow: after signup, check auto-assign before showing tenant creation form. Skip tenant creation if auto-assigned. - Add DemoBanner component shown when tenant.settings.demo is true - Extend GET /api/me to return is_demo flag from tenant settings
This commit is contained in:
@@ -240,6 +240,54 @@ func (s *TenantService) UpdateMemberRole(ctx context.Context, tenantID, userID u
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoAssignByDomain finds a tenant with a matching auto_assign_domains setting
|
||||
// and adds the user as a member. Returns the tenant and role, or nil if no match.
|
||||
func (s *TenantService) AutoAssignByDomain(ctx context.Context, userID uuid.UUID, emailDomain string) (*models.TenantWithRole, error) {
|
||||
// Find tenant where settings.auto_assign_domains contains this domain
|
||||
var tenant models.Tenant
|
||||
err := s.db.GetContext(ctx, &tenant,
|
||||
`SELECT id, name, slug, settings, created_at, updated_at
|
||||
FROM tenants
|
||||
WHERE settings->'auto_assign_domains' ? $1
|
||||
LIMIT 1`,
|
||||
emailDomain,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil // no match — not an error
|
||||
}
|
||||
|
||||
// Check if already a member
|
||||
var exists bool
|
||||
err = s.db.GetContext(ctx, &exists,
|
||||
`SELECT EXISTS(SELECT 1 FROM user_tenants WHERE user_id = $1 AND tenant_id = $2)`,
|
||||
userID, tenant.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("check membership: %w", err)
|
||||
}
|
||||
if exists {
|
||||
// Already a member — return the existing membership
|
||||
role, err := s.GetUserRole(ctx, userID, tenant.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get existing role: %w", err)
|
||||
}
|
||||
return &models.TenantWithRole{Tenant: tenant, Role: role}, nil
|
||||
}
|
||||
|
||||
// Add as member (associate by default for auto-assigned users)
|
||||
role := "associate"
|
||||
_, err = s.db.ExecContext(ctx,
|
||||
`INSERT INTO user_tenants (user_id, tenant_id, role) VALUES ($1, $2, $3)`,
|
||||
userID, tenant.ID, role,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auto-assign user: %w", err)
|
||||
}
|
||||
|
||||
s.audit.Log(ctx, "create", "auto_membership", &tenant.ID, map[string]any{"domain": emailDomain}, map[string]any{"user_id": userID, "role": role})
|
||||
return &models.TenantWithRole{Tenant: tenant, Role: role}, nil
|
||||
}
|
||||
|
||||
// RemoveMember removes a user from a tenant. Cannot remove the last owner.
|
||||
func (s *TenantService) RemoveMember(ctx context.Context, tenantID, userID uuid.UUID) error {
|
||||
// Check if the user being removed is an owner
|
||||
|
||||
Reference in New Issue
Block a user