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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2046dd747c4cf51ac0d7e234515660e7245e55327647299bf7f132bf4aae78e8
4
- data.tar.gz: fd9229c34306586e999a0fbaab5e2a8e90b57ceed353eba6fe6b3e6230105eb1
3
+ metadata.gz: b8c433f50fb2876095dad4e8adbf87547a04c0bdef08f1db15f545a5f3539aa2
4
+ data.tar.gz: 3af1f877767be983922d340cd0249c3bfbf897e6e544ffaae3ea2bb37e00bd82
5
5
  SHA512:
6
- metadata.gz: 8472c56f7159e01aea47fbb5add8613d6f9b0158c95de0bca11c149fd7e8c15e2d8e67fa69b3bd97ccef545c7e94a4d580bd2f4e604d54ca6914948c62a3c6bd
7
- data.tar.gz: 3d78511c0dd9b9d21c9bf26c044fe304d88f246f243ce7a81101bc9ea42172cc9127ba6db150f7e4923060fb3e0d6c1140b1d35076c56b8b1e9838e2fabe3ab4
6
+ metadata.gz: 7010f9de4eb7b82981659da7e3a39896a03a88fb94aa6781e2e2379cda802568626c210b303e71541fc0b9b907e42d4df29253b12da9482902f1b786b531c402
7
+ data.tar.gz: 42a5c84dc33246476af82617e1629c10de17caf44175ecc33afbda90f8991d5ae16534dab195bf7154f32fa40f43cecb562ceffddc0564e6ed991d8905aca470
data/Gemfile.lock CHANGED
@@ -34,7 +34,7 @@ GIT
34
34
  PATH
35
35
  remote: .
36
36
  specs:
37
- spree_cm_commissioner (2.5.16.pre.pre9)
37
+ spree_cm_commissioner (2.5.16)
38
38
  activerecord-multi-tenant
39
39
  activerecord_json_validator (~> 2.1, >= 2.1.3)
40
40
  aws-sdk-cloudfront
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,9 @@
1
+ module SpreeCmCommissioner
2
+ class CleanupExpiredAccessTokensJob < SpreeCmCommissioner::ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform
6
+ SpreeCmCommissioner::CleanupExpiredAccessTokens.new.call
7
+ end
8
+ end
9
+ 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
@@ -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
+
@@ -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
@@ -0,0 +1,5 @@
1
+ class AddRoleTypeToSpreeRoles < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :spree_roles, :role_type, :integer, null: false, default: 0, if_not_exists: true
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class AddIndexToSpreeOauthAccessTokensCreatedAt < ActiveRecord::Migration[7.0]
2
+ def change
3
+ unless index_exists?(:spree_oauth_access_tokens, :created_at)
4
+ add_index :spree_oauth_access_tokens, :created_at
5
+ end
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  module SpreeCmCommissioner
2
- VERSION = '2.5.16-pre9'.freeze
2
+ VERSION = '2.5.16'.freeze
3
3
 
4
4
  module_function
5
5
 
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.pre.pre9
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-19 00:00:00.000000000 Z
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: 1.3.1
3287
+ version: '0'
3280
3288
  requirements:
3281
3289
  - none
3282
3290
  rubygems_version: 3.4.1