fix(auth): record attribution for oauth signups (#14796)
Google OAuth signups now persist the same first-party attribution cookies as email signups on Chatwoot Cloud. This keeps attribution capture owned by the website and reuses the existing Enterprise-only account attribution service. The OAuth callback only records the already-shaped first-touch and last-touch cookie payload after a new account is created. ## What changed - Added Enterprise-only attribution persistence to the Google OAuth signup account creation path. - Reuses `Internal::Accounts::MarketingAttributionService`. - Keeps attribution best-effort so failures do not interrupt OAuth signup. - Leaves existing email signup and SAML behavior unchanged. ## How to test - Start from a Chatwoot Cloud-like setup with attribution cookies present. - Sign up using the Google OAuth button. - Confirm the created account has `internal_attributes['marketing_attribution']` with `first_touch` and `last_touch`. - Confirm existing Google OAuth login still redirects normally. Validation run locally: - `bundle exec rspec spec/enterprise/controllers/enterprise/devise_overrides/google_oauth_attribution_spec.rb` - `bundle exec rspec spec/controllers/devise/omniauth_callbacks_controller_spec.rb spec/enterprise/controllers/enterprise/devise_overrides/google_oauth_attribution_spec.rb` - `bundle exec rubocop enterprise/app/controllers/enterprise/devise_overrides/omniauth_callbacks_controller.rb spec/enterprise/controllers/enterprise/devise_overrides/google_oauth_attribution_spec.rb`
This commit is contained in:
+13
@@ -29,6 +29,19 @@ module Enterprise::DeviseOverrides::OmniauthCallbacksController
|
||||
|
||||
private
|
||||
|
||||
def create_account_for_user
|
||||
super
|
||||
record_marketing_attribution
|
||||
end
|
||||
|
||||
def record_marketing_attribution
|
||||
return if @account.blank?
|
||||
|
||||
Internal::Accounts::MarketingAttributionService.new(account: @account, cookies: cookies).perform
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e).capture_exception
|
||||
end
|
||||
|
||||
def handle_saml_auth
|
||||
account_id = extract_saml_account_id
|
||||
relay_state = saml_relay_state
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
require 'rails_helper'
|
||||
require 'base64'
|
||||
|
||||
RSpec.describe 'Enterprise Google OAuth attribution', type: :request do
|
||||
let(:email_validation_service) { instance_double(Account::SignUpEmailValidationService) }
|
||||
let(:email) { 'oauth-attribution@example.com' }
|
||||
let(:account_builder) { double }
|
||||
let(:account) { create(:account) }
|
||||
let(:first_touch_cookie) { encoded_cookie('source' => 'reddit', 'source_type' => 'paid_social') }
|
||||
let(:last_touch_cookie) { encoded_cookie('source' => 'github', 'source_type' => 'referral') }
|
||||
|
||||
before do
|
||||
allow(ChatwootApp).to receive(:enterprise?).and_return(true)
|
||||
allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true)
|
||||
allow(Account::SignUpEmailValidationService).to receive(:new).and_return(email_validation_service)
|
||||
allow(email_validation_service).to receive(:perform).and_return(true)
|
||||
allow(AccountBuilder).to receive(:new).and_return(account_builder)
|
||||
allow(account_builder).to receive(:perform) do
|
||||
[create(:user, email: email, account: account), account]
|
||||
end
|
||||
|
||||
OmniAuth.config.test_mode = true
|
||||
OmniAuth.config.mock_auth[:google_oauth2] = OmniAuth::AuthHash.new(
|
||||
provider: 'google',
|
||||
uid: '123545',
|
||||
info: {
|
||||
name: 'OAuth Attribution',
|
||||
email: email,
|
||||
image: 'https://example.com/image.jpg'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'records marketing attribution for Google OAuth signups' do
|
||||
cookies[Internal::Accounts::MarketingAttributionService::FIRST_TOUCH_COOKIE] = first_touch_cookie
|
||||
cookies[Internal::Accounts::MarketingAttributionService::LAST_TOUCH_COOKIE] = last_touch_cookie
|
||||
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true', FRONTEND_URL: 'http://www.example.com' do
|
||||
get '/omniauth/google_oauth2/callback'
|
||||
follow_redirect!
|
||||
end
|
||||
|
||||
attribution = account.reload.internal_attributes['marketing_attribution']
|
||||
|
||||
expect(attribution['captured_from']).to eq('cookie')
|
||||
expect(attribution['first_touch']).to include('source' => 'reddit', 'source_type' => 'paid_social')
|
||||
expect(attribution['last_touch']).to include('source' => 'github', 'source_type' => 'referral')
|
||||
end
|
||||
|
||||
def encoded_cookie(payload)
|
||||
Base64.urlsafe_encode64(payload.to_json, padding: false)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user