followupboss_client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +12 -0
  3. data/.gitignore +18 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.tool-versions +1 -0
  8. data/.travis.yml +9 -0
  9. data/CHANGELOG.md +149 -0
  10. data/CODE_OF_CONDUCT.md +49 -0
  11. data/Gemfile +4 -0
  12. data/LICENSE.txt +22 -0
  13. data/README.md +882 -0
  14. data/Rakefile +6 -0
  15. data/bin/console +14 -0
  16. data/bin/setup +8 -0
  17. data/followupboss_client.gemspec +43 -0
  18. data/lib/followupboss_client.rb +16 -0
  19. data/lib/fub_client/action_plan.rb +5 -0
  20. data/lib/fub_client/appointment.rb +42 -0
  21. data/lib/fub_client/appointment_outcome.rb +51 -0
  22. data/lib/fub_client/appointment_type.rb +51 -0
  23. data/lib/fub_client/call.rb +4 -0
  24. data/lib/fub_client/client.rb +200 -0
  25. data/lib/fub_client/compatibility.rb +18 -0
  26. data/lib/fub_client/configuration.rb +54 -0
  27. data/lib/fub_client/cookie_client.rb +190 -0
  28. data/lib/fub_client/custom_field.rb +5 -0
  29. data/lib/fub_client/deal.rb +41 -0
  30. data/lib/fub_client/deal_attachment.rb +61 -0
  31. data/lib/fub_client/deal_custom_field.rb +47 -0
  32. data/lib/fub_client/em_event.rb +5 -0
  33. data/lib/fub_client/email_template.rb +5 -0
  34. data/lib/fub_client/event.rb +8 -0
  35. data/lib/fub_client/group.rb +58 -0
  36. data/lib/fub_client/her_patch.rb +101 -0
  37. data/lib/fub_client/identity.rb +33 -0
  38. data/lib/fub_client/message.rb +41 -0
  39. data/lib/fub_client/middleware/authentication.rb +26 -0
  40. data/lib/fub_client/middleware/cookie_authentication.rb +61 -0
  41. data/lib/fub_client/middleware/parser.rb +59 -0
  42. data/lib/fub_client/middleware.rb +8 -0
  43. data/lib/fub_client/note.rb +4 -0
  44. data/lib/fub_client/people_relationship.rb +34 -0
  45. data/lib/fub_client/person.rb +5 -0
  46. data/lib/fub_client/person_attachment.rb +50 -0
  47. data/lib/fub_client/pipeline.rb +45 -0
  48. data/lib/fub_client/property.rb +26 -0
  49. data/lib/fub_client/rails8_patch.rb +39 -0
  50. data/lib/fub_client/resource.rb +33 -0
  51. data/lib/fub_client/shared_inbox.rb +389 -0
  52. data/lib/fub_client/smart_list.rb +5 -0
  53. data/lib/fub_client/stage.rb +39 -0
  54. data/lib/fub_client/task.rb +18 -0
  55. data/lib/fub_client/team.rb +65 -0
  56. data/lib/fub_client/team_inbox.rb +65 -0
  57. data/lib/fub_client/text_message.rb +46 -0
  58. data/lib/fub_client/text_message_template.rb +49 -0
  59. data/lib/fub_client/user.rb +4 -0
  60. data/lib/fub_client/version.rb +3 -0
  61. data/lib/fub_client/webhook.rb +47 -0
  62. data/lib/fub_client.rb +61 -0
  63. data/scripts/test_api.rb +110 -0
  64. data/scripts/test_shared_inbox.rb +90 -0
  65. metadata +335 -0
