flow_chat 0.4.1 → 0.5.1
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.
- checksums.yaml +4 -4
- data/README.md +418 -295
- data/SECURITY.md +365 -0
- data/examples/initializer.rb +56 -1
- data/examples/media_prompts_examples.rb +1 -2
- data/examples/multi_tenant_whatsapp_controller.rb +61 -57
- data/examples/simulator_controller.rb +95 -0
- data/examples/ussd_controller.rb +17 -11
- data/examples/whatsapp_controller.rb +103 -14
- data/examples/whatsapp_media_examples.rb +78 -80
- data/examples/whatsapp_message_job.rb +3 -3
- data/lib/flow_chat/base_processor.rb +3 -2
- data/lib/flow_chat/config.rb +6 -3
- data/lib/flow_chat/session/cache_session_store.rb +5 -5
- data/lib/flow_chat/simulator/controller.rb +34 -5
- data/lib/flow_chat/simulator/views/simulator.html.erb +287 -12
- data/lib/flow_chat/ussd/gateway/nsano.rb +1 -1
- data/lib/flow_chat/ussd/processor.rb +1 -1
- data/lib/flow_chat/ussd/prompt.rb +13 -13
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/app.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +44 -50
- data/lib/flow_chat/whatsapp/configuration.rb +21 -20
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +129 -19
- data/lib/flow_chat/whatsapp/middleware/executor.rb +1 -1
- data/lib/flow_chat/whatsapp/processor.rb +1 -1
- data/lib/flow_chat/whatsapp/prompt.rb +27 -31
- data/lib/flow_chat/whatsapp/send_job_support.rb +7 -7
- data/lib/flow_chat/whatsapp/template_manager.rb +10 -10
- metadata +4 -2
data/SECURITY.md
ADDED
@@ -0,0 +1,365 @@
|
|
1
|
+
# FlowChat Security Guide
|
2
|
+
|
3
|
+
This guide covers the security features and best practices for FlowChat, including webhook signature validation and simulator authentication.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
FlowChat includes comprehensive security features to protect your WhatsApp and USSD applications:
|
8
|
+
|
9
|
+
- **Webhook Signature Validation**: Verify that WhatsApp webhooks are authentic
|
10
|
+
- **Simulator Authentication**: Secure access to the testing simulator
|
11
|
+
- **Configuration Validation**: Prevent insecure configurations
|
12
|
+
- **Environment-Specific Security**: Different security levels per environment
|
13
|
+
|
14
|
+
## WhatsApp Webhook Security
|
15
|
+
|
16
|
+
### Signature Validation
|
17
|
+
|
18
|
+
FlowChat automatically validates WhatsApp webhook signatures using HMAC-SHA256 to ensure requests come from WhatsApp.
|
19
|
+
|
20
|
+
#### Required Configuration
|
21
|
+
|
22
|
+
For webhook signature validation, you need to configure your WhatsApp app secret:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
# Using Rails credentials
|
26
|
+
rails credentials:edit
|
27
|
+
```
|
28
|
+
|
29
|
+
```yaml
|
30
|
+
whatsapp:
|
31
|
+
app_secret: "your_whatsapp_app_secret"
|
32
|
+
# ... other credentials
|
33
|
+
```
|
34
|
+
|
35
|
+
Or using environment variables:
|
36
|
+
|
37
|
+
```bash
|
38
|
+
export WHATSAPP_APP_SECRET="your_whatsapp_app_secret"
|
39
|
+
```
|
40
|
+
|
41
|
+
#### Security Modes
|
42
|
+
|
43
|
+
FlowChat supports two security modes for webhook validation:
|
44
|
+
|
45
|
+
**1. Full Security (Recommended for Production)**
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
config = FlowChat::Whatsapp::Configuration.new
|
49
|
+
config.app_secret = "your_whatsapp_app_secret" # Required
|
50
|
+
config.skip_signature_validation = false # Default: enforce validation
|
51
|
+
```
|
52
|
+
|
53
|
+
**2. Development Mode (Testing Only)**
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
config = FlowChat::Whatsapp::Configuration.new
|
57
|
+
config.app_secret = nil # Not required when disabled
|
58
|
+
config.skip_signature_validation = true # Explicitly disable validation
|
59
|
+
```
|
60
|
+
|
61
|
+
⚠️ **Security Warning**: Never disable signature validation in production environments.
|
62
|
+
|
63
|
+
#### Configuration Error Handling
|
64
|
+
|
65
|
+
When `app_secret` is missing and validation is not explicitly disabled, FlowChat raises a `ConfigurationError`:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
begin
|
69
|
+
processor.run WelcomeFlow, :main_page
|
70
|
+
rescue FlowChat::Whatsapp::ConfigurationError => e
|
71
|
+
Rails.logger.error "Security configuration error: #{e.message}"
|
72
|
+
head :internal_server_error
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
The error message provides clear guidance:
|
77
|
+
|
78
|
+
```
|
79
|
+
WhatsApp app_secret is required for webhook signature validation.
|
80
|
+
Either configure app_secret or set skip_signature_validation=true to explicitly disable validation.
|
81
|
+
```
|
82
|
+
|
83
|
+
### Environment-Specific Security
|
84
|
+
|
85
|
+
Configure different security levels per environment:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# config/initializers/flowchat.rb
|
89
|
+
case Rails.env
|
90
|
+
when 'development'
|
91
|
+
# Relaxed security for easier development
|
92
|
+
config.skip_signature_validation = true
|
93
|
+
|
94
|
+
when 'test'
|
95
|
+
# Skip validation for deterministic testing
|
96
|
+
config.skip_signature_validation = true
|
97
|
+
|
98
|
+
when 'staging', 'production'
|
99
|
+
# Full security for production-like environments
|
100
|
+
config.skip_signature_validation = false
|
101
|
+
|
102
|
+
# Ensure app_secret is configured
|
103
|
+
if ENV['WHATSAPP_APP_SECRET'].blank?
|
104
|
+
raise "WHATSAPP_APP_SECRET required for #{Rails.env} environment"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
## Simulator Security
|
110
|
+
|
111
|
+
### Authentication System
|
112
|
+
|
113
|
+
The FlowChat simulator uses secure HMAC-SHA256 signed cookies for authentication. This prevents unauthorized access to your simulator endpoints.
|
114
|
+
|
115
|
+
#### Required Configuration
|
116
|
+
|
117
|
+
Configure a simulator secret in your initializer:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
# config/initializers/flowchat.rb
|
121
|
+
FlowChat::Config.simulator_secret = "your_secure_secret_here"
|
122
|
+
```
|
123
|
+
|
124
|
+
#### Environment-Specific Secrets
|
125
|
+
|
126
|
+
Use different secrets per environment:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
case Rails.env
|
130
|
+
when 'development', 'test'
|
131
|
+
# Use Rails secret key with environment suffix
|
132
|
+
FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_#{Rails.env}"
|
133
|
+
|
134
|
+
when 'staging', 'production'
|
135
|
+
# Use environment variable for production
|
136
|
+
FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
|
137
|
+
|
138
|
+
if FlowChat::Config.simulator_secret.blank?
|
139
|
+
Rails.logger.warn "FLOWCHAT_SIMULATOR_SECRET not configured. Simulator will be unavailable."
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
#### Cookie Security
|
145
|
+
|
146
|
+
Simulator cookies are automatically configured with security best practices:
|
147
|
+
|
148
|
+
- **HMAC-SHA256 signed**: Prevents tampering
|
149
|
+
- **24-hour expiration**: Limits exposure window
|
150
|
+
- **Secure flag**: Only sent over HTTPS in production
|
151
|
+
- **HttpOnly flag**: Prevents XSS access
|
152
|
+
- **SameSite=Lax**: CSRF protection
|
153
|
+
|
154
|
+
#### Enabling Simulator Mode
|
155
|
+
|
156
|
+
Enable simulator mode only when needed:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# Enable simulator only in development/staging
|
160
|
+
enable_simulator = Rails.env.development? || Rails.env.staging?
|
161
|
+
|
162
|
+
processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: enable_simulator) do |config|
|
163
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
164
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### Simulator Request Flow
|
169
|
+
|
170
|
+
1. **User visits simulator**: Browser requests `/simulator`
|
171
|
+
2. **Cookie generation**: Server generates HMAC-signed cookie
|
172
|
+
3. **Simulator requests**: Include valid cookie for authentication
|
173
|
+
4. **Cookie validation**: Server validates HMAC signature and timestamp
|
174
|
+
5. **Request processing**: Continues if authentication succeeds
|
175
|
+
|
176
|
+
## Security Best Practices
|
177
|
+
|
178
|
+
### Production Checklist
|
179
|
+
|
180
|
+
✅ **WhatsApp Security**
|
181
|
+
- Configure `app_secret` for webhook validation
|
182
|
+
- Set `skip_signature_validation = false`
|
183
|
+
- Use environment variables for secrets
|
184
|
+
- Handle `ConfigurationError` exceptions
|
185
|
+
|
186
|
+
✅ **Simulator Security**
|
187
|
+
- Configure `simulator_secret` using environment variables
|
188
|
+
- Enable simulator only in development/staging
|
189
|
+
- Use unique secrets per environment
|
190
|
+
|
191
|
+
✅ **Environment Configuration**
|
192
|
+
- Different security levels per environment
|
193
|
+
- Fail fast on missing required configuration
|
194
|
+
- Log security warnings appropriately
|
195
|
+
|
196
|
+
✅ **Error Handling**
|
197
|
+
- Catch and log `ConfigurationError` exceptions
|
198
|
+
- Return appropriate HTTP status codes
|
199
|
+
- Don't expose sensitive information in errors
|
200
|
+
|
201
|
+
### Development Guidelines
|
202
|
+
|
203
|
+
**DO:**
|
204
|
+
- Use Rails `secret_key_base` + suffix for development secrets
|
205
|
+
- Skip webhook validation in development for easier testing
|
206
|
+
- Enable simulator mode for testing
|
207
|
+
- Use test-specific credentials in test environment
|
208
|
+
|
209
|
+
**DON'T:**
|
210
|
+
- Hardcode secrets in source code
|
211
|
+
- Disable security in production
|
212
|
+
- Use production secrets in development
|
213
|
+
- Commit secrets to version control
|
214
|
+
|
215
|
+
### Example Secure Configuration
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
# config/initializers/flowchat.rb
|
219
|
+
case Rails.env
|
220
|
+
when 'development'
|
221
|
+
FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_dev"
|
222
|
+
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
223
|
+
|
224
|
+
when 'test'
|
225
|
+
FlowChat::Config.simulator_secret = "test_secret_#{Rails.application.secret_key_base}"
|
226
|
+
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
227
|
+
|
228
|
+
when 'staging'
|
229
|
+
FlowChat::Config.simulator_secret = ENV.fetch('FLOWCHAT_SIMULATOR_SECRET')
|
230
|
+
FlowChat::Config.whatsapp.message_handling_mode = :inline
|
231
|
+
|
232
|
+
when 'production'
|
233
|
+
FlowChat::Config.simulator_secret = ENV.fetch('FLOWCHAT_SIMULATOR_SECRET')
|
234
|
+
FlowChat::Config.whatsapp.message_handling_mode = :background
|
235
|
+
FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
## Testing Security Features
|
240
|
+
|
241
|
+
### Webhook Signature Validation Tests
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
test "webhook accepts valid signature" do
|
245
|
+
payload = valid_webhook_payload.to_json
|
246
|
+
signature = OpenSSL::HMAC.hexdigest(
|
247
|
+
OpenSSL::Digest.new("sha256"),
|
248
|
+
"your_app_secret",
|
249
|
+
payload
|
250
|
+
)
|
251
|
+
|
252
|
+
post "/whatsapp/webhook",
|
253
|
+
params: payload,
|
254
|
+
headers: {
|
255
|
+
"Content-Type" => "application/json",
|
256
|
+
"X-Hub-Signature-256" => "sha256=#{signature}"
|
257
|
+
}
|
258
|
+
|
259
|
+
assert_response :success
|
260
|
+
end
|
261
|
+
|
262
|
+
test "webhook rejects invalid signature" do
|
263
|
+
post "/whatsapp/webhook",
|
264
|
+
params: valid_webhook_payload,
|
265
|
+
headers: { "X-Hub-Signature-256" => "sha256=invalid_signature" }
|
266
|
+
|
267
|
+
assert_response :unauthorized
|
268
|
+
end
|
269
|
+
|
270
|
+
test "webhook rejects missing signature" do
|
271
|
+
post "/whatsapp/webhook", params: valid_webhook_payload
|
272
|
+
assert_response :unauthorized
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
### Simulator Authentication Tests
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
test "simulator requires valid authentication" do
|
280
|
+
post "/whatsapp/webhook", params: {
|
281
|
+
simulator_mode: true,
|
282
|
+
# ... webhook payload
|
283
|
+
}
|
284
|
+
|
285
|
+
assert_response :unauthorized # No valid simulator cookie
|
286
|
+
end
|
287
|
+
|
288
|
+
test "simulator accepts valid authentication" do
|
289
|
+
# Generate valid simulator cookie
|
290
|
+
timestamp = Time.now.to_i
|
291
|
+
message = "simulator:#{timestamp}"
|
292
|
+
signature = OpenSSL::HMAC.hexdigest(
|
293
|
+
OpenSSL::Digest.new("sha256"),
|
294
|
+
FlowChat::Config.simulator_secret,
|
295
|
+
message
|
296
|
+
)
|
297
|
+
|
298
|
+
post "/whatsapp/webhook",
|
299
|
+
params: { simulator_mode: true, /* ... */ },
|
300
|
+
cookies: { flowchat_simulator: "#{timestamp}:#{signature}" }
|
301
|
+
|
302
|
+
assert_response :success
|
303
|
+
end
|
304
|
+
```
|
305
|
+
|
306
|
+
## Troubleshooting
|
307
|
+
|
308
|
+
### Common Issues
|
309
|
+
|
310
|
+
**1. ConfigurationError: app_secret required**
|
311
|
+
|
312
|
+
```
|
313
|
+
WhatsApp app_secret is required for webhook signature validation.
|
314
|
+
```
|
315
|
+
|
316
|
+
**Solution**: Configure `WHATSAPP_APP_SECRET` environment variable or disable validation explicitly.
|
317
|
+
|
318
|
+
**2. Invalid webhook signature**
|
319
|
+
|
320
|
+
```
|
321
|
+
Invalid webhook signature received
|
322
|
+
```
|
323
|
+
|
324
|
+
**Solution**: Verify your `app_secret` matches your WhatsApp app configuration.
|
325
|
+
|
326
|
+
**3. Simulator authentication failed**
|
327
|
+
|
328
|
+
```
|
329
|
+
Invalid simulator cookie format
|
330
|
+
```
|
331
|
+
|
332
|
+
**Solution**: Ensure `FlowChat::Config.simulator_secret` is properly configured.
|
333
|
+
|
334
|
+
**4. Missing simulator secret**
|
335
|
+
|
336
|
+
```
|
337
|
+
Simulator secret not configured
|
338
|
+
```
|
339
|
+
|
340
|
+
**Solution**: Set `FLOWCHAT_SIMULATOR_SECRET` environment variable or configure in initializer.
|
341
|
+
|
342
|
+
### Debug Mode
|
343
|
+
|
344
|
+
Enable debug logging for security events:
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
# config/initializers/flowchat.rb
|
348
|
+
if Rails.env.development?
|
349
|
+
FlowChat::Config.logger.level = Logger::DEBUG
|
350
|
+
end
|
351
|
+
```
|
352
|
+
|
353
|
+
This will log security validation attempts and help troubleshoot configuration issues.
|
354
|
+
|
355
|
+
## Security Updates
|
356
|
+
|
357
|
+
This security system was introduced in FlowChat v2.0.0 and includes:
|
358
|
+
|
359
|
+
- HMAC-SHA256 webhook signature validation
|
360
|
+
- Secure simulator authentication with signed cookies
|
361
|
+
- Environment-specific security configuration
|
362
|
+
- Comprehensive error handling and logging
|
363
|
+
- Timing-attack resistant signature comparison
|
364
|
+
|
365
|
+
For the latest security updates and recommendations, check the FlowChat changelog and security advisories.
|
data/examples/initializer.rb
CHANGED
@@ -19,6 +19,22 @@ FlowChat::Config.cache = Rails.cache
|
|
19
19
|
# Configure logger (optional)
|
20
20
|
FlowChat::Config.logger = Rails.logger
|
21
21
|
|
22
|
+
# Configure simulator security (REQUIRED for simulator mode)
|
23
|
+
# This secret is used to authenticate simulator requests via signed cookies
|
24
|
+
case Rails.env
|
25
|
+
when 'development', 'test'
|
26
|
+
# Use Rails secret key with environment suffix for development
|
27
|
+
FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_#{Rails.env}"
|
28
|
+
when 'staging', 'production'
|
29
|
+
# Use environment variable for production security
|
30
|
+
FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
|
31
|
+
|
32
|
+
# Fail fast if simulator secret is not configured but might be needed
|
33
|
+
if FlowChat::Config.simulator_secret.blank?
|
34
|
+
Rails.logger.warn "FLOWCHAT_SIMULATOR_SECRET not configured. Simulator mode will be unavailable."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
22
38
|
# Configure USSD pagination (optional)
|
23
39
|
FlowChat::Config.ussd.pagination_page_size = 140
|
24
40
|
FlowChat::Config.ussd.pagination_back_option = "0"
|
@@ -28,4 +44,43 @@ FlowChat::Config.ussd.pagination_next_text = "More"
|
|
28
44
|
|
29
45
|
# Configure resumable sessions (optional)
|
30
46
|
FlowChat::Config.ussd.resumable_sessions_enabled = true
|
31
|
-
FlowChat::Config.ussd.resumable_sessions_timeout_seconds = 300 # 5 minutes
|
47
|
+
FlowChat::Config.ussd.resumable_sessions_timeout_seconds = 300 # 5 minutes
|
48
|
+
|
49
|
+
# Configure WhatsApp message handling mode based on environment
|
50
|
+
case Rails.env
|
51
|
+
when 'development'
|
52
|
+
# Development: Use simulator mode for easy testing
|
53
|
+
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
54
|
+
|
55
|
+
when 'test'
|
56
|
+
# Test: Use simulator mode for deterministic testing
|
57
|
+
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
58
|
+
|
59
|
+
when 'staging'
|
60
|
+
# Staging: Use inline mode for real WhatsApp testing
|
61
|
+
FlowChat::Config.whatsapp.message_handling_mode = :inline
|
62
|
+
|
63
|
+
when 'production'
|
64
|
+
# Production: Use background mode for high volume
|
65
|
+
FlowChat::Config.whatsapp.message_handling_mode = :background
|
66
|
+
FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
|
67
|
+
end
|
68
|
+
|
69
|
+
# Configure per-environment WhatsApp security
|
70
|
+
# Note: These are global defaults. You can override per-configuration in your controllers.
|
71
|
+
|
72
|
+
# Example of per-environment WhatsApp security configuration:
|
73
|
+
#
|
74
|
+
# For development/test: You might want to disable signature validation for easier testing
|
75
|
+
# For staging: Enable validation to match production behavior
|
76
|
+
# For production: Always enable validation for security
|
77
|
+
#
|
78
|
+
# Individual WhatsApp configurations can override these settings:
|
79
|
+
#
|
80
|
+
# config = FlowChat::Whatsapp::Configuration.new
|
81
|
+
# config.access_token = "your_token"
|
82
|
+
# config.app_secret = "your_app_secret" # Required for webhook validation
|
83
|
+
# config.skip_signature_validation = false # false = validate signatures (recommended)
|
84
|
+
#
|
85
|
+
# Development override example:
|
86
|
+
# config.skip_signature_validation = Rails.env.development? # Only skip in development
|
@@ -9,7 +9,7 @@ class MediaPromptFlow < FlowChat::Flow
|
|
9
9
|
def main_page
|
10
10
|
# ✅ Simple text input with attached image
|
11
11
|
# The prompt text becomes the image caption
|
12
|
-
|
12
|
+
app.screen(:feedback) do |prompt|
|
13
13
|
prompt.ask "What do you think of our new product?",
|
14
14
|
media: {
|
15
15
|
type: :image,
|
@@ -25,4 +25,3 @@ class MediaPromptFlow < FlowChat::Flow
|
|
25
25
|
}
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
@@ -7,11 +7,15 @@ class MultiTenantWhatsappController < ApplicationController
|
|
7
7
|
def webhook
|
8
8
|
# Determine tenant from subdomain, path, or other logic
|
9
9
|
tenant = determine_tenant(request)
|
10
|
-
|
10
|
+
|
11
11
|
# Get tenant-specific WhatsApp configuration
|
12
12
|
whatsapp_config = get_whatsapp_config_for_tenant(tenant)
|
13
|
-
|
14
|
-
|
13
|
+
|
14
|
+
# Enable simulator for local endpoint testing during development
|
15
|
+
# This allows testing different tenant endpoints via the simulator interface
|
16
|
+
enable_simulator = Rails.env.development? || Rails.env.staging?
|
17
|
+
|
18
|
+
processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: enable_simulator) do |config|
|
15
19
|
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
|
16
20
|
config.use_session_store FlowChat::Session::CacheSessionStore
|
17
21
|
end
|
@@ -26,41 +30,41 @@ class MultiTenantWhatsappController < ApplicationController
|
|
26
30
|
def determine_tenant(request)
|
27
31
|
# Option 1: From subdomain
|
28
32
|
return request.subdomain if request.subdomain.present?
|
29
|
-
|
33
|
+
|
30
34
|
# Option 2: From path
|
31
35
|
tenant_from_path = request.path.match(%r{^/whatsapp/(\w+)/})&.captures&.first
|
32
36
|
return tenant_from_path if tenant_from_path
|
33
|
-
|
37
|
+
|
34
38
|
# Option 3: From custom header
|
35
|
-
return request.headers[
|
36
|
-
|
39
|
+
return request.headers["X-Tenant-ID"] if request.headers["X-Tenant-ID"]
|
40
|
+
|
37
41
|
# Fallback to default
|
38
|
-
|
42
|
+
"default"
|
39
43
|
end
|
40
44
|
|
41
45
|
def get_whatsapp_config_for_tenant(tenant)
|
42
46
|
case tenant
|
43
|
-
when
|
47
|
+
when "acme_corp"
|
44
48
|
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
45
|
-
config.access_token = ENV[
|
46
|
-
config.phone_number_id = ENV[
|
47
|
-
config.verify_token = ENV[
|
48
|
-
config.app_id = ENV[
|
49
|
-
config.app_secret = ENV[
|
50
|
-
config.business_account_id = ENV[
|
49
|
+
config.access_token = ENV["ACME_WHATSAPP_ACCESS_TOKEN"]
|
50
|
+
config.phone_number_id = ENV["ACME_WHATSAPP_PHONE_NUMBER_ID"]
|
51
|
+
config.verify_token = ENV["ACME_WHATSAPP_VERIFY_TOKEN"]
|
52
|
+
config.app_id = ENV["ACME_WHATSAPP_APP_ID"]
|
53
|
+
config.app_secret = ENV["ACME_WHATSAPP_APP_SECRET"]
|
54
|
+
config.business_account_id = ENV["ACME_WHATSAPP_BUSINESS_ACCOUNT_ID"]
|
51
55
|
end
|
52
|
-
|
53
|
-
when
|
56
|
+
|
57
|
+
when "tech_startup"
|
54
58
|
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
55
|
-
config.access_token = ENV[
|
56
|
-
config.phone_number_id = ENV[
|
57
|
-
config.verify_token = ENV[
|
58
|
-
config.app_id = ENV[
|
59
|
-
config.app_secret = ENV[
|
60
|
-
config.business_account_id = ENV[
|
59
|
+
config.access_token = ENV["TECHSTARTUP_WHATSAPP_ACCESS_TOKEN"]
|
60
|
+
config.phone_number_id = ENV["TECHSTARTUP_WHATSAPP_PHONE_NUMBER_ID"]
|
61
|
+
config.verify_token = ENV["TECHSTARTUP_WHATSAPP_VERIFY_TOKEN"]
|
62
|
+
config.app_id = ENV["TECHSTARTUP_WHATSAPP_APP_ID"]
|
63
|
+
config.app_secret = ENV["TECHSTARTUP_WHATSAPP_APP_SECRET"]
|
64
|
+
config.business_account_id = ENV["TECHSTARTUP_WHATSAPP_BUSINESS_ACCOUNT_ID"]
|
61
65
|
end
|
62
|
-
|
63
|
-
when
|
66
|
+
|
67
|
+
when "retail_store"
|
64
68
|
# Load from database
|
65
69
|
tenant_config = WhatsappConfiguration.find_by(tenant: tenant)
|
66
70
|
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
@@ -71,7 +75,7 @@ class MultiTenantWhatsappController < ApplicationController
|
|
71
75
|
config.app_secret = tenant_config.app_secret
|
72
76
|
config.business_account_id = tenant_config.business_account_id
|
73
77
|
end
|
74
|
-
|
78
|
+
|
75
79
|
else
|
76
80
|
# Use default/global configuration
|
77
81
|
FlowChat::Whatsapp::Configuration.from_credentials
|
@@ -80,11 +84,11 @@ class MultiTenantWhatsappController < ApplicationController
|
|
80
84
|
|
81
85
|
def get_flow_for_tenant(tenant)
|
82
86
|
case tenant
|
83
|
-
when
|
87
|
+
when "acme_corp"
|
84
88
|
AcmeCorpFlow
|
85
|
-
when
|
89
|
+
when "tech_startup"
|
86
90
|
TechStartupFlow
|
87
|
-
when
|
91
|
+
when "retail_store"
|
88
92
|
RetailStoreFlow
|
89
93
|
else
|
90
94
|
WelcomeFlow # Default flow
|
@@ -99,7 +103,7 @@ class DatabaseWhatsappController < ApplicationController
|
|
99
103
|
def webhook
|
100
104
|
# Get account from business phone number or other identifier
|
101
105
|
business_account = find_business_account(params)
|
102
|
-
|
106
|
+
|
103
107
|
if business_account.nil?
|
104
108
|
return head :not_found
|
105
109
|
end
|
@@ -129,7 +133,7 @@ class DatabaseWhatsappController < ApplicationController
|
|
129
133
|
# 1. Phone number ID from webhook
|
130
134
|
# 2. Business account ID from webhook
|
131
135
|
# 3. Custom routing parameter
|
132
|
-
|
136
|
+
|
133
137
|
# Example: Find by phone number ID in webhook
|
134
138
|
phone_number_id = extract_phone_number_id_from_webhook(params)
|
135
139
|
BusinessAccount.find_by(whatsapp_phone_number_id: phone_number_id)
|
@@ -149,11 +153,11 @@ class EnvironmentWhatsappController < ApplicationController
|
|
149
153
|
def webhook
|
150
154
|
# Different configurations for different environments
|
151
155
|
whatsapp_config = case Rails.env
|
152
|
-
when
|
156
|
+
when "production"
|
153
157
|
production_whatsapp_config
|
154
|
-
when
|
158
|
+
when "staging"
|
155
159
|
staging_whatsapp_config
|
156
|
-
when
|
160
|
+
when "development"
|
157
161
|
development_whatsapp_config
|
158
162
|
else
|
159
163
|
FlowChat::Whatsapp::Configuration.from_credentials
|
@@ -171,34 +175,34 @@ class EnvironmentWhatsappController < ApplicationController
|
|
171
175
|
|
172
176
|
def production_whatsapp_config
|
173
177
|
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
174
|
-
config.access_token = ENV[
|
175
|
-
config.phone_number_id = ENV[
|
176
|
-
config.verify_token = ENV[
|
177
|
-
config.app_id = ENV[
|
178
|
-
config.app_secret = ENV[
|
179
|
-
config.business_account_id = ENV[
|
178
|
+
config.access_token = ENV["PROD_WHATSAPP_ACCESS_TOKEN"]
|
179
|
+
config.phone_number_id = ENV["PROD_WHATSAPP_PHONE_NUMBER_ID"]
|
180
|
+
config.verify_token = ENV["PROD_WHATSAPP_VERIFY_TOKEN"]
|
181
|
+
config.app_id = ENV["PROD_WHATSAPP_APP_ID"]
|
182
|
+
config.app_secret = ENV["PROD_WHATSAPP_APP_SECRET"]
|
183
|
+
config.business_account_id = ENV["PROD_WHATSAPP_BUSINESS_ACCOUNT_ID"]
|
180
184
|
end
|
181
185
|
end
|
182
186
|
|
183
187
|
def staging_whatsapp_config
|
184
188
|
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
185
|
-
config.access_token = ENV[
|
186
|
-
config.phone_number_id = ENV[
|
187
|
-
config.verify_token = ENV[
|
188
|
-
config.app_id = ENV[
|
189
|
-
config.app_secret = ENV[
|
190
|
-
config.business_account_id = ENV[
|
189
|
+
config.access_token = ENV["STAGING_WHATSAPP_ACCESS_TOKEN"]
|
190
|
+
config.phone_number_id = ENV["STAGING_WHATSAPP_PHONE_NUMBER_ID"]
|
191
|
+
config.verify_token = ENV["STAGING_WHATSAPP_VERIFY_TOKEN"]
|
192
|
+
config.app_id = ENV["STAGING_WHATSAPP_APP_ID"]
|
193
|
+
config.app_secret = ENV["STAGING_WHATSAPP_APP_SECRET"]
|
194
|
+
config.business_account_id = ENV["STAGING_WHATSAPP_BUSINESS_ACCOUNT_ID"]
|
191
195
|
end
|
192
196
|
end
|
193
197
|
|
194
198
|
def development_whatsapp_config
|
195
199
|
FlowChat::Whatsapp::Configuration.new.tap do |config|
|
196
|
-
config.access_token = ENV[
|
197
|
-
config.phone_number_id = ENV[
|
198
|
-
config.verify_token = ENV[
|
199
|
-
config.app_id = ENV[
|
200
|
-
config.app_secret = ENV[
|
201
|
-
config.business_account_id = ENV[
|
200
|
+
config.access_token = ENV["DEV_WHATSAPP_ACCESS_TOKEN"]
|
201
|
+
config.phone_number_id = ENV["DEV_WHATSAPP_PHONE_NUMBER_ID"]
|
202
|
+
config.verify_token = ENV["DEV_WHATSAPP_VERIFY_TOKEN"]
|
203
|
+
config.app_id = ENV["DEV_WHATSAPP_APP_ID"]
|
204
|
+
config.app_secret = ENV["DEV_WHATSAPP_APP_SECRET"]
|
205
|
+
config.business_account_id = ENV["DEV_WHATSAPP_BUSINESS_ACCOUNT_ID"]
|
202
206
|
end
|
203
207
|
end
|
204
208
|
end
|
@@ -232,13 +236,13 @@ end
|
|
232
236
|
# constraints subdomain: /\w+/ do
|
233
237
|
# post '/whatsapp/webhook', to: 'multi_tenant_whatsapp#webhook'
|
234
238
|
# end
|
235
|
-
#
|
236
|
-
# # Path-based routing
|
239
|
+
#
|
240
|
+
# # Path-based routing
|
237
241
|
# post '/whatsapp/:tenant/webhook', to: 'multi_tenant_whatsapp#webhook'
|
238
|
-
#
|
242
|
+
#
|
239
243
|
# # Environment-specific
|
240
244
|
# post '/whatsapp/env/webhook', to: 'environment_whatsapp#webhook'
|
241
|
-
#
|
245
|
+
#
|
242
246
|
# # Custom endpoint
|
243
247
|
# post '/whatsapp/custom/webhook', to: 'custom_whatsapp#webhook'
|
244
|
-
# end
|
248
|
+
# end
|