Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tests to the magic login submodule (#95)
* Add magic login feature * Use `.nil?` instead of `== nil` * Prepare for setting up database - create a migration file for spec - create a spec to be run and a shared_expmple file * Add configration tests change the default of `@magic_login_mailer_disabled` into true because the default breaks the tests * Change the configuration key, magic_login_mailer into magic_login_mailer_class * Add specs of `.generate_magic_login_token` * Add specs of `.clear_magic_login_token` * Add faliure case specs of `.magic_login_email` * Add success case specs of `.magic_login_email` * Refactoring: split the success case of the `.generate_magic_login_token` spec into two * Fix posix compliance offence: No newline at end of file
- Loading branch information
1 parent
172659f
commit 5f05584
Showing
10 changed files
with
402 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
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_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, | ||
:@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) | ||
|
||
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_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! | ||
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.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! | ||
unless config.magic_login_mailer_disabled | ||
send_magic_login_email! | ||
mail = true | ||
end | ||
end | ||
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_class) | ||
end | ||
end | ||
|
||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
spec/rails_app/app/views/sorcery_mailer/magic_login_email.html.erb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /> | ||
</head> | ||
<body> | ||
<h1>Hello, <%= @user.username %></h1> | ||
<p> | ||
To login without a password, just follow this link: <%= @url %>. | ||
</p> | ||
<p>Have a great day!</p> | ||
</body> | ||
</html> |
6 changes: 6 additions & 0 deletions
6
spec/rails_app/app/views/sorcery_mailer/magic_login_email.text.erb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Hello, <%= @user.username %> | ||
=============================================== | ||
|
||
To login without a password, just follow this link: <%= @url %>. | ||
|
||
Have a great day! |
17 changes: 17 additions & 0 deletions
17
spec/rails_app/db/migrate/magic_login/20170924151831_add_magic_login_to_users.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.