@@ -0,0 +1,190 @@
1
+ module FubClient
2
+ class CookieClient
3
+ attr_accessor :gist_url, :encryption_key, :subdomain
4
+ attr_reader :cookies
5
+
6
+ def initialize(subdomain: nil, gist_url: nil, encryption_key: nil, cookie: nil)
7
+ config = FubClient.configuration
8
+
9
+ @subdomain = subdomain || config.subdomain
10
+ @gist_url = gist_url || config.gist_url
11
+ @encryption_key = encryption_key || config.encryption_key
12
+
13
+ raise ArgumentError, 'Subdomain is required for cookie authentication' unless @subdomain
14
+
15
+ if cookie
16
+ self.cookies = cookie
17
+ puts "Using provided cookie (#{cookie.length} chars)" if ENV['DEBUG']
18
+ elsif @gist_url && @encryption_key
19
+ raise ArgumentError, 'Failed to fetch or decrypt cookie from GIST URL' unless fetch_cookie_from_gist
20
+ else
21
+ raise ArgumentError, "Either 'cookie' or both 'gist_url' and 'encryption_key' must be provided"
22
+ end
23
+
24
+ configure_client
25
+ end
26
+
27
+ def cookies=(value)
28
+ @cookies = value
29
+ client.cookies = value if @cookies
30
+ end
31
+
32
+ def client
33
+ @client ||= FubClient::Client.instance
34
+ end
35
+
36
+ def configure_client
37
+ client.subdomain = @subdomain
38
+ client.cookies = @cookies
39
+ client.reset_her_api
40
+ end
41
+
42
+ def reset_her_api
43
+ client.reset_her_api
44
+ end
45
+
46
+ def fetch_cookie_from_gist
47
+ begin
48
+ require 'net/http'
49
+ require 'uri'
50
+ require 'json'
51
+ require 'openssl'
52
+
53
+ puts "Fetching cookie from gist URL: #{@gist_url}" if ENV['DEBUG']
54
+
55
+ uri = URI.parse(@gist_url)
56
+ response = Net::HTTP.get_response(uri)
57
+
58
+ if response.code == '200'
59
+ json_data = JSON.parse(response.body)
60
+
61
+ if json_data && json_data['followupboss.com']
62
+ cookie_value = json_data['followupboss.com']
63
+ puts "Found cookie data (#{cookie_value.length} chars)" if ENV['DEBUG']
64
+
65
+ if cookie_value.length > 1000 && cookie_value.match?(/^[0-9a-fA-F]+$/)
66
+ puts 'Data appears to be encrypted hex, attempting decryption...' if ENV['DEBUG']
67
+ decrypted_cookie = decrypt_cookie(cookie_value)
68
+
69
+ if decrypted_cookie && !decrypted_cookie.empty?
70
+ processed_cookie = process_decrypted_data(decrypted_cookie)
71
+ self.cookies = processed_cookie
72
+ if ENV['DEBUG']
73
+ puts "Successfully decrypted and processed cookie from gist (#{processed_cookie.length} chars)"
74
+ end
75
+ return true
76
+ else
77
+ puts 'Failed to decrypt cookie from GIST' if ENV['DEBUG']
78
+ return false
79
+ end
80
+ else
81
+ puts 'Data appears to be unencrypted cookie string, using directly' if ENV['DEBUG']
82
+ self.cookies = cookie_value
83
+ puts "Successfully loaded unencrypted cookie from gist (#{cookie_value.length} chars)" if ENV['DEBUG']
84
+ return true
85
+ end
86
+ elsif ENV['DEBUG']
87
+ puts 'Invalid cookie data format in gist'
88
+ end
89
+ elsif ENV['DEBUG']
90
+ puts "Failed to fetch gist data: HTTP #{response.code}"
91
+ end
92
+ rescue StandardError => e
93
+ puts "Error fetching from gist: #{e.message}" if ENV['DEBUG']
94
+ puts e.backtrace.join("\n") if ENV['DEBUG']
95
+ return false
96
+ end
97
+
98
+ puts 'Failed to fetch or decrypt cookie from GIST' if ENV['DEBUG']
99
+ false
100
+ end
101
+
102
+ private
103
+
104
+ def decrypt_cookie(encrypted_hex)
105
+ result = try_aes_cbc_decrypt(encrypted_hex)
106
+
107
+ if result
108
+ puts 'Successfully decrypted using kevast-encrypt method' if ENV['DEBUG']
109
+ result
110
+ else
111
+ puts 'Kevast-encrypt decryption failed' if ENV['DEBUG']
112
+ nil
113
+ end
114
+ end
115
+
116
+ def try_aes_cbc_decrypt(encrypted_hex)
117
+ encrypted_data = [encrypted_hex].pack('H*')
118
+
119
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
120
+ cipher.decrypt
121
+
122
+ key_size = 16
123
+ iv_size = 16
124
+
125
+ derived_key_iv = openssl_derive_bytes(@encryption_key, nil, key_size + iv_size)
126
+
127
+ key = derived_key_iv[0, key_size]
128
+ iv = derived_key_iv[key_size, iv_size]
129
+
130
+ cipher.key = key
131
+ cipher.iv = iv
132
+
133
+ decrypted = cipher.update(encrypted_data) + cipher.final
134
+ decrypted.force_encoding('UTF-8')
135
+
136
+ puts 'Kevast-encrypt compatible decryption successful' if ENV['DEBUG']
137
+ decrypted
138
+ rescue StandardError => e
139
+ puts "Kevast-encrypt compatible decryption failed: #{e.message}" if ENV['DEBUG']
140
+ nil
141
+ end
142
+
143
+ def openssl_derive_bytes(password, salt, key_len)
144
+ d = d_i = ''
145
+ while d.length < key_len
146
+ d_i = OpenSSL::Digest::MD5.digest(d_i + password + (salt || ''))
147
+ d += d_i
148
+ end
149
+ d[0, key_len]
150
+ end
151
+
152
+ def process_decrypted_data(decrypted_data)
153
+ begin
154
+ cookies_array = JSON.parse(decrypted_data)
155
+
156
+ if cookies_array.is_a?(Array)
157
+ puts "Processing #{cookies_array.length} cookie objects from JSON" if ENV['DEBUG']
158
+
159
+ cookie_parts = []
160
+
161
+ cookies_array.each do |cookie_obj|
162
+ next unless cookie_obj.is_a?(Hash) && cookie_obj['name'] && cookie_obj['value']
163
+
164
+ domain = cookie_obj['domain'] || ''
165
+ if domain.include?('followupboss.com') || domain.include?('.followupboss.com') || domain.empty?
166
+ cookie_parts << "#{cookie_obj['name']}=#{cookie_obj['value']}"
167
+ puts "Added cookie: #{cookie_obj['name']} (domain: '#{domain}')" if ENV['DEBUG']
168
+ end
169
+ end
170
+
171
+ if cookie_parts.any?
172
+ cookie_string = cookie_parts.join('; ')
173
+ if ENV['DEBUG']
174
+ puts "Created cookie string with #{cookie_parts.length} cookies (#{cookie_string.length} chars)"
175
+ end
176
+ return cookie_string
177
+ elsif ENV['DEBUG']
178
+ puts 'No followupboss.com cookies found in JSON data'
179
+ end
180
+ end
181
+ rescue JSON::ParserError => e
182
+ puts "Data is not JSON, treating as plain cookie string: #{e.message}" if ENV['DEBUG']
183
+ rescue StandardError => e
184
+ puts "Error processing decrypted data: #{e.message}" if ENV['DEBUG']
185
+ end
186
+
187
+ decrypted_data
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,5 @@
1
+ module FubClient
2
+ class CustomField < Resource
3
+ collection_path 'customFields'
4
+ end
5
+ end
@@ -0,0 +1,41 @@
1
+ module FubClient
2
+ class Deal < Resource
3
+ collection_path 'deals'
4
+ root_element :deal
5
+ include_root_in_json true
6
+
7
+ scope :by_stage, ->(stage_id) { where(stageId: stage_id) }
8
+ scope :for_person, ->(person_id) { where(personId: person_id) }
9
+ scope :closing_between, lambda { |start_date, end_date|
10
+ where(closeDateStart: start_date, closeDateEnd: end_date)
11
+ }
12
+ scope :by_price_range, ->(min, max) { where(minPrice: min, maxPrice: max) }
13
+ scope :assigned_to, ->(user_id) { where(assignedTo: user_id) }
14
+
15
+ def self.active
16
+ where(status: 'active')
17
+ end
18
+
19
+ def self.won
20
+ where(status: 'won')
21
+ end
22
+
23
+ def self.lost
24
+ where(status: 'lost')
25
+ end
26
+
27
+ def people
28
+ return [] unless respond_to?(:person_ids) && person_ids.is_a?(Array)
29
+
30
+ person_ids.map do |id|
31
+ FubClient::Person.find(id)
32
+ end.compact
33
+ end
34
+
35
+ def property
36
+ return nil unless respond_to?(:property_id) && property_id
37
+
38
+ FubClient::Property.find(property_id)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ module FubClient
2
+ class DealAttachment < Resource
3
+ collection_path 'dealAttachments'
4
+ root_element :deal_attachment
5
+ include_root_in_json true
6
+
7
+ # Convenience method to find attachments by deal
8
+ scope :for_deal, ->(deal_id) { where(dealId: deal_id) }
9
+
10
+ # Convenience method to find attachments by type
11
+ scope :by_type, ->(type) { where(type: type) }
12
+
13
+ # Convenience method to find attachments by name (partial match)
14
+ scope :by_name, ->(name) { where(q: name) }
15
+
16
+ # Helper method to get the deal for this attachment
17
+ def deal
18
+ return nil unless respond_to?(:deal_id) && deal_id
19
+
20
+ FubClient::Deal.find(deal_id)
21
+ end
22
+
23
+ # Upload a new attachment for a deal
24
+ def self.upload(deal_id, file_path, name = nil, type = nil, description = nil)
25
+ # This would typically be implemented with a multipart form,
26
+ # but we'll just define the interface here
27
+ params = {
28
+ dealId: deal_id,
29
+ file: File.new(file_path, 'rb')
30
+ }
31
+ params[:name] = name if name
32
+ params[:type] = type if type
33
+ params[:description] = description if description
34
+
35
+ post('', params)
36
+ end
37
+
38
+ # Download the attachment content
39
+ def download
40
+ return nil unless id
41
+
42
+ begin
43
+ self.class.get("#{id}/download")
44
+ rescue StandardError
45
+ nil
46
+ end
47
+ end
48
+
49
+ # Delete the attachment
50
+ def delete
51
+ return false unless id
52
+
53
+ begin
54
+ self.class.delete(id.to_s)
55
+ true
56
+ rescue StandardError
57
+ false
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,47 @@
1
+ module FubClient
2
+ class DealCustomField < Resource
3
+ collection_path 'dealCustomFields'
4
+ root_element :deal_custom_field
5
+ include_root_in_json true
6
+
7
+ # Convenience method to find custom fields by type
8
+ scope :by_type, ->(type) { where(type: type) }
9
+
10
+ # Convenience method to find custom fields by name (partial match)
11
+ scope :by_name, ->(name) { where(q: name) }
12
+
13
+ # Convenience method to find active custom fields
14
+ def self.active
15
+ where(active: true)
16
+ end
17
+
18
+ # Convenience method to find inactive custom fields
19
+ def self.inactive
20
+ where(active: false)
21
+ end
22
+
23
+ # Update a custom field
24
+ def update(attributes)
25
+ return false unless id
26
+
27
+ begin
28
+ self.class.put(id.to_s, attributes)
29
+ true
30
+ rescue StandardError
31
+ false
32
+ end
33
+ end
34
+
35
+ # Delete a custom field
36
+ def delete
37
+ return false unless id
38
+
39
+ begin
40
+ self.class.delete(id.to_s)
41
+ true
42
+ rescue StandardError
43
+ false
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ module FubClient
2
+ class EmEvent < Resource
3
+ collection_path 'emEvents'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module FubClient
2
+ class EmailTemplate < Resource
3
+ collection_path 'templates'
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module FubClient
2
+ class Event < Resource
3
+ attributes :created, :updated, :person_id, :message, :description,
4
+ :note_id, :source, :type, :property
5
+
6
+ belongs_to :person
7
+ end
8
+ end
@@ -0,0 +1,58 @@
1
+ module FubClient
2
+ class Group < Resource
3
+ collection_path 'groups'
4
+ root_element :group
5
+ include_root_in_json true
6
+
7
+ # Convenience method to find groups by name (partial match)
8
+ scope :by_name, ->(name) { where(q: name) }
9
+
10
+ # Convenience method to find active groups
11
+ def self.active
12
+ where(active: true)
13
+ end
14
+
15
+ # Get members of this group
16
+ def members
17
+ return [] unless id
18
+
19
+ begin
20
+ response = self.class.get("#{id}/members")
21
+ response[:members] || []
22
+ rescue StandardError
23
+ []
24
+ end
25
+ end
26
+
27
+ # Add a user to this group
28
+ def add_user(user_id)
29
+ return false unless id && user_id
30
+
31
+ begin
32
+ self.class.post("#{id}/members", { userId: user_id })
33
+ true
34
+ rescue StandardError
35
+ false
36
+ end
37
+ end
38
+
39
+ # Remove a user from this group
40
+ def remove_user(user_id)
41
+ return false unless id && user_id
42
+
43
+ begin
44
+ self.class.delete("#{id}/members/#{user_id}")
45
+ true
46
+ rescue StandardError
47
+ false
48
+ end
49
+ end
50
+
51
+ # Get round-robin distribution information
52
+ def self.round_robin
53
+ get('roundRobin')
54
+ rescue StandardError
55
+ nil
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,101 @@
1
+ # Her gem compatibility patch for Faraday 2.x
2
+
3
+ if defined?(Faraday) && !defined?(Faraday::Response::Middleware)
4
+ middleware_class = Class.new(Faraday::Middleware) do
5
+ def initialize(app, options = {})
6
+ super(app)
7
+ @options = options
8
+ end
9
+ end
10
+
11
+ Faraday::Response.const_set('Middleware', middleware_class)
12
+ puts '[FubClient] Created Faraday::Response::Middleware using const_set'
13
+ end
14
+
15
+ original_require = method(:require)
16
+
17
+ define_method(:require) do |name|
18
+ case name
19
+ when 'her/middleware/parse_json'
20
+ module Her
21
+ module Middleware
22
+ class ParseJSON < Faraday::Middleware
23
+ def initialize(app, options = {})
24
+ super(app)
25
+ @options = options
26
+ end
27
+
28
+ def on_complete(env)
29
+ return unless process_response_type?(env[:response_headers]['content-type'])
30
+
31
+ env[:body] = parse(env[:body])
32
+ end
33
+
34
+ private
35
+
36
+ def parse(body)
37
+ MultiJson.load(body, symbolize_keys: true)
38
+ rescue MultiJson::ParseError => e
39
+ {
40
+ error: "JSON parsing error: #{e.message}",
41
+ body: body
42
+ }
43
+ end
44
+
45
+ def process_response_type?(content_type)
46
+ content_type && content_type.match(%r{application/json})
47
+ end
48
+ end
49
+ end
50
+ end
51
+ puts '[FubClient] Intercepted and replaced her/middleware/parse_json'
52
+ true
53
+ when 'her/middleware/first_level_parse_json'
54
+ module Her
55
+ module Middleware
56
+ class FirstLevelParseJSON < Faraday::Middleware
57
+ def initialize(app, options = {})
58
+ super(app)
59
+ @options = options
60
+ end
61
+
62
+ def on_complete(env)
63
+ return unless process_response_type?(env[:response_headers]['content-type'])
64
+
65
+ env[:body] = parse(env[:body])
66
+ end
67
+
68
+ private
69
+
70
+ def parse(body)
71
+ MultiJson.load(body)
72
+ rescue MultiJson::ParseError => e
73
+ {
74
+ error: "JSON parsing error: #{e.message}",
75
+ body: body
76
+ }
77
+ end
78
+
79
+ def process_response_type?(content_type)
80
+ content_type && content_type.match(%r{application/json})
81
+ end
82
+ end
83
+ end
84
+ end
85
+ puts '[FubClient] Intercepted and replaced her/middleware/first_level_parse_json'
86
+ true
87
+ when %r{^her/middleware/}
88
+ begin
89
+ original_require.call(name)
90
+ rescue NameError => e
91
+ raise e unless e.message.include?('Faraday::Response::Middleware')
92
+
93
+ puts "[FubClient] Skipped loading #{name} due to Faraday::Response::Middleware error"
94
+ true
95
+ end
96
+ else
97
+ original_require.call(name)
98
+ end
99
+ end
100
+
101
+ puts '[FubClient] Applied Her gem require interception patches'
@@ -0,0 +1,33 @@
1
+ module FubClient
2
+ class Identity < Resource
3
+ collection_path 'identity'
4
+ root_element :identity
5
+ include_root_in_json true
6
+
7
+ def self.current
8
+ get('')
9
+ rescue StandardError
10
+ nil
11
+ end
12
+
13
+ def user
14
+ return nil unless respond_to?(:user_id) && user_id
15
+
16
+ FubClient::User.find(user_id)
17
+ end
18
+
19
+ def teams
20
+ return [] unless respond_to?(:team_ids) && team_ids.is_a?(Array)
21
+
22
+ team_ids.map do |id|
23
+ FubClient::Team.find(id)
24
+ end.compact
25
+ end
26
+
27
+ def has_permission?(permission)
28
+ return false unless respond_to?(:permissions) && permissions.is_a?(Array)
29
+
30
+ permissions.include?(permission)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ module FubClient
2
+ class Message < Resource
3
+ collection_path 'messages'
4
+ root_element :message
5
+ include_root_in_json true
6
+
7
+ scope :for_person, ->(person_id) { where(personId: person_id) }
8
+ scope :by_type, ->(type) { where(type: type) }
9
+ scope :sent_between, lambda { |start_date, end_date|
10
+ where(startDate: start_date, endDate: end_date)
11
+ }
12
+ scope :inbound, -> { where(direction: 'inbound') }
13
+ scope :outbound, -> { where(direction: 'outbound') }
14
+ scope :by_user, ->(user_id) { where(userId: user_id) }
15
+ scope :search, ->(query) { where(q: query) }
16
+
17
+ def self.unread
18
+ where(read: false)
19
+ end
20
+
21
+ def self.with_attachments
22
+ where(hasAttachments: true)
23
+ end
24
+
25
+ def mark_as_read
26
+ self.class.put("#{id}/read", {})
27
+ end
28
+
29
+ def person
30
+ return nil unless respond_to?(:person_id) && person_id
31
+
32
+ FubClient::Person.find(person_id)
33
+ end
34
+
35
+ def user
36
+ return nil unless respond_to?(:user_id) && user_id
37
+
38
+ FubClient::User.find(user_id)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ module FubClient
2
+ module Middleware
3
+ class Authentication < Faraday::Middleware
4
+ def call(env)
5
+ # Check if cookies are being used - if so, don't add API key
6
+ if FubClient::Client.instance.use_cookies?
7
+ puts 'Skipping API key auth since cookies are being used' if ENV['DEBUG']
8
+ else
9
+ # Get the API key from the client
10
+ api_key = FubClient::Client.instance.api_key
11
+
12
+ # Format the authorization header - API key as username with blank password
13
+ # This follows the exact format seen working in the curl command
14
+ auth_encoded = Base64.strict_encode64("#{api_key}:")
15
+ env[:request_headers]['Authorization'] = "Basic #{auth_encoded}"
16
+
17
+ # Debug - remove in production
18
+ puts "Debug Authentication Header: #{env[:request_headers]['Authorization']}" if ENV['DEBUG']
19
+ end
20
+
21
+ # Call the next middleware in the chain
22
+ @app.call(env)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ module FubClient
2
+ module Middleware
3
+ class CookieAuthentication < Faraday::Middleware
4
+ def call(env)
5
+ # Get the cookies from the client
6
+ cookies = FubClient::Client.instance.cookies
7
+
8
+ if cookies && !cookies.empty?
9
+ # CRITICAL: Must set a request header to enable cookie-based auth
10
+ # and prevent falling back to API key auth
11
+
12
+ # First, remove any Authorization header that might be added elsewhere
13
+ env[:request_headers].delete('Authorization')
14
+
15
+ # Add cookies as a request header (just like curl -b)
16
+ env[:request_headers]['Cookie'] = cookies
17
+
18
+ # Add other required headers as seen in the working curl example
19
+ env[:request_headers]['X-Requested-With'] = 'XMLHttpRequest'
20
+ env[:request_headers]['X-System'] = 'fub-spa'
21
+ env[:request_headers]['Accept'] = 'application/json, text/javascript, */*; q=0.01'
22
+ env[:request_headers]['Accept-Language'] = 'en-US,en;q=0.9'
23
+ env[:request_headers]['User-Agent'] =
24
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
25
+
26
+ if ENV['DEBUG']
27
+ puts "Using cookies for authentication (#{cookies.length} chars)"
28
+ puts "Cookie starts with: #{cookies[0..50]}..." if cookies.length > 50
29
+ puts "Request URL: #{env[:url]}"
30
+
31
+ # Remove any API key that might have been added
32
+ # This is to diagnose if we're seeing API key auth being used
33
+ api_key = env[:request_headers]['Authorization']
34
+ if api_key
35
+ puts "WARNING: Authorization header still present: #{api_key}"
36
+ puts 'Removing Authorization header before request'
37
+ env[:request_headers].delete('Authorization')
38
+ end
39
+ end
40
+ elsif ENV['DEBUG']
41
+ puts 'Warning: No cookies available for authentication'
42
+ end
43
+
44
+ # Debug the request one more time
45
+ if ENV['DEBUG']
46
+ puts 'Final request headers:'
47
+ env[:request_headers].each do |k, v|
48
+ if k.downcase == 'cookie' && v.length > 50
49
+ puts " #{k}: #{v[0..50]}..."
50
+ else
51
+ puts " #{k}: #{v}"
52
+ end
53
+ end
54
+ end
55
+
56
+ # Call the next middleware in the chain
57
+ @app.call(env)
58
+ end
59
+ end
60
+ end
61
+ end