spree_cm_commissioner 2.5.16.pre.pre9 → 2.5.16
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/Gemfile.lock +1 -1
- data/README.md +29 -0
- data/app/controllers/concerns/spree_cm_commissioner/turnstile_protectable.rb +42 -0
- data/app/interactors/spree_cm_commissioner/turnstile_token_validator.rb +55 -0
- data/app/jobs/spree_cm_commissioner/cleanup_expired_access_tokens_job.rb +9 -0
- data/app/models/spree_cm_commissioner/agency.rb +12 -0
- data/app/models/spree_cm_commissioner/role_decorator.rb +3 -0
- data/app/serializers/spree/v2/tenant/line_item_serializer.rb +1 -1
- data/app/services/spree_cm_commissioner/cleanup_expired_access_tokens.rb +52 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/km.yml +5 -0
- data/db/migrate/20260318081516_create_cm_agencies.rb +18 -0
- data/db/migrate/20260319090000_add_role_type_to_spree_roles.rb +5 -0
- data/db/migrate/20260320103313_add_index_to_spree_oauth_access_tokens_created_at.rb +7 -0
- data/lib/spree_cm_commissioner/version.rb +1 -1
- metadata +12 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8c433f50fb2876095dad4e8adbf87547a04c0bdef08f1db15f545a5f3539aa2
|
|
4
|
+
data.tar.gz: 3af1f877767be983922d340cd0249c3bfbf897e6e544ffaae3ea2bb37e00bd82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7010f9de4eb7b82981659da7e3a39896a03a88fb94aa6781e2e2379cda802568626c210b303e71541fc0b9b907e42d4df29253b12da9482902f1b786b531c402
|
|
7
|
+
data.tar.gz: 42a5c84dc33246476af82617e1629c10de17caf44175ecc33afbda90f8991d5ae16534dab195bf7154f32fa40f43cecb562ceffddc0564e6ed991d8905aca470
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -153,8 +153,37 @@ PIN_CODE_DEBUG_NOTIFIY_TELEGRAM_ENABLE="yes"
|
|
|
153
153
|
EXCEPTION_NOTIFY_ENABLE="yes" # yes or no
|
|
154
154
|
EXCEPTION_TELEGRAM_BOT_TOKEN=""
|
|
155
155
|
EXCEPTION_NOTIFIER_TELEGRAM_CHANNEL_ID=""
|
|
156
|
+
|
|
157
|
+
TURNSTILE_SECRET_KEY="" # Cloudflare Turnstile secret key (server-side verification)
|
|
156
158
|
```
|
|
157
159
|
|
|
160
|
+
### Cloudflare Turnstile
|
|
161
|
+
|
|
162
|
+
Cloudflare Turnstile is used to protect sensitive API endpoints from bots. The server-side validation is handled by `SpreeCmCommissioner::TurnstileTokenValidator` and the concern `SpreeCmCommissioner::TurnstileProtectable`.
|
|
163
|
+
|
|
164
|
+
**Protected endpoints:**
|
|
165
|
+
|
|
166
|
+
- `POST /api/v2/storefront/pin_code_generators`
|
|
167
|
+
- `POST /api/v2/storefront/pin_code_otp_generators`
|
|
168
|
+
- `POST /api/v2/storefront/user_registration_with_pin_codes`
|
|
169
|
+
- `PUT /api/v2/storefront/reset_passwords`
|
|
170
|
+
- `PATCH /api/v2/storefront/checkout/complete`
|
|
171
|
+
|
|
172
|
+
**How it works:**
|
|
173
|
+
|
|
174
|
+
1. The client sends a Turnstile token via the `X-Turnstile-Token` HTTP header
|
|
175
|
+
2. `TurnstileProtectable` reads the header and calls `TurnstileTokenValidator`
|
|
176
|
+
3. The validator POSTs to Cloudflare's siteverify API with the token and `TURNSTILE_SECRET_KEY`
|
|
177
|
+
4. Returns 403 if verification fails
|
|
178
|
+
|
|
179
|
+
**Configuration:**
|
|
180
|
+
|
|
181
|
+
1. Set `TURNSTILE_SECRET_KEY` in `.env` (obtained from [Cloudflare Turnstile dashboard](https://dash.cloudflare.com/))
|
|
182
|
+
2. For local development testing, use Cloudflare's test keys:
|
|
183
|
+
- Always pass: `1x0000000000000000000000000000000AA`
|
|
184
|
+
- Always fail: `2x0000000000000000000000000000000AA`
|
|
185
|
+
- Token spent: `3x0000000000000000000000000000000AA`
|
|
186
|
+
|
|
158
187
|
## JSON Format Examples For Store and Tenant Preferences
|
|
159
188
|
|
|
160
189
|
### Asset Links Format
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
module TurnstileProtectable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# Verify the Turnstile token from the X-Turnstile-Token header.
|
|
8
|
+
# Renders 403 and halts the request if verification fails.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# before_action -> { verify_turnstile! }, only: :create
|
|
12
|
+
#
|
|
13
|
+
def verify_turnstile!
|
|
14
|
+
token = request.headers['X-Turnstile-Token']
|
|
15
|
+
|
|
16
|
+
# Skip verification if no token and Turnstile is not enforced
|
|
17
|
+
return if token.blank? && turnstile_optional?
|
|
18
|
+
|
|
19
|
+
result = TurnstileTokenValidator.call(
|
|
20
|
+
token: token,
|
|
21
|
+
remote_ip: request.remote_ip
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return unless result.failure?
|
|
25
|
+
|
|
26
|
+
render json: {
|
|
27
|
+
errors: [{
|
|
28
|
+
status: '403',
|
|
29
|
+
title: 'Forbidden',
|
|
30
|
+
detail: result.message
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}, status: :forbidden
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Override in controllers to make Turnstile optional for specific actions.
|
|
37
|
+
# Default: required (returns false).
|
|
38
|
+
def turnstile_optional?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class TurnstileTokenValidator < BaseInteractor
|
|
3
|
+
SITE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'.freeze
|
|
4
|
+
|
|
5
|
+
delegate :token, :remote_ip, to: :context
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
context.fail!(message: I18n.t('turnstile.token_missing')) if token.blank?
|
|
9
|
+
|
|
10
|
+
response = verify_token
|
|
11
|
+
body = JSON.parse(response.body)
|
|
12
|
+
|
|
13
|
+
unless body['success']
|
|
14
|
+
error_codes = body['error-codes']&.join(', ') || 'unknown'
|
|
15
|
+
Rails.logger.warn(
|
|
16
|
+
"Turnstile verification failed: #{error_codes} " \
|
|
17
|
+
"from IP #{remote_ip} | hostname=#{body['hostname']}"
|
|
18
|
+
)
|
|
19
|
+
context.fail!(message: I18n.t('turnstile.verification_failed'))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context.challenge_ts = body['challenge_ts']
|
|
23
|
+
context.hostname = body['hostname']
|
|
24
|
+
context.action = body['action']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def verify_token
|
|
30
|
+
connection.post do |req|
|
|
31
|
+
req.body = {
|
|
32
|
+
secret: secret_key,
|
|
33
|
+
response: token,
|
|
34
|
+
remoteip: remote_ip
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
|
|
38
|
+
Rails.logger.error("Turnstile API error: #{e.class} - #{e.message}")
|
|
39
|
+
context.fail!(message: I18n.t('turnstile.service_unavailable'))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def connection
|
|
43
|
+
@connection ||= Faraday.new(url: SITE_VERIFY_URL) do |f|
|
|
44
|
+
f.request :url_encoded
|
|
45
|
+
f.options.open_timeout = 3
|
|
46
|
+
f.options.timeout = 5
|
|
47
|
+
f.adapter Faraday.default_adapter
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def secret_key
|
|
52
|
+
ENV.fetch('TURNSTILE_SECRET_KEY')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class Agency < Base
|
|
3
|
+
belongs_to :vendor, class_name: 'Spree::Vendor', optional: false
|
|
4
|
+
belongs_to :agency_category, class_name: 'Spree::Taxon', optional: false
|
|
5
|
+
|
|
6
|
+
enum approval_status: { pending: 0, approved: 1, rejected: 2 }
|
|
7
|
+
enum status: { inactive: 0, active: 1 }
|
|
8
|
+
enum paid_type: { postpaid: 0, prepaid: 1 }
|
|
9
|
+
|
|
10
|
+
validates :name, presence: true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module SpreeCmCommissioner
|
|
2
2
|
module RoleDecorator
|
|
3
3
|
def self.prepended(base)
|
|
4
|
+
base.enum role_type: { internal: 0, external: 1 }
|
|
5
|
+
|
|
4
6
|
base.has_many :role_permissions, class_name: 'SpreeCmCommissioner::RolePermission'
|
|
5
7
|
base.has_many :permissions, through: :role_permissions, class_name: 'SpreeCmCommissioner::Permission'
|
|
6
8
|
|
|
@@ -10,6 +12,7 @@ module SpreeCmCommissioner
|
|
|
10
12
|
base.scope :filter_by_vendor, lambda { |vendor|
|
|
11
13
|
where(vendor_id: vendor)
|
|
12
14
|
}
|
|
15
|
+
base.scope :filter_external, -> { where(role_type: :external) }
|
|
13
16
|
|
|
14
17
|
base.accepts_nested_attributes_for :role_permissions, allow_destroy: true
|
|
15
18
|
|
|
@@ -11,7 +11,7 @@ module Spree
|
|
|
11
11
|
:display_amount, :number, :qr_data, :kyc, :kyc_fields, :remaining_total_guests, :number_of_guests,
|
|
12
12
|
:completion_steps, :available_social_contact_platforms, :allow_anonymous_booking,
|
|
13
13
|
:discontinue_on, :high_demand, :jwt_token, :pre_tax_amount, :display_pre_tax_amount, :public_metadata,
|
|
14
|
-
:direction, :trip_id, :boarding_trip_stop_id, :drop_off_trip_stop_id
|
|
14
|
+
:direction, :trip_id, :boarding_trip_stop_id, :drop_off_trip_stop_id, :passenger_count, :remark
|
|
15
15
|
|
|
16
16
|
attribute :required_self_check_in_location, &:required_self_check_in_location?
|
|
17
17
|
attribute :allowed_self_check_in, &:allowed_self_check_in?
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module SpreeCmCommissioner
|
|
2
|
+
class CleanupExpiredAccessTokens
|
|
3
|
+
prepend ::Spree::ServiceModule::Base
|
|
4
|
+
|
|
5
|
+
BATCH_SIZE = 1000
|
|
6
|
+
EXPIRATION_THRESHOLD_DAYS = 1
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
cutoff_date = EXPIRATION_THRESHOLD_DAYS.days.ago
|
|
10
|
+
total_deleted = 0
|
|
11
|
+
|
|
12
|
+
Spree::OauthAccessToken
|
|
13
|
+
.where.not(expires_in: nil)
|
|
14
|
+
.where('created_at + make_interval(secs => expires_in) < ?', cutoff_date)
|
|
15
|
+
.in_batches(of: BATCH_SIZE) do |relation|
|
|
16
|
+
deleted_count = relation.delete_all
|
|
17
|
+
total_deleted += deleted_count
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
log_cleanup_result(total_deleted, cutoff_date)
|
|
21
|
+
success(total_deleted: total_deleted, cutoff_date: cutoff_date, batch_size: BATCH_SIZE, expiration_threshold_days: EXPIRATION_THRESHOLD_DAYS)
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
log_error(e)
|
|
24
|
+
failure(nil, e.message)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def log_cleanup_result(total_deleted, cutoff_date)
|
|
30
|
+
CmAppLogger.log(
|
|
31
|
+
label: 'SpreeCmCommissioner::CleanupExpiredAccessTokens completed',
|
|
32
|
+
data: {
|
|
33
|
+
total_deleted: total_deleted,
|
|
34
|
+
cutoff_date: cutoff_date,
|
|
35
|
+
batch_size: BATCH_SIZE,
|
|
36
|
+
expiration_threshold_days: EXPIRATION_THRESHOLD_DAYS
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def log_error(error)
|
|
42
|
+
CmAppLogger.error(
|
|
43
|
+
label: 'SpreeCmCommissioner::CleanupExpiredAccessTokens error',
|
|
44
|
+
data: {
|
|
45
|
+
error_class: error.class.name,
|
|
46
|
+
error_message: error.message,
|
|
47
|
+
backtrace: error.backtrace&.first(5)&.join("\n")
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/config/locales/en.yml
CHANGED
|
@@ -718,3 +718,8 @@ en:
|
|
|
718
718
|
after_validity: "Selected date is after ticket validity (expires %{date})"
|
|
719
719
|
before_validity: "Selected date is before ticket validity (starts %{date})"
|
|
720
720
|
trip_not_available: "Trip is not available for redemption"
|
|
721
|
+
turnstile:
|
|
722
|
+
token_missing: "Verification token is missing"
|
|
723
|
+
verification_failed: "Human verification failed"
|
|
724
|
+
service_unavailable: "Verification service is temporarily unavailable"
|
|
725
|
+
|
data/config/locales/km.yml
CHANGED
|
@@ -571,3 +571,8 @@ km:
|
|
|
571
571
|
"Previous Period": "រយៈពេលមុន"
|
|
572
572
|
"Change": "បំលាស់ប្តូរ"
|
|
573
573
|
"Change %": "ភាគរយនៃបំលាស់ប្តូរ"
|
|
574
|
+
turnstile:
|
|
575
|
+
token_missing: "សញ្ញាណផ្ទៀងផ្ទាត់គឺបាត់ខ្លួច"
|
|
576
|
+
verification_failed: "ការផ្ទៀងផ្ទាត់មនុស្សបានបរាជ័យ"
|
|
577
|
+
service_unavailable: "សេវាកម្មផ្ទៀងផ្ទាត់មិនមាននៅលើក"
|
|
578
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class CreateCmAgencies < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :cm_agencies, if_not_exists: true do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.references :vendor, null: false, foreign_key: { to_table: :spree_vendors }
|
|
6
|
+
t.references :agency_category, null: false, foreign_key: { to_table: :spree_taxons }
|
|
7
|
+
t.string :address
|
|
8
|
+
t.string :contact_person
|
|
9
|
+
t.string :phone
|
|
10
|
+
t.string :email
|
|
11
|
+
t.integer :approval_status, default: 0
|
|
12
|
+
t.integer :status, default: 0
|
|
13
|
+
t.integer :paid_type, default: 0
|
|
14
|
+
t.index [:vendor_id, :agency_category_id], name: 'index_cm_agencies_on_vendor_id_and_agency_category_id'
|
|
15
|
+
t.timestamps
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_cm_commissioner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.5.16
|
|
4
|
+
version: 2.5.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: spree
|
|
@@ -826,6 +826,7 @@ files:
|
|
|
826
826
|
- app/controllers/concerns/spree_cm_commissioner/content_cachable.rb
|
|
827
827
|
- app/controllers/concerns/spree_cm_commissioner/exception_notificable.rb
|
|
828
828
|
- app/controllers/concerns/spree_cm_commissioner/order_concern.rb
|
|
829
|
+
- app/controllers/concerns/spree_cm_commissioner/turnstile_protectable.rb
|
|
829
830
|
- app/controllers/concerns/spree_cm_commissioner/waiting_room_authorization.rb
|
|
830
831
|
- app/controllers/spree/admin/account_deletions_controller.rb
|
|
831
832
|
- app/controllers/spree/admin/base_controller_decorator.rb
|
|
@@ -1278,6 +1279,7 @@ files:
|
|
|
1278
1279
|
- app/interactors/spree_cm_commissioner/telegram_web_app_vendor_user_checker.rb
|
|
1279
1280
|
- app/interactors/spree_cm_commissioner/transactional_email_sender.rb
|
|
1280
1281
|
- app/interactors/spree_cm_commissioner/trip_stops_creator.rb
|
|
1282
|
+
- app/interactors/spree_cm_commissioner/turnstile_token_validator.rb
|
|
1281
1283
|
- app/interactors/spree_cm_commissioner/unique_device_token_cron_executor.rb
|
|
1282
1284
|
- app/interactors/spree_cm_commissioner/update_payment_gateway_status.rb
|
|
1283
1285
|
- app/interactors/spree_cm_commissioner/user_contact_updater.rb
|
|
@@ -1317,6 +1319,7 @@ files:
|
|
|
1317
1319
|
- app/jobs/spree_cm_commissioner/application_job_decorator.rb
|
|
1318
1320
|
- app/jobs/spree_cm_commissioner/application_unique_job.rb
|
|
1319
1321
|
- app/jobs/spree_cm_commissioner/chatrace_order_creator_job.rb
|
|
1322
|
+
- app/jobs/spree_cm_commissioner/cleanup_expired_access_tokens_job.rb
|
|
1320
1323
|
- app/jobs/spree_cm_commissioner/completion_steps/regenerate_for_line_items_job.rb
|
|
1321
1324
|
- app/jobs/spree_cm_commissioner/customer_content_notification_creator_job.rb
|
|
1322
1325
|
- app/jobs/spree_cm_commissioner/customer_notification_cron_job.rb
|
|
@@ -1430,6 +1433,7 @@ files:
|
|
|
1430
1433
|
- app/models/spree_cm_commissioner/address_decorator.rb
|
|
1431
1434
|
- app/models/spree_cm_commissioner/adjustable/adjuster/pricing_action.rb
|
|
1432
1435
|
- app/models/spree_cm_commissioner/adjustment_decorator.rb
|
|
1436
|
+
- app/models/spree_cm_commissioner/agency.rb
|
|
1433
1437
|
- app/models/spree_cm_commissioner/asset.rb
|
|
1434
1438
|
- app/models/spree_cm_commissioner/back_image.rb
|
|
1435
1439
|
- app/models/spree_cm_commissioner/base.rb
|
|
@@ -2032,6 +2036,7 @@ files:
|
|
|
2032
2036
|
- app/services/spree_cm_commissioner/check_ins/destroy_bulk.rb
|
|
2033
2037
|
- app/services/spree_cm_commissioner/checkout/advance_decorator.rb
|
|
2034
2038
|
- app/services/spree_cm_commissioner/checkout/update_decorator.rb
|
|
2039
|
+
- app/services/spree_cm_commissioner/cleanup_expired_access_tokens.rb
|
|
2035
2040
|
- app/services/spree_cm_commissioner/completion_steps/mark_line_item_as_completed.rb
|
|
2036
2041
|
- app/services/spree_cm_commissioner/completion_steps/regenerate_for_line_items.rb
|
|
2037
2042
|
- app/services/spree_cm_commissioner/exports/export_order_csv_service.rb
|
|
@@ -3087,6 +3092,9 @@ files:
|
|
|
3087
3092
|
- db/migrate/20260225120100_create_cm_payment_references.rb
|
|
3088
3093
|
- db/migrate/20260302062115_add_import_by_id_to_cm_imports.rb
|
|
3089
3094
|
- db/migrate/20260311105437_add_tenant_to_cm_sms_logs.rb
|
|
3095
|
+
- db/migrate/20260318081516_create_cm_agencies.rb
|
|
3096
|
+
- db/migrate/20260319090000_add_role_type_to_spree_roles.rb
|
|
3097
|
+
- db/migrate/20260320103313_add_index_to_spree_oauth_access_tokens_created_at.rb
|
|
3090
3098
|
- docker-compose.yml
|
|
3091
3099
|
- docs/api/scoped-access-token-endpoints.md
|
|
3092
3100
|
- docs/option_types/attr_types.md
|
|
@@ -3274,9 +3282,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
3274
3282
|
version: '2.7'
|
|
3275
3283
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
3276
3284
|
requirements:
|
|
3277
|
-
- - "
|
|
3285
|
+
- - ">="
|
|
3278
3286
|
- !ruby/object:Gem::Version
|
|
3279
|
-
version:
|
|
3287
|
+
version: '0'
|
|
3280
3288
|
requirements:
|
|
3281
3289
|
- none
|
|
3282
3290
|
rubygems_version: 3.4.1
|