spree_otp_auth 0.2.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +42 -0
- data/LICENSE +29 -0
- data/README.md +190 -0
- data/app/controllers/spree/admin/otp_configurations_controller.rb +50 -0
- data/app/controllers/spree/api/v3/store/otp_controller.rb +301 -0
- data/app/mailers/spree/otp_mailer.rb +57 -0
- data/app/models/spree/otp_attempt.rb +82 -0
- data/app/models/spree/otp_configuration.rb +90 -0
- data/app/models/spree/otp_store_decorator.rb +15 -0
- data/app/models/spree/otp_user_decorator.rb +59 -0
- data/app/services/spree/otp/code_sent_payload.rb +55 -0
- data/app/services/spree/otp/delivery.rb +77 -0
- data/app/services/spree/otp/fallback.rb +86 -0
- data/app/services/spree/otp/generate.rb +198 -0
- data/app/services/spree/otp/identity.rb +81 -0
- data/app/services/spree/otp/marketing_consent.rb +35 -0
- data/app/services/spree/otp/rate_limiter.rb +82 -0
- data/app/services/spree/otp/resend.rb +27 -0
- data/app/services/spree/otp/verify.rb +173 -0
- data/app/subscribers/spree/otp/delivery_failed_subscriber.rb +43 -0
- data/app/views/spree/admin/otp_configurations/edit.html.erb +141 -0
- data/app/views/spree/otp_mailer/send_otp.html.erb +100 -0
- data/app/views/spree/otp_mailer/send_otp.text.erb +8 -0
- data/config/locales/en.yml +39 -0
- data/config/routes.rb +22 -0
- data/db/migrate/20260927000001_create_spree_otp_configurations.spree_otp_auth.rb +20 -0
- data/db/migrate/20260927000002_create_spree_otp_attempts.spree_otp_auth.rb +26 -0
- data/db/migrate/20260927000003_add_otp_fields_to_spree_users.spree_otp_auth.rb +12 -0
- data/db/migrate/20260927000004_update_otp_schema_for_mandatory_mobile_and_templates.spree_otp_auth.rb +28 -0
- data/db/migrate/20261018000001_add_gupshup_fields_to_spree_otp_configurations.spree_otp_auth.rb +18 -0
- data/db/migrate/20261018000002_add_delivery_fields_to_spree_otp_attempts.spree_otp_auth.rb +18 -0
- data/db/migrate/20261018000003_add_email_marketing_decided_at_to_spree_users.spree_otp_auth.rb +30 -0
- data/db/migrate/20261019000004_add_delivery_message_ref_to_spree_otp_attempts.spree_otp_auth.rb +8 -0
- data/lib/generators/spree_otp_auth/install/install_generator.rb +27 -0
- data/lib/spree_otp_auth/engine.rb +44 -0
- data/lib/spree_otp_auth/version.rb +5 -0
- data/lib/spree_otp_auth.rb +28 -0
- data/spree_otp_auth.gemspec +39 -0
- metadata +167 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
en:
|
|
2
|
+
spree:
|
|
3
|
+
admin:
|
|
4
|
+
tab:
|
|
5
|
+
otp_authentication: "OTP Authentication"
|
|
6
|
+
otp_authentication: "OTP Authentication"
|
|
7
|
+
otp_auth:
|
|
8
|
+
admin:
|
|
9
|
+
title: "OTP Authentication Settings"
|
|
10
|
+
update_success: "OTP authentication settings updated successfully."
|
|
11
|
+
channel_header: "Channel & Timing Configuration"
|
|
12
|
+
active_channel: "Active OTP Channel"
|
|
13
|
+
otp_length: "OTP Length"
|
|
14
|
+
otp_expiry_seconds: "OTP Expiry (seconds)"
|
|
15
|
+
resend_cooldown_seconds: "Resend Cooldown (seconds)"
|
|
16
|
+
fallback_delay_seconds: "WhatsApp Fallback Delay (seconds)"
|
|
17
|
+
msg91_header: "MSG91 Provider Credentials & DLT Templates"
|
|
18
|
+
msg91_auth_key: "MSG91 Auth Key"
|
|
19
|
+
msg91_sender_id: "Sender ID (Header)"
|
|
20
|
+
msg91_sms_template_id: "SMS Template ID"
|
|
21
|
+
msg91_whatsapp_template_id: "WhatsApp Template ID"
|
|
22
|
+
gupshup_header: "WhatsApp via Gupshup"
|
|
23
|
+
gupshup_source_number: "Business WhatsApp number"
|
|
24
|
+
gupshup_app_name: "Gupshup app name"
|
|
25
|
+
gupshup_app_id: "Gupshup app ID"
|
|
26
|
+
gupshup_template_name: "Template name"
|
|
27
|
+
gupshup_template_id: "Template ID (optional)"
|
|
28
|
+
gupshup_api_key_status: "API key"
|
|
29
|
+
gupshup_webhook_url: "Delivery callback URL"
|
|
30
|
+
email_backup: "Email backup"
|
|
31
|
+
test_whatsapp: "Send test WhatsApp"
|
|
32
|
+
errors:
|
|
33
|
+
requires_email: "Please enter your email address to continue."
|
|
34
|
+
invalid_email: "Please enter a valid email address."
|
|
35
|
+
email_linked_to_other_mobile: "This email is already linked to another mobile number. Please sign in with the mobile number linked to that email."
|
|
36
|
+
invalid_email_code: "The code sent to your email is incorrect or expired."
|
|
37
|
+
email_fallback_disabled: "Email codes are not available right now."
|
|
38
|
+
no_email_on_account: "There is no email on this account to send a code to."
|
|
39
|
+
whatsapp_not_ready: "WhatsApp can't be switched on yet."
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Spree::Core::Engine.add_routes do
|
|
4
|
+
namespace :api, defaults: { format: 'json' } do
|
|
5
|
+
namespace :v3 do
|
|
6
|
+
namespace :store do
|
|
7
|
+
scope :otp, controller: 'otp' do
|
|
8
|
+
post :request, action: :request_otp
|
|
9
|
+
post :verify, action: :verify
|
|
10
|
+
post :resend, action: :resend
|
|
11
|
+
post :fallback, action: :fallback
|
|
12
|
+
post :status, action: :delivery_status
|
|
13
|
+
patch 'customer/mobile', action: :update_mobile
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
namespace :admin do
|
|
20
|
+
resource :otp_configuration, only: [:edit, :update]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSpreeOtpConfigurations < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
create_table :spree_otp_configurations do |t|
|
|
6
|
+
t.references :store, null: false, foreign_key: { to_table: :spree_stores }, index: { unique: true }
|
|
7
|
+
t.string :active_channel, null: false, default: 'email'
|
|
8
|
+
t.integer :otp_length, null: false, default: 6
|
|
9
|
+
t.integer :otp_expiry_seconds, null: false, default: 600
|
|
10
|
+
t.integer :resend_cooldown_seconds, null: false, default: 30
|
|
11
|
+
t.integer :fallback_delay_seconds, null: false, default: 20
|
|
12
|
+
t.text :msg91_auth_key
|
|
13
|
+
t.string :msg91_sms_template_id
|
|
14
|
+
t.string :msg91_whatsapp_template_id
|
|
15
|
+
t.string :msg91_sender_id
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSpreeOtpAttempts < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
create_table :spree_otp_attempts do |t|
|
|
6
|
+
t.references :store, null: false, foreign_key: { to_table: :spree_stores }, index: true
|
|
7
|
+
t.references :user, foreign_key: { to_table: :spree_users }, index: true
|
|
8
|
+
t.string :identifier, null: false
|
|
9
|
+
t.string :channel, null: false
|
|
10
|
+
t.string :code_digest, null: false
|
|
11
|
+
t.string :salt, null: false
|
|
12
|
+
t.integer :attempts_count, null: false, default: 0
|
|
13
|
+
t.integer :max_attempts, null: false, default: 5
|
|
14
|
+
t.datetime :expires_at, null: false
|
|
15
|
+
t.datetime :verified_at
|
|
16
|
+
t.string :ip_address
|
|
17
|
+
t.string :user_agent
|
|
18
|
+
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_index :spree_otp_attempts, [:identifier, :expires_at]
|
|
23
|
+
add_index :spree_otp_attempts, :verified_at
|
|
24
|
+
add_index :spree_otp_attempts, :created_at
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddOtpFieldsToSpreeUsers < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :spree_users, :auth_identifier, :string unless column_exists?(:spree_users, :auth_identifier)
|
|
6
|
+
add_column :spree_users, :auth_channel, :string, default: 'email', null: false unless column_exists?(:spree_users, :auth_channel)
|
|
7
|
+
add_column :spree_users, :mobile_number, :string unless column_exists?(:spree_users, :mobile_number)
|
|
8
|
+
|
|
9
|
+
add_index :spree_users, :auth_identifier unless index_exists?(:spree_users, :auth_identifier)
|
|
10
|
+
add_index :spree_users, :mobile_number, unique: true, where: "mobile_number IS NOT NULL AND mobile_number != ''", name: 'idx_spree_users_on_unique_mobile_number' unless index_exists?(:spree_users, :mobile_number, name: 'idx_spree_users_on_unique_mobile_number')
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class UpdateOtpSchemaForMandatoryMobileAndTemplates < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
# 1. spree_otp_configurations: fallback channel and email template association
|
|
6
|
+
unless column_exists?(:spree_otp_configurations, :fallback_channel)
|
|
7
|
+
add_column :spree_otp_configurations, :fallback_channel, :string, default: 'none', null: false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
unless column_exists?(:spree_otp_configurations, :email_template_id)
|
|
11
|
+
if table_exists?(:spree_email_templates)
|
|
12
|
+
add_reference :spree_otp_configurations, :email_template, foreign_key: { to_table: :spree_email_templates }, index: true
|
|
13
|
+
else
|
|
14
|
+
add_reference :spree_otp_configurations, :email_template, index: true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# 2. spree_otp_attempts: staged mobile number for progressive disclosure flow
|
|
19
|
+
unless column_exists?(:spree_otp_attempts, :staged_mobile_number)
|
|
20
|
+
add_column :spree_otp_attempts, :staged_mobile_number, :string
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 3. spree_users: confirmed at timestamp for mobile phone verification
|
|
24
|
+
unless column_exists?(:spree_users, :mobile_confirmed_at)
|
|
25
|
+
add_column :spree_users, :mobile_confirmed_at, :datetime
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/db/migrate/20261018000001_add_gupshup_fields_to_spree_otp_configurations.spree_otp_auth.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddGupshupFieldsToSpreeOtpConfigurations < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :spree_otp_configurations, :gupshup_source_number, :string unless column_exists?(:spree_otp_configurations, :gupshup_source_number)
|
|
6
|
+
add_column :spree_otp_configurations, :gupshup_app_name, :string unless column_exists?(:spree_otp_configurations, :gupshup_app_name)
|
|
7
|
+
add_column :spree_otp_configurations, :gupshup_app_id, :string unless column_exists?(:spree_otp_configurations, :gupshup_app_id)
|
|
8
|
+
unless column_exists?(:spree_otp_configurations, :gupshup_template_name)
|
|
9
|
+
add_column :spree_otp_configurations, :gupshup_template_name, :string, default: 'verify_otp'
|
|
10
|
+
end
|
|
11
|
+
add_column :spree_otp_configurations, :gupshup_template_id, :string unless column_exists?(:spree_otp_configurations, :gupshup_template_id)
|
|
12
|
+
|
|
13
|
+
# New stores default to WhatsApp with email backup. Existing rows keep their
|
|
14
|
+
# channel until an admin switches after a successful test send.
|
|
15
|
+
change_column_default :spree_otp_configurations, :active_channel, from: 'email', to: 'whatsapp'
|
|
16
|
+
change_column_default :spree_otp_configurations, :fallback_channel, from: 'none', to: 'email'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddDeliveryFieldsToSpreeOtpAttempts < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
change_table :spree_otp_attempts, bulk: true do |t|
|
|
6
|
+
t.string :purpose, null: false, default: 'login' unless column_exists?(:spree_otp_attempts, :purpose)
|
|
7
|
+
t.string :staged_email unless column_exists?(:spree_otp_attempts, :staged_email)
|
|
8
|
+
t.string :provider_message_id unless column_exists?(:spree_otp_attempts, :provider_message_id)
|
|
9
|
+
t.string :delivery_status, null: false, default: 'pending' unless column_exists?(:spree_otp_attempts, :delivery_status)
|
|
10
|
+
t.string :fallback_reason unless column_exists?(:spree_otp_attempts, :fallback_reason)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
unless column_exists?(:spree_otp_attempts, :parent_attempt_id)
|
|
14
|
+
add_reference :spree_otp_attempts, :parent_attempt, foreign_key: { to_table: :spree_otp_attempts }, index: true
|
|
15
|
+
end
|
|
16
|
+
add_index :spree_otp_attempts, :provider_message_id unless index_exists?(:spree_otp_attempts, :provider_message_id)
|
|
17
|
+
end
|
|
18
|
+
end
|
data/db/migrate/20261018000003_add_email_marketing_decided_at_to_spree_users.spree_otp_auth.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddEmailMarketingDecidedAtToSpreeUsers < ActiveRecord::Migration[8.1]
|
|
4
|
+
# Null = the customer never made a news-and-offers choice; with
|
|
5
|
+
# accepts_email_marketing = false and a timestamp, they opted out.
|
|
6
|
+
def up
|
|
7
|
+
add_column :spree_users, :email_marketing_decided_at, :datetime unless column_exists?(:spree_users, :email_marketing_decided_at)
|
|
8
|
+
|
|
9
|
+
# Customers already on a verified newsletter subscription count as
|
|
10
|
+
# subscribed, decided when they first subscribed.
|
|
11
|
+
execute <<~SQL.squish
|
|
12
|
+
UPDATE spree_users u
|
|
13
|
+
SET accepts_email_marketing = TRUE,
|
|
14
|
+
email_marketing_decided_at = s.first_subscribed_at
|
|
15
|
+
FROM (
|
|
16
|
+
SELECT COALESCE(ns.user_id, matched.id) AS user_id, MIN(ns.created_at) AS first_subscribed_at
|
|
17
|
+
FROM spree_newsletter_subscribers ns
|
|
18
|
+
LEFT JOIN spree_users matched
|
|
19
|
+
ON ns.user_id IS NULL AND LOWER(matched.email) = LOWER(ns.email)
|
|
20
|
+
WHERE ns.verified_at IS NOT NULL
|
|
21
|
+
GROUP BY COALESCE(ns.user_id, matched.id)
|
|
22
|
+
) s
|
|
23
|
+
WHERE s.user_id IS NOT NULL AND u.id = s.user_id
|
|
24
|
+
SQL
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def down
|
|
28
|
+
remove_column :spree_users, :email_marketing_decided_at if column_exists?(:spree_users, :email_marketing_decided_at)
|
|
29
|
+
end
|
|
30
|
+
end
|
data/db/migrate/20261019000004_add_delivery_message_ref_to_spree_otp_attempts.spree_otp_auth.rb
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddDeliveryMessageRefToSpreeOtpAttempts < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :spree_otp_attempts, :delivery_message_ref, :uuid unless column_exists?(:spree_otp_attempts, :delivery_message_ref)
|
|
6
|
+
add_index :spree_otp_attempts, :delivery_message_ref unless index_exists?(:spree_otp_attempts, :delivery_message_ref)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module SpreeOtpAuth
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
desc 'Install spree_otp_auth migrations and assets'
|
|
9
|
+
|
|
10
|
+
class_option :migrate, type: :boolean, default: true, banner: 'Run migrations'
|
|
11
|
+
|
|
12
|
+
# spree_otp_auth sends codes through spree_notification_providers, so its
|
|
13
|
+
# tables are installed too (already-copied migrations are skipped).
|
|
14
|
+
def add_migrations
|
|
15
|
+
run 'bundle exec rake railties:install:migrations FROM=spree_notification_providers,spree_otp_auth'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run_migrations
|
|
19
|
+
if options[:migrate]
|
|
20
|
+
run 'bundle exec rake db:migrate'
|
|
21
|
+
else
|
|
22
|
+
say_status :skipping, 'db:migrate, remember to run migrations manually'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreeOtpAuth
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
engine_name 'spree_otp_auth'
|
|
6
|
+
|
|
7
|
+
config.generators do |g|
|
|
8
|
+
g.test_framework :rspec
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# The verify endpoint takes the one-time code as `code`; keep it out of the
|
|
12
|
+
# "Parameters:" log lines. Exact match so promo_code, country_code, etc.
|
|
13
|
+
# stay readable.
|
|
14
|
+
initializer 'spree_otp_auth.filter_parameters' do |app|
|
|
15
|
+
app.config.filter_parameters += [/\Acode\z/]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# OTP codes go out through spree_notification_providers; the code's
|
|
19
|
+
# provider-side template is entered per provider under "Login code".
|
|
20
|
+
initializer 'spree_otp_auth.message_types' do
|
|
21
|
+
SpreeNotificationProviders.register_message_type :otp_code,
|
|
22
|
+
label: 'Login code', channels: %i[email sms whatsapp]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# A provider that reports a code as failed after accepting it (e.g. a
|
|
26
|
+
# Gupshup "failed" webhook) triggers the email backup. Messages sent before
|
|
27
|
+
# spree_notification_providers have no delivery row, so the webhook asks
|
|
28
|
+
# this resolver which store owns the message id.
|
|
29
|
+
config.after_initialize do
|
|
30
|
+
Spree.subscribers << Spree::Otp::DeliveryFailedSubscriber
|
|
31
|
+
SpreeNotificationProviders.register_message_resolver(:gupshup) do |provider_message_id|
|
|
32
|
+
Spree::OtpAttempt.find_by(provider_message_id: provider_message_id, channel: 'whatsapp')&.store
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.activate
|
|
37
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
|
|
38
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
config.to_prepare(&method(:activate).to_proc)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spree/core'
|
|
4
|
+
require 'spree_notification_providers'
|
|
5
|
+
require 'spree_otp_auth/version'
|
|
6
|
+
require 'spree_otp_auth/engine'
|
|
7
|
+
|
|
8
|
+
module SpreeOtpAuth
|
|
9
|
+
# Domain of the placeholder email given to accounts created from a mobile
|
|
10
|
+
# number alone ("<digits>@guest.example.invalid"). Such emails count as
|
|
11
|
+
# "no email". Set it in an initializer to match your storefront:
|
|
12
|
+
# SpreeOtpAuth.synthetic_email_domain = '@guest.example.local'
|
|
13
|
+
mattr_accessor :synthetic_email_domain, default: '@guest.example.invalid'
|
|
14
|
+
|
|
15
|
+
# Optional model (class name) for admin-editable OTP email templates. Its
|
|
16
|
+
# records need `name`, `render_subject(vars)` and `render_body(vars)`, and
|
|
17
|
+
# may be scoped by a `store_id` column. Unset: the built-in mailer view is used.
|
|
18
|
+
# SpreeOtpAuth.email_template_class = 'Spree::EmailTemplate'
|
|
19
|
+
mattr_accessor :email_template_class, default: nil
|
|
20
|
+
|
|
21
|
+
def self.email_template_model
|
|
22
|
+
email_template_class.presence&.safe_constantize
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Error < StandardError; end
|
|
26
|
+
class DeliveryError < Error; end
|
|
27
|
+
class RateLimitError < Error; end
|
|
28
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/spree_otp_auth/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'spree_otp_auth'
|
|
7
|
+
spec.version = SpreeOtpAuth::VERSION
|
|
8
|
+
spec.authors = ['ONTODI']
|
|
9
|
+
spec.summary = 'Passwordless multi-channel OTP authentication and login-gated checkout for Spree Commerce'
|
|
10
|
+
spec.description = <<~DESC.strip
|
|
11
|
+
Provides mandatory login-gated checkout, passwordless customer authentication over WhatsApp,
|
|
12
|
+
SMS or email with an automatic email backup, and store-scoped admin configuration. Codes are
|
|
13
|
+
delivered through spree_notification_providers, so any provider and priority order works.
|
|
14
|
+
DESC
|
|
15
|
+
spec.license = 'BSD-3-Clause'
|
|
16
|
+
|
|
17
|
+
spec.required_ruby_version = '>= 3.3'
|
|
18
|
+
|
|
19
|
+
spec.files = Dir[
|
|
20
|
+
'{app,config,db,lib}/**/*',
|
|
21
|
+
'README.md',
|
|
22
|
+
'CHANGELOG.md',
|
|
23
|
+
'LICENSE',
|
|
24
|
+
'spree_otp_auth.gemspec'
|
|
25
|
+
]
|
|
26
|
+
spec.require_paths = ['lib']
|
|
27
|
+
|
|
28
|
+
spec.add_dependency 'rails', '>= 8.0'
|
|
29
|
+
spec.add_dependency 'spree', '>= 5.5'
|
|
30
|
+
spec.add_dependency 'spree_core', '>= 5.5'
|
|
31
|
+
spec.add_dependency 'spree_api', '>= 5.5'
|
|
32
|
+
spec.add_dependency 'spree_notification_providers', '~> 0.1'
|
|
33
|
+
spec.add_dependency 'spree_admin', '>= 5.5'
|
|
34
|
+
|
|
35
|
+
spec.homepage = 'https://github.com/kalanjiyaVishnu/spree_otp_auth'
|
|
36
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
37
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
38
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
39
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: spree_otp_auth
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- ONTODI
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: spree
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.5'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.5'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: spree_core
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.5'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '5.5'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: spree_api
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.5'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.5'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: spree_notification_providers
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: spree_admin
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '5.5'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '5.5'
|
|
96
|
+
description: |-
|
|
97
|
+
Provides mandatory login-gated checkout, passwordless customer authentication over WhatsApp,
|
|
98
|
+
SMS or email with an automatic email backup, and store-scoped admin configuration. Codes are
|
|
99
|
+
delivered through spree_notification_providers, so any provider and priority order works.
|
|
100
|
+
executables: []
|
|
101
|
+
extensions: []
|
|
102
|
+
extra_rdoc_files: []
|
|
103
|
+
files:
|
|
104
|
+
- CHANGELOG.md
|
|
105
|
+
- LICENSE
|
|
106
|
+
- README.md
|
|
107
|
+
- app/controllers/spree/admin/otp_configurations_controller.rb
|
|
108
|
+
- app/controllers/spree/api/v3/store/otp_controller.rb
|
|
109
|
+
- app/mailers/spree/otp_mailer.rb
|
|
110
|
+
- app/models/spree/otp_attempt.rb
|
|
111
|
+
- app/models/spree/otp_configuration.rb
|
|
112
|
+
- app/models/spree/otp_store_decorator.rb
|
|
113
|
+
- app/models/spree/otp_user_decorator.rb
|
|
114
|
+
- app/services/spree/otp/code_sent_payload.rb
|
|
115
|
+
- app/services/spree/otp/delivery.rb
|
|
116
|
+
- app/services/spree/otp/fallback.rb
|
|
117
|
+
- app/services/spree/otp/generate.rb
|
|
118
|
+
- app/services/spree/otp/identity.rb
|
|
119
|
+
- app/services/spree/otp/marketing_consent.rb
|
|
120
|
+
- app/services/spree/otp/rate_limiter.rb
|
|
121
|
+
- app/services/spree/otp/resend.rb
|
|
122
|
+
- app/services/spree/otp/verify.rb
|
|
123
|
+
- app/subscribers/spree/otp/delivery_failed_subscriber.rb
|
|
124
|
+
- app/views/spree/admin/otp_configurations/edit.html.erb
|
|
125
|
+
- app/views/spree/otp_mailer/send_otp.html.erb
|
|
126
|
+
- app/views/spree/otp_mailer/send_otp.text.erb
|
|
127
|
+
- config/locales/en.yml
|
|
128
|
+
- config/routes.rb
|
|
129
|
+
- db/migrate/20260927000001_create_spree_otp_configurations.spree_otp_auth.rb
|
|
130
|
+
- db/migrate/20260927000002_create_spree_otp_attempts.spree_otp_auth.rb
|
|
131
|
+
- db/migrate/20260927000003_add_otp_fields_to_spree_users.spree_otp_auth.rb
|
|
132
|
+
- db/migrate/20260927000004_update_otp_schema_for_mandatory_mobile_and_templates.spree_otp_auth.rb
|
|
133
|
+
- db/migrate/20261018000001_add_gupshup_fields_to_spree_otp_configurations.spree_otp_auth.rb
|
|
134
|
+
- db/migrate/20261018000002_add_delivery_fields_to_spree_otp_attempts.spree_otp_auth.rb
|
|
135
|
+
- db/migrate/20261018000003_add_email_marketing_decided_at_to_spree_users.spree_otp_auth.rb
|
|
136
|
+
- db/migrate/20261019000004_add_delivery_message_ref_to_spree_otp_attempts.spree_otp_auth.rb
|
|
137
|
+
- lib/generators/spree_otp_auth/install/install_generator.rb
|
|
138
|
+
- lib/spree_otp_auth.rb
|
|
139
|
+
- lib/spree_otp_auth/engine.rb
|
|
140
|
+
- lib/spree_otp_auth/version.rb
|
|
141
|
+
- spree_otp_auth.gemspec
|
|
142
|
+
homepage: https://github.com/kalanjiyaVishnu/spree_otp_auth
|
|
143
|
+
licenses:
|
|
144
|
+
- BSD-3-Clause
|
|
145
|
+
metadata:
|
|
146
|
+
source_code_uri: https://github.com/kalanjiyaVishnu/spree_otp_auth
|
|
147
|
+
changelog_uri: https://github.com/kalanjiyaVishnu/spree_otp_auth/blob/main/CHANGELOG.md
|
|
148
|
+
rubygems_mfa_required: 'true'
|
|
149
|
+
rdoc_options: []
|
|
150
|
+
require_paths:
|
|
151
|
+
- lib
|
|
152
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
153
|
+
requirements:
|
|
154
|
+
- - ">="
|
|
155
|
+
- !ruby/object:Gem::Version
|
|
156
|
+
version: '3.3'
|
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
|
+
requirements:
|
|
159
|
+
- - ">="
|
|
160
|
+
- !ruby/object:Gem::Version
|
|
161
|
+
version: '0'
|
|
162
|
+
requirements: []
|
|
163
|
+
rubygems_version: 4.0.3
|
|
164
|
+
specification_version: 4
|
|
165
|
+
summary: Passwordless multi-channel OTP authentication and login-gated checkout for
|
|
166
|
+
Spree Commerce
|
|
167
|
+
test_files: []
|