Files
chatwoot/app/models/concerns/user_attribute_helpers.rb
Muhsin Keloth 7554156abe chore: Account switching issue in newly added accounts (#12403)
The system determines a user’s active account by checking the
`active_at` field in the `account_users` table and selecting the most
recently active account:

```ruby
def active_account_user
  account_users.order(active_at: :desc)&.first
end
```

This works fine when all accounts have a valid active_at timestamp.

**Problem**

When a user is added to a new account, the `active_at` value is NULL
(because the account has never been explicitly activated). Ordering by
active_at DESC produces inconsistent results across databases, since
handling of NULL values differs (sometimes treated as high, sometimes
low).

As a result:

- Mobile apps (critical impact): `/profile` returns the wrong account.
The UI keeps showing the old account even after switching, and
restarting does not fix it.
- Web app (accidentally works): Appears correct because the active
account is inferred from the browser URL, but the backend API is still
wrong.

**Root Cause**

- The ordering logic did not account for NULL `active_at`.
- New accounts without active_at sometimes get incorrectly prioritized
as the “active” account.

**Solution**

Explicitly ensure that accounts with NULL active_at are sorted after
accounts with real timestamps by using NULLS LAST:

```ruby
def active_account_user
  account_users.order(Arel.sql('active_at DESC NULLS LAST, id DESC'))&.first
end
```

- Accounts with actual `active_at` values will always be prioritized.
- New accounts (with NULL active_at) will be placed at the bottom until
the user explicitly activates them.
- Adding id DESC as a secondary ordering ensures consistent tie-breaking
when multiple accounts have the same `active_at`.
2025-09-10 14:12:22 +05:30

54 lines
1.1 KiB
Ruby

module UserAttributeHelpers
extend ActiveSupport::Concern
def available_name
self[:display_name].presence || name
end
def availability_status
current_account_user&.availability_status
end
def auto_offline
current_account_user&.auto_offline
end
def inviter
current_account_user&.inviter
end
def active_account_user
account_users.order(Arel.sql('active_at DESC NULLS LAST'))&.first
end
def current_account_user
# We want to avoid subsequent queries in case where the association is preloaded.
# using where here will trigger n+1 queries.
account_users.find { |ac_usr| ac_usr.account_id == Current.account.id } if Current.account
end
def account
current_account_user&.account
end
def administrator?
current_account_user&.administrator?
end
def agent?
current_account_user&.agent?
end
def role
current_account_user&.role
end
# Used internally for Chatwoot in Chatwoot
def hmac_identifier
hmac_key = GlobalConfig.get('CHATWOOT_INBOX_HMAC_KEY')['CHATWOOT_INBOX_HMAC_KEY']
return OpenSSL::HMAC.hexdigest('sha256', hmac_key, email) if hmac_key.present?
''
end
end