From 78dff9121e793906460b0c27ba5a19eb764b3499 Mon Sep 17 00:00:00 2001
From: tomomichi
Date: Mon, 14 Mar 2016 22:55:29 +0900
Subject: [PATCH 01/11] Add magic login feature
---
.../sorcery/templates/initializer.rb | 51 +++++++
.../templates/migration/magic_login.rb | 9 ++
lib/sorcery.rb | 1 +
lib/sorcery/model/submodules/magic_login.rb | 128 ++++++++++++++++++
4 files changed, 189 insertions(+)
create mode 100644 lib/generators/sorcery/templates/migration/magic_login.rb
create mode 100644 lib/sorcery/model/submodules/magic_login.rb
diff --git a/lib/generators/sorcery/templates/initializer.rb b/lib/generators/sorcery/templates/initializer.rb
index 8a736c3f..237e1f56 100644
--- a/lib/generators/sorcery/templates/initializer.rb
+++ b/lib/generators/sorcery/templates/initializer.rb
@@ -381,6 +381,57 @@
# user.reset_password_time_between_emails =
+ # -- magic_login --
+ # magic login code attribute name.
+ # Default: `:magic_login_token`
+ #
+ # user.magic_login_token_attribute_name =
+
+
+ # expires at attribute name.
+ # Default: `:magic_login_token_expires_at`
+ #
+ # user.magic_login_token_expires_at_attribute_name =
+
+
+ # when was email sent, used for hammering protection.
+ # Default: `:magic_login_email_sent_at`
+ #
+ # user.magic_login_email_sent_at_attribute_name =
+
+
+ # mailer class. Needed.
+ # Default: `nil`
+ #
+ # user.magic_login_mailer =
+
+
+ # magic login email method on your mailer class.
+ # Default: `:magic_login_email`
+ #
+ # user.magic_login_email_method_name =
+
+
+ # when true sorcery will not automatically
+ # email magic login details and allow you to
+ # manually handle how and when email is sent
+ # Default: `false`
+ #
+ # user.magic_login_mailer_disabled =
+
+
+ # how many seconds before the request expires. nil for never expires.
+ # Default: `nil`
+ #
+ # user.magic_login_expiration_period =
+
+
+ # hammering protection, how long in seconds to wait before allowing another email to be sent.
+ # Default: `5 * 60`
+ #
+ # user.magic_login_time_between_emails =
+
+
# -- brute_force_protection --
# Failed logins attribute name.
# Default: `:failed_logins_count`
diff --git a/lib/generators/sorcery/templates/migration/magic_login.rb b/lib/generators/sorcery/templates/migration/magic_login.rb
new file mode 100644
index 00000000..a1a4082f
--- /dev/null
+++ b/lib/generators/sorcery/templates/migration/magic_login.rb
@@ -0,0 +1,9 @@
+class SorceryMagicLogin < ActiveRecord::Migration
+ def change
+ add_column :<%= model_class_name.tableize %>, :magic_login_token, :string, :default => nil
+ add_column :<%= model_class_name.tableize %>, :magic_login_token_expires_at, :datetime, :default => nil
+ add_column :<%= model_class_name.tableize %>, :magic_login_email_sent_at, :datetime, :default => nil
+
+ add_index :<%= model_class_name.tableize %>, :magic_login_token
+ end
+end
diff --git a/lib/sorcery.rb b/lib/sorcery.rb
index e9e90d8e..a4c2e8ff 100644
--- a/lib/sorcery.rb
+++ b/lib/sorcery.rb
@@ -20,6 +20,7 @@ module Submodules
require 'sorcery/model/submodules/activity_logging'
require 'sorcery/model/submodules/brute_force_protection'
require 'sorcery/model/submodules/external'
+ require 'sorcery/model/submodules/magic_login'
end
end
diff --git a/lib/sorcery/model/submodules/magic_login.rb b/lib/sorcery/model/submodules/magic_login.rb
new file mode 100644
index 00000000..1efe7f1a
--- /dev/null
+++ b/lib/sorcery/model/submodules/magic_login.rb
@@ -0,0 +1,128 @@
+module Sorcery
+ module Model
+ module Submodules
+ # This submodule adds the ability to login via email without password.
+ # When the user requests an email is sent to him with a url.
+ # The url includes a token, which is also saved with the user's record in the db.
+ # The token has configurable expiration.
+ # When the user clicks the url in the email, providing the token has not yet expired,
+ # he will be able to login.
+ #
+ # When using this submodule, supplying a mailer is mandatory.
+ module MagicLogin
+ def self.included(base)
+ base.sorcery_config.class_eval do
+ attr_accessor :magic_login_token_attribute_name, # magic login code attribute name.
+ :magic_login_token_expires_at_attribute_name, # expires at attribute name.
+ :magic_login_email_sent_at_attribute_name, # when was email sent, used for hammering
+ # protection.
+
+ :magic_login_mailer, # mailer class. Needed.
+
+ :magic_login_mailer_disabled, # when true sorcery will not automatically
+ # email magic login details and allow you to
+ # manually handle how and when email is sent
+
+ :magic_login_email_method_name, # magic login email method on your
+ # mailer class.
+
+ :magic_login_expiration_period, # how many seconds before the request
+ # expires. nil for never expires.
+
+ :magic_login_time_between_emails # hammering protection, how long to wait
+ # before allowing another email to be sent.
+
+ end
+
+ base.sorcery_config.instance_eval do
+ @defaults.merge!(:@magic_login_token_attribute_name => :magic_login_token,
+ :@magic_login_token_expires_at_attribute_name => :magic_login_token_expires_at,
+ :@magic_login_email_sent_at_attribute_name => :magic_login_email_sent_at,
+ :@magic_login_mailer => nil,
+ :@magic_login_mailer_disabled => false,
+ :@magic_login_email_method_name => :magic_login_email,
+ :@magic_login_expiration_period => 15 * 60,
+ :@magic_login_time_between_emails => 5 * 60 )
+
+ reset!
+ end
+
+ base.extend(ClassMethods)
+
+ base.sorcery_config.after_config << :validate_mailer_defined
+ base.sorcery_config.after_config << :define_magic_login_fields
+
+ base.send(:include, InstanceMethods)
+
+ end
+
+ module ClassMethods
+ # Find user by token, also checks for expiration.
+ # Returns the user if token found and is valid.
+ def load_from_magic_login_token(token)
+ token_attr_name = @sorcery_config.magic_login_token_attribute_name
+ token_expiration_date_attr = @sorcery_config.magic_login_token_expires_at_attribute_name
+ load_from_token(token, token_attr_name, token_expiration_date_attr)
+ end
+
+ protected
+
+ # This submodule requires the developer to define his own mailer class to be used by it
+ # when magic_login_mailer_disabled is false
+ def validate_mailer_defined
+ msg = "To use magic_login submodule, you must define a mailer (config.magic_login_mailer = YourMailerClass)."
+ raise ArgumentError, msg if @sorcery_config.magic_login_mailer == nil and @sorcery_config.magic_login_mailer_disabled == false
+ end
+
+ def define_magic_login_fields
+ sorcery_adapter.define_field sorcery_config.magic_login_token_attribute_name, String
+ sorcery_adapter.define_field sorcery_config.magic_login_token_expires_at_attribute_name, Time
+ sorcery_adapter.define_field sorcery_config.magic_login_email_sent_at_attribute_name, Time
+ end
+
+ end
+
+ module InstanceMethods
+ # generates a reset code with expiration
+ def generate_magic_login_token!
+ config = sorcery_config
+ attributes = {config.magic_login_token_attribute_name => TemporaryToken.generate_random_token,
+ config.magic_login_email_sent_at_attribute_name => Time.now.in_time_zone}
+ attributes[config.magic_login_token_expires_at_attribute_name] = Time.now.in_time_zone + config.magic_login_expiration_period if config.magic_login_expiration_period
+
+ self.sorcery_adapter.update_attributes(attributes)
+ end
+
+ # generates a magic login code with expiration and sends an email to the user.
+ def deliver_magic_login_instructions!
+ mail = false
+ config = sorcery_config
+ # hammering protection
+ return false if config.magic_login_time_between_emails.present? && self.send(config.magic_login_email_sent_at_attribute_name) && self.send(config.magic_login_email_sent_at_attribute_name) > config.magic_login_time_between_emails.seconds.ago.utc
+ self.class.sorcery_adapter.transaction do
+ generate_magic_login_token!
+ mail = send_magic_login_email! unless config.magic_login_mailer_disabled
+ end
+ mail
+ end
+
+ # Clears the token.
+ def clear_magic_login_token!
+ config = sorcery_config
+ self.sorcery_adapter.update_attributes({
+ config.magic_login_token_attribute_name => nil,
+ config.magic_login_token_expires_at_attribute_name => nil
+ })
+ end
+
+ protected
+
+ def send_magic_login_email!
+ generic_send_email(:magic_login_email_method_name, :magic_login_mailer)
+ end
+ end
+
+ end
+ end
+ end
+end
From fbd9dd32342753852427b285d7cb20f16faf3b09 Mon Sep 17 00:00:00 2001
From: Josh Buker
Date: Wed, 19 Oct 2016 14:18:51 -0700
Subject: [PATCH 02/11] Use `.nil?` instead of `== nil`
---
lib/sorcery/model/submodules/magic_login.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/sorcery/model/submodules/magic_login.rb b/lib/sorcery/model/submodules/magic_login.rb
index 1efe7f1a..d414bde1 100644
--- a/lib/sorcery/model/submodules/magic_login.rb
+++ b/lib/sorcery/model/submodules/magic_login.rb
@@ -71,7 +71,7 @@ def load_from_magic_login_token(token)
# when magic_login_mailer_disabled is false
def validate_mailer_defined
msg = "To use magic_login submodule, you must define a mailer (config.magic_login_mailer = YourMailerClass)."
- raise ArgumentError, msg if @sorcery_config.magic_login_mailer == nil and @sorcery_config.magic_login_mailer_disabled == false
+ raise ArgumentError, msg if @sorcery_config.magic_login_mailer.nil? and @sorcery_config.magic_login_mailer_disabled == false
end
def define_magic_login_fields
From 675e69607d42f282aef81a155e962c44a56eeed5 Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Mon, 25 Sep 2017 00:38:48 +0900
Subject: [PATCH 03/11] Prepare for setting up database
- create a migration file for spec
- create a spec to be run and a shared_expmple file
---
spec/active_record/user_magic_login_spec.rb | 15 +++++++++++++++
.../20170924151831_add_magic_login_to_users.rb | 17 +++++++++++++++++
.../user_magic_login_shared_examples.rb | 4 ++++
3 files changed, 36 insertions(+)
create mode 100644 spec/active_record/user_magic_login_spec.rb
create mode 100644 spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb
create mode 100644 spec/shared_examples/user_magic_login_shared_examples.rb
diff --git a/spec/active_record/user_magic_login_spec.rb b/spec/active_record/user_magic_login_spec.rb
new file mode 100644
index 00000000..ea2e86aa
--- /dev/null
+++ b/spec/active_record/user_magic_login_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+require 'shared_examples/user_magic_login_shared_examples'
+
+describe User, 'with magic_login submodule', active_record: true do
+ before(:all) do
+ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate/magic_login")
+ User.reset_column_information
+ end
+
+ after(:all) do
+ ActiveRecord::Migrator.rollback("#{Rails.root}/db/migrate/magic_login")
+ end
+
+ it_behaves_like 'magic_login_model'
+end
diff --git a/spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb b/spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb
new file mode 100644
index 00000000..33196a6c
--- /dev/null
+++ b/spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb
@@ -0,0 +1,17 @@
+class AddMagicLoginToUsers < ActiveRecord::CompatibleLegacyMigration.migration_class
+ def self.up
+ add_column :users, :magic_login_token, :string, default: nil
+ add_column :users, :magic_login_token_expires_at, :datetime, default: nil
+ add_column :users, :magic_login_email_sent_at, :datetime, default: nil
+
+ add_index :users, :magic_login_token
+ end
+
+ def self.down
+ remove_index :users, :magic_login_token
+
+ remove_column :users, :magic_login_token
+ remove_column :users, :magic_login_token_expires_at
+ remove_column :users, :magic_login_email_sent_at
+ end
+end
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
new file mode 100644
index 00000000..09268c90
--- /dev/null
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -0,0 +1,4 @@
+shared_examples_for 'magic_login_model' do
+ it "test" do
+ end
+end
\ No newline at end of file
From 4406b15355d3a601bf2dea7d294ea58bc3442c46 Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Sun, 1 Oct 2017 14:46:02 +0900
Subject: [PATCH 04/11] Add configration tests
change the default of `@magic_login_mailer_disabled` into true because
the default breaks the tests
---
.../sorcery/templates/initializer.rb | 2 +-
lib/sorcery/model/submodules/magic_login.rb | 2 +-
.../user_magic_login_shared_examples.rb | 63 ++++++++++++++++++-
3 files changed, 62 insertions(+), 5 deletions(-)
diff --git a/lib/generators/sorcery/templates/initializer.rb b/lib/generators/sorcery/templates/initializer.rb
index 879450de..6c7f2e17 100644
--- a/lib/generators/sorcery/templates/initializer.rb
+++ b/lib/generators/sorcery/templates/initializer.rb
@@ -381,7 +381,7 @@
# when true sorcery will not automatically
# email magic login details and allow you to
# manually handle how and when email is sent
- # Default: `false`
+ # Default: `true`
#
# user.magic_login_mailer_disabled =
diff --git a/lib/sorcery/model/submodules/magic_login.rb b/lib/sorcery/model/submodules/magic_login.rb
index d414bde1..51f711a5 100644
--- a/lib/sorcery/model/submodules/magic_login.rb
+++ b/lib/sorcery/model/submodules/magic_login.rb
@@ -39,7 +39,7 @@ def self.included(base)
:@magic_login_token_expires_at_attribute_name => :magic_login_token_expires_at,
:@magic_login_email_sent_at_attribute_name => :magic_login_email_sent_at,
:@magic_login_mailer => nil,
- :@magic_login_mailer_disabled => false,
+ :@magic_login_mailer_disabled => true,
:@magic_login_email_method_name => :magic_login_email,
:@magic_login_expiration_period => 15 * 60,
:@magic_login_time_between_emails => 5 * 60 )
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
index 09268c90..38a520c5 100644
--- a/spec/shared_examples/user_magic_login_shared_examples.rb
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -1,4 +1,61 @@
-shared_examples_for 'magic_login_model' do
- it "test" do
+shared_examples_for "magic_login_model" do
+ let(:user) {create_new_user}
+ before(:each) do
+ User.sorcery_adapter.delete_all
end
-end
\ No newline at end of file
+
+ context 'loaded plugin configuration' do
+ let(:config) {User.sorcery_config}
+
+ before(:all) do
+ sorcery_reload!([:magic_login])
+ end
+
+ after(:each) do
+ User.sorcery_config.reset!
+ end
+
+ describe "enables configuration options" do
+ it do
+ sorcery_model_property_set(:magic_login_token_attribute_name, :test_magic_login_token)
+ expect(config.magic_login_token_attribute_name).to eq :test_magic_login_token
+ end
+
+ it do
+ sorcery_model_property_set(:magic_login_token_expires_at_attribute_name, :test_magic_login_token_expires_at)
+ expect(config.magic_login_token_expires_at_attribute_name).to eq :test_magic_login_token_expires_at
+ end
+
+ it do
+ sorcery_model_property_set(:magic_login_email_sent_at_attribute_name, :test_magic_login_email_sent_at)
+ expect(config.magic_login_email_sent_at_attribute_name).to eq :test_magic_login_email_sent_at
+ end
+
+ it do
+ TestMailerClass = Class.new # need a mailer class to test
+ sorcery_model_property_set(:magic_login_mailer, TestMailerClass)
+ expect(config.magic_login_mailer).to eq TestMailerClass
+ end
+
+ it do
+ sorcery_model_property_set(:magic_login_mailer_disabled, false)
+ expect(config.magic_login_mailer_disabled).to eq false
+ end
+
+ it do
+ sorcery_model_property_set(:magic_login_email_method_name, :test_magic_login_email)
+ expect(config.magic_login_email_method_name).to eq :test_magic_login_email
+ end
+
+ it do
+ sorcery_model_property_set(:magic_login_expiration_period, 100000000)
+ expect(config.magic_login_expiration_period).to eq 100000000
+ end
+
+ it do
+ sorcery_model_property_set(:magic_login_time_between_emails, 100000000)
+ expect(config.magic_login_time_between_emails).to eq 100000000
+ end
+ end
+ end
+end
From ecb5e8122055e0d8ad77baef77a380283f49c007 Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Sun, 1 Oct 2017 14:48:40 +0900
Subject: [PATCH 05/11] Change the configuration key, magic_login_mailer into
magic_login_mailer_class
---
lib/generators/sorcery/templates/initializer.rb | 2 +-
lib/sorcery/model/submodules/magic_login.rb | 10 +++++-----
.../user_magic_login_shared_examples.rb | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/lib/generators/sorcery/templates/initializer.rb b/lib/generators/sorcery/templates/initializer.rb
index 6c7f2e17..e25e861a 100644
--- a/lib/generators/sorcery/templates/initializer.rb
+++ b/lib/generators/sorcery/templates/initializer.rb
@@ -369,7 +369,7 @@
# mailer class. Needed.
# Default: `nil`
#
- # user.magic_login_mailer =
+ # user.magic_login_mailer_class =
# magic login email method on your mailer class.
diff --git a/lib/sorcery/model/submodules/magic_login.rb b/lib/sorcery/model/submodules/magic_login.rb
index 51f711a5..c1653b4a 100644
--- a/lib/sorcery/model/submodules/magic_login.rb
+++ b/lib/sorcery/model/submodules/magic_login.rb
@@ -17,7 +17,7 @@ def self.included(base)
:magic_login_email_sent_at_attribute_name, # when was email sent, used for hammering
# protection.
- :magic_login_mailer, # mailer class. Needed.
+ :magic_login_mailer_class, # mailer class. Needed.
:magic_login_mailer_disabled, # when true sorcery will not automatically
# email magic login details and allow you to
@@ -38,7 +38,7 @@ def self.included(base)
@defaults.merge!(:@magic_login_token_attribute_name => :magic_login_token,
:@magic_login_token_expires_at_attribute_name => :magic_login_token_expires_at,
:@magic_login_email_sent_at_attribute_name => :magic_login_email_sent_at,
- :@magic_login_mailer => nil,
+ :@magic_login_mailer_class => nil,
:@magic_login_mailer_disabled => true,
:@magic_login_email_method_name => :magic_login_email,
:@magic_login_expiration_period => 15 * 60,
@@ -70,8 +70,8 @@ def load_from_magic_login_token(token)
# This submodule requires the developer to define his own mailer class to be used by it
# when magic_login_mailer_disabled is false
def validate_mailer_defined
- msg = "To use magic_login submodule, you must define a mailer (config.magic_login_mailer = YourMailerClass)."
- raise ArgumentError, msg if @sorcery_config.magic_login_mailer.nil? and @sorcery_config.magic_login_mailer_disabled == false
+ msg = "To use magic_login submodule, you must define a mailer (config.magic_login_mailer_class = YourMailerClass)."
+ raise ArgumentError, msg if @sorcery_config.magic_login_mailer_class.nil? and @sorcery_config.magic_login_mailer_disabled == false
end
def define_magic_login_fields
@@ -118,7 +118,7 @@ def clear_magic_login_token!
protected
def send_magic_login_email!
- generic_send_email(:magic_login_email_method_name, :magic_login_mailer)
+ generic_send_email(:magic_login_email_method_name, :magic_login_mailer_class)
end
end
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
index 38a520c5..e0095e37 100644
--- a/spec/shared_examples/user_magic_login_shared_examples.rb
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -33,8 +33,8 @@
it do
TestMailerClass = Class.new # need a mailer class to test
- sorcery_model_property_set(:magic_login_mailer, TestMailerClass)
- expect(config.magic_login_mailer).to eq TestMailerClass
+ sorcery_model_property_set(:magic_login_mailer_class, TestMailerClass)
+ expect(config.magic_login_mailer_class).to eq TestMailerClass
end
it do
From 0ed9a42f7a723a6d72ceedd15044f499296fab3e Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Tue, 3 Oct 2017 00:36:21 +0900
Subject: [PATCH 06/11] Add specs of `.generate_magic_login_token`
---
.../user_magic_login_shared_examples.rb | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
index e0095e37..a06c85e6 100644
--- a/spec/shared_examples/user_magic_login_shared_examples.rb
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -57,5 +57,25 @@
expect(config.magic_login_time_between_emails).to eq 100000000
end
end
+
+ describe "#generate_magic_login_token!" do
+ context "magic_login_token is nil" do
+ it do
+ token_before = user.magic_login_token
+ user.generate_magic_login_token!
+ expect(user.magic_login_token).not_to eq token_before
+ expect(user.magic_login_token_expires_at).not_to be_nil
+ expect(user.magic_login_email_sent_at).not_to be_nil
+ end
+ end
+
+ context "magic_login_token is not nil" do
+ it "changes `user.magic_login_token`" do
+ token_before = user.magic_login_token
+ user.generate_magic_login_token!
+ expect(user.magic_login_token).not_to eq token_before
+ end
+ end
+ end
end
end
From e41f618e1412284ffae7e4d285e22ac50cc45e27 Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Fri, 6 Oct 2017 00:53:00 +0900
Subject: [PATCH 07/11] Add specs of `.clear_magic_login_token`
---
.../user_magic_login_shared_examples.rb | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
index a06c85e6..974f76c3 100644
--- a/spec/shared_examples/user_magic_login_shared_examples.rb
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -77,5 +77,17 @@
end
end
end
+
+ describe "#clear_magic_login_token!" do
+ it "makes magic_login_token_attribute_name and magic_login_token_expires_at_attribute_name nil" do
+ user.magic_login_token = "test_token"
+ user.magic_login_token_expires_at = Time.now
+
+ user.clear_magic_login_token!
+
+ expect(user.magic_login_token).to eq nil
+ expect(user.magic_login_token_expires_at).to eq nil
+ end
+ end
end
end
From 49b329755414d9ef7540a20db0fb66e260968380 Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Mon, 16 Oct 2017 01:12:32 +0900
Subject: [PATCH 08/11] Add faliure case specs of `.magic_login_email`
---
lib/sorcery/model/submodules/magic_login.rb | 4 ++-
.../user_magic_login_shared_examples.rb | 33 +++++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/lib/sorcery/model/submodules/magic_login.rb b/lib/sorcery/model/submodules/magic_login.rb
index c1653b4a..a4270fc0 100644
--- a/lib/sorcery/model/submodules/magic_login.rb
+++ b/lib/sorcery/model/submodules/magic_login.rb
@@ -98,7 +98,9 @@ def deliver_magic_login_instructions!
mail = false
config = sorcery_config
# hammering protection
- return false if config.magic_login_time_between_emails.present? && self.send(config.magic_login_email_sent_at_attribute_name) && self.send(config.magic_login_email_sent_at_attribute_name) > config.magic_login_time_between_emails.seconds.ago.utc
+ return false if !config.magic_login_time_between_emails.nil? &&
+ self.send(config.magic_login_email_sent_at_attribute_name) &&
+ self.send(config.magic_login_email_sent_at_attribute_name) > config.magic_login_time_between_emails.seconds.ago
self.class.sorcery_adapter.transaction do
generate_magic_login_token!
mail = send_magic_login_email! unless config.magic_login_mailer_disabled
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
index 974f76c3..398295e5 100644
--- a/spec/shared_examples/user_magic_login_shared_examples.rb
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -78,6 +78,39 @@
end
end
+ describe "#deliver_magic_login_instructions!" do
+ context "failure" do
+ context "magic_login_time_between_emails is nil" do
+ it "returns false" do
+ sorcery_model_property_set(:magic_login_time_between_emails, nil)
+ expect(user.deliver_magic_login_instructions!).to eq false
+ end
+ end
+
+ context "magic_login_email_sent_at is nil" do
+ it "returns false" do
+ user.send(:"#{config.magic_login_email_sent_at_attribute_name}=", nil)
+ expect(user.deliver_magic_login_instructions!).to eq false
+ end
+ end
+
+ context "now is before magic_login_email_sent_at plus the interval" do
+ it "returns false" do
+ user.send(:"#{config.magic_login_email_sent_at_attribute_name}=", DateTime.now)
+ sorcery_model_property_set(:magic_login_time_between_emails, 30*60)
+ expect(user.deliver_magic_login_instructions!).to eq false
+ end
+ end
+
+ context "magic_login_mailer_disabled is true" do
+ it "returns false" do
+ sorcery_model_property_set(:magic_login_mailer_disabled, true)
+ expect(user.deliver_magic_login_instructions!).to eq false
+ end
+ end
+ end
+ end
+
describe "#clear_magic_login_token!" do
it "makes magic_login_token_attribute_name and magic_login_token_expires_at_attribute_name nil" do
user.magic_login_token = "test_token"
From 6aba7d95460bedeaf619473079ed87865b5bd357 Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Fri, 20 Oct 2017 01:01:47 +0900
Subject: [PATCH 09/11] Add success case specs of `.magic_login_email`
---
lib/sorcery/model/submodules/magic_login.rb | 102 +++++++++---------
spec/rails_app/app/mailers/sorcery_mailer.rb | 7 ++
.../sorcery_mailer/magic_login_email.html.erb | 13 +++
.../sorcery_mailer/magic_login_email.text.erb | 6 ++
.../user_magic_login_shared_examples.rb | 20 ++++
5 files changed, 99 insertions(+), 49 deletions(-)
create mode 100644 spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb
create mode 100644 spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb
diff --git a/lib/sorcery/model/submodules/magic_login.rb b/lib/sorcery/model/submodules/magic_login.rb
index a4270fc0..b1e534cc 100644
--- a/lib/sorcery/model/submodules/magic_login.rb
+++ b/lib/sorcery/model/submodules/magic_login.rb
@@ -12,50 +12,50 @@ module Submodules
module MagicLogin
def self.included(base)
base.sorcery_config.class_eval do
- attr_accessor :magic_login_token_attribute_name, # magic login code attribute name.
- :magic_login_token_expires_at_attribute_name, # expires at attribute name.
- :magic_login_email_sent_at_attribute_name, # when was email sent, used for hammering
- # protection.
-
- :magic_login_mailer_class, # mailer class. Needed.
-
- :magic_login_mailer_disabled, # when true sorcery will not automatically
- # email magic login details and allow you to
- # manually handle how and when email is sent
-
- :magic_login_email_method_name, # magic login email method on your
- # mailer class.
-
- :magic_login_expiration_period, # how many seconds before the request
- # expires. nil for never expires.
-
- :magic_login_time_between_emails # hammering protection, how long to wait
- # before allowing another email to be sent.
-
+ attr_accessor :magic_login_token_attribute_name, # magic login code attribute name.
+ :magic_login_token_expires_at_attribute_name, # expires at attribute name.
+ :magic_login_email_sent_at_attribute_name, # when was email sent, used for hammering
+ # protection.
+
+ :magic_login_mailer_class, # mailer class. Needed.
+
+ :magic_login_mailer_disabled, # when true sorcery will not automatically
+ # email magic login details and allow you to
+ # manually handle how and when email is sent
+
+ :magic_login_email_method_name, # magic login email method on your
+ # mailer class.
+
+ :magic_login_expiration_period, # how many seconds before the request
+ # expires. nil for never expires.
+
+ :magic_login_time_between_emails # hammering protection, how long to wait
+ # before allowing another email to be sent.
+
end
-
+
base.sorcery_config.instance_eval do
- @defaults.merge!(:@magic_login_token_attribute_name => :magic_login_token,
+ @defaults.merge!(:@magic_login_token_attribute_name => :magic_login_token,
:@magic_login_token_expires_at_attribute_name => :magic_login_token_expires_at,
- :@magic_login_email_sent_at_attribute_name => :magic_login_email_sent_at,
- :@magic_login_mailer_class => nil,
- :@magic_login_mailer_disabled => true,
- :@magic_login_email_method_name => :magic_login_email,
- :@magic_login_expiration_period => 15 * 60,
- :@magic_login_time_between_emails => 5 * 60 )
-
+ :@magic_login_email_sent_at_attribute_name => :magic_login_email_sent_at,
+ :@magic_login_mailer_class => nil,
+ :@magic_login_mailer_disabled => true,
+ :@magic_login_email_method_name => :magic_login_email,
+ :@magic_login_expiration_period => 15 * 60,
+ :@magic_login_time_between_emails => 5 * 60)
+
reset!
end
-
+
base.extend(ClassMethods)
-
+
base.sorcery_config.after_config << :validate_mailer_defined
base.sorcery_config.after_config << :define_magic_login_fields
-
+
base.send(:include, InstanceMethods)
-
+
end
-
+
module ClassMethods
# Find user by token, also checks for expiration.
# Returns the user if token found and is valid.
@@ -64,24 +64,24 @@ def load_from_magic_login_token(token)
token_expiration_date_attr = @sorcery_config.magic_login_token_expires_at_attribute_name
load_from_token(token, token_attr_name, token_expiration_date_attr)
end
-
+
protected
-
+
# This submodule requires the developer to define his own mailer class to be used by it
# when magic_login_mailer_disabled is false
def validate_mailer_defined
msg = "To use magic_login submodule, you must define a mailer (config.magic_login_mailer_class = YourMailerClass)."
raise ArgumentError, msg if @sorcery_config.magic_login_mailer_class.nil? and @sorcery_config.magic_login_mailer_disabled == false
end
-
+
def define_magic_login_fields
sorcery_adapter.define_field sorcery_config.magic_login_token_attribute_name, String
sorcery_adapter.define_field sorcery_config.magic_login_token_expires_at_attribute_name, Time
sorcery_adapter.define_field sorcery_config.magic_login_email_sent_at_attribute_name, Time
end
-
+
end
-
+
module InstanceMethods
# generates a reset code with expiration
def generate_magic_login_token!
@@ -89,10 +89,10 @@ def generate_magic_login_token!
attributes = {config.magic_login_token_attribute_name => TemporaryToken.generate_random_token,
config.magic_login_email_sent_at_attribute_name => Time.now.in_time_zone}
attributes[config.magic_login_token_expires_at_attribute_name] = Time.now.in_time_zone + config.magic_login_expiration_period if config.magic_login_expiration_period
-
+
self.sorcery_adapter.update_attributes(attributes)
end
-
+
# generates a magic login code with expiration and sends an email to the user.
def deliver_magic_login_instructions!
mail = false
@@ -101,29 +101,33 @@ def deliver_magic_login_instructions!
return false if !config.magic_login_time_between_emails.nil? &&
self.send(config.magic_login_email_sent_at_attribute_name) &&
self.send(config.magic_login_email_sent_at_attribute_name) > config.magic_login_time_between_emails.seconds.ago
+
self.class.sorcery_adapter.transaction do
generate_magic_login_token!
- mail = send_magic_login_email! unless config.magic_login_mailer_disabled
+ unless config.magic_login_mailer_disabled
+ send_magic_login_email!
+ mail = true
+ end
end
mail
end
-
+
# Clears the token.
def clear_magic_login_token!
config = sorcery_config
self.sorcery_adapter.update_attributes({
- config.magic_login_token_attribute_name => nil,
- config.magic_login_token_expires_at_attribute_name => nil
- })
+ config.magic_login_token_attribute_name => nil,
+ config.magic_login_token_expires_at_attribute_name => nil
+ })
end
-
+
protected
-
+
def send_magic_login_email!
generic_send_email(:magic_login_email_method_name, :magic_login_mailer_class)
end
end
-
+
end
end
end
diff --git a/spec/rails_app/app/mailers/sorcery_mailer.rb b/spec/rails_app/app/mailers/sorcery_mailer.rb
index 4a415d43..bf68d59e 100644
--- a/spec/rails_app/app/mailers/sorcery_mailer.rb
+++ b/spec/rails_app/app/mailers/sorcery_mailer.rb
@@ -28,4 +28,11 @@ def send_unlock_token_email(user)
mail(to: user.email,
subject: 'Your account has been locked due to many wrong logins')
end
+
+ def magic_login_email(user)
+ @user = user
+ @url = 'http://example.com/login'
+ mail(to: user.email,
+ subject: 'Magic Login')
+ end
end
diff --git a/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb b/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb
new file mode 100644
index 00000000..c414ab48
--- /dev/null
+++ b/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+Hello, <%= @user.username %>
+
+ To login without a password, just follow this link: <%= @url %>.
+
+Have a great day!
+
+
\ No newline at end of file
diff --git a/spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb b/spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb
new file mode 100644
index 00000000..ee11bcb9
--- /dev/null
+++ b/spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb
@@ -0,0 +1,6 @@
+Hello, <%= @user.username %>
+===============================================
+
+To login without a password, just follow this link: <%= @url %>.
+
+Have a great day!
\ No newline at end of file
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
index 398295e5..e0c8b5e3 100644
--- a/spec/shared_examples/user_magic_login_shared_examples.rb
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -79,6 +79,26 @@
end
describe "#deliver_magic_login_instructions!" do
+ context "success" do
+ before do
+ sorcery_model_property_set(:magic_login_time_between_emails, 30*60)
+ sorcery_model_property_set(:magic_login_mailer_disabled, false)
+ Timecop.travel(10.days.ago) do
+ user.send(:"#{config.magic_login_email_sent_at_attribute_name}=", DateTime.now)
+ end
+ sorcery_model_property_set(:magic_login_mailer_class, ::SorceryMailer)
+ end
+
+ it do
+ user.deliver_magic_login_instructions!
+ expect(ActionMailer::Base.deliveries.size).to eq 1
+ end
+
+ it do
+ expect(user.deliver_magic_login_instructions!).to eq true
+ end
+ end
+
context "failure" do
context "magic_login_time_between_emails is nil" do
it "returns false" do
From 3896d8a93264718e39b1e2b3b8195e61a628f2ec Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Mon, 23 Oct 2017 23:37:55 +0900
Subject: [PATCH 10/11] Refactoring: split the success case of the
`.generate_magic_login_token` spec into two
---
.../user_magic_login_shared_examples.rb | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/spec/shared_examples/user_magic_login_shared_examples.rb b/spec/shared_examples/user_magic_login_shared_examples.rb
index e0c8b5e3..758a2526 100644
--- a/spec/shared_examples/user_magic_login_shared_examples.rb
+++ b/spec/shared_examples/user_magic_login_shared_examples.rb
@@ -16,7 +16,7 @@
end
describe "enables configuration options" do
- it do
+ it do
sorcery_model_property_set(:magic_login_token_attribute_name, :test_magic_login_token)
expect(config.magic_login_token_attribute_name).to eq :test_magic_login_token
end
@@ -60,13 +60,17 @@
describe "#generate_magic_login_token!" do
context "magic_login_token is nil" do
- it do
- token_before = user.magic_login_token
+ it "magic_login_token_expires_at and magic_login_email_sent_at aren't nil " do
user.generate_magic_login_token!
- expect(user.magic_login_token).not_to eq token_before
expect(user.magic_login_token_expires_at).not_to be_nil
expect(user.magic_login_email_sent_at).not_to be_nil
end
+
+ it "magic_login_token is different from the one before" do
+ token_before = user.magic_login_token
+ user.generate_magic_login_token!
+ expect(user.magic_login_token).not_to eq token_before
+ end
end
context "magic_login_token is not nil" do
From ceac23f01d82ba2062eccc812cadf019cb5ad65c Mon Sep 17 00:00:00 2001
From: Yusuke Ebihara
Date: Wed, 29 Nov 2017 11:02:11 +0900
Subject: [PATCH 11/11] Fix posix compliance offence: No newline at end of file
---
.../app/views/sorcery_mailer/magic_login_email.html.erb | 2 +-
.../app/views/sorcery_mailer/magic_login_email.text.erb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb b/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb
index c414ab48..cf8243f3 100644
--- a/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb
+++ b/spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb
@@ -10,4 +10,4 @@
Have a great day!