Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merged back in most of my working

  • Loading branch information...
commit 738b72b23a4c28d2b1276cd63641750a0d8b71f4 2 parents ba35f2a + bfb9b2c
Christian Frichot authored
Showing with 1,770 additions and 175 deletions.
  1. +15 −1 Gemfile
  2. +95 −35 Gemfile.lock
  3. +2 −1  Rakefile
  4. +1 −1  VERSION
  5. +21 −3 app/controllers/devise/checkga_controller.rb
  6. +4 −3 app/views/devise/checkga/show.html.erb
  7. +4 −4 app/views/devise/displayqr/show.html.erb
  8. +9 −0 config/locales/en.yml
  9. +2 −26 devise_google_authenticator.gemspec
  10. +30 −2 lib/devise_google_authenticatable/models/google_authenticatable.rb
  11. +11 −70 lib/devise_google_authenticatable/patches/check_ga.rb
  12. +8 −0 lib/devise_google_authenticatable/schema.rb
  13. +8 −4 lib/devise_google_authenticator.rb
  14. +13 −0 lib/generators/active_record/devise_google_authenticator_generator.rb
  15. +17 −0 lib/generators/active_record/templates/migration.rb
  16. +19 −0 lib/generators/devise_google_authenticator/devise_google_authenticator_generator.rb
  17. +23 −0 lib/generators/devise_google_authenticator/install_generator.rb
  18. +19 −0 lib/generators/devise_google_authenticator/views_generator.rb
  19. +48 −0 test/generators_test.rb
  20. +0 −18 test/helper.rb
  21. +207 −0 test/integration/invitation_test.rb
  22. +48 −0 test/integration_tests_helper.rb
  23. +59 −0 test/mailers/invitation_mail_test.rb
  24. +33 −0 test/model_tests_helper.rb
  25. +372 −0 test/models/invitable_test.rb
  26. +74 −0 test/models_test.rb
  27. +4 −0 test/orm/active_record.rb
  28. +10 −0 test/orm/mongoid.rb
  29. +6 −0 test/rails_app/app/controllers/admins_controller.rb
  30. +3 −0  test/rails_app/app/controllers/application_controller.rb
  31. +6 −0 test/rails_app/app/controllers/free_invitations_controller.rb
  32. +4 −0 test/rails_app/app/controllers/home_controller.rb
  33. +12 −0 test/rails_app/app/controllers/users_controller.rb
  34. +2 −0  test/rails_app/app/helpers/application_helper.rb
  35. +5 −0 test/rails_app/app/models/admin.rb
  36. +5 −0 test/rails_app/app/models/octopussy.rb
  37. +15 −0 test/rails_app/app/models/user.rb
  38. +12 −0 test/rails_app/app/views/admins/new.html.erb
  39. +12 −0 test/rails_app/app/views/free_invitations/new.html.erb
  40. 0  test/rails_app/app/views/home/index.html.erb
  41. +16 −0 test/rails_app/app/views/layouts/application.html.erb
  42. +15 −0 test/rails_app/app/views/users/invitations/new.html.erb
  43. +4 −0 test/rails_app/config.ru
  44. +25 −0 test/rails_app/config/application.rb
  45. +11 −0 test/rails_app/config/boot.rb
  46. +22 −0 test/rails_app/config/database.yml
  47. +5 −0 test/rails_app/config/environment.rb
  48. +26 −0 test/rails_app/config/environments/development.rb
  49. +49 −0 test/rails_app/config/environments/production.rb
  50. +35 −0 test/rails_app/config/environments/test.rb
  51. +7 −0 test/rails_app/config/initializers/backtrace_silencers.rb
  52. +184 −0 test/rails_app/config/initializers/devise.rb
  53. +10 −0 test/rails_app/config/initializers/inflections.rb
  54. +5 −0 test/rails_app/config/initializers/mime_types.rb
  55. +7 −0 test/rails_app/config/initializers/secret_token.rb
  56. +8 −0 test/rails_app/config/initializers/session_store.rb
  57. +10 −0 test/rails_app/config/locales/en.yml
  58. +12 −0 test/rails_app/config/mongoid.yml
  59. +9 −0 test/rails_app/config/routes.rb
  60. +20 −0 test/rails_app/db/migrate/20100401102949_create_tables.rb
  61. +6 −0 test/rails_app/script/rails
  62. +20 −0 test/routes_test.rb
  63. +5 −0 test/support/locale/en.yml
  64. +0 −7 test/test_devise_google_authenticator.rb
  65. +21 −0 test/test_helper.rb
View
16 Gemfile
@@ -6,10 +6,24 @@ gem "rails"
gem "devise"
gem "rotp"
+# The below are yoinked from devise_invitable's gemfile
+group :test do
+ gem "sqlite3"
+ gem "mongoid", "~> 2.0"
+ gem "bson_ext", "~> 1.3"
+ gem "capybara", "~> 0.4.0"
+ gem 'shoulda', '~> 2.11.3'
+ gem 'mocha', '~> 0.9.9'
+ gem 'factory_girl_rails', '~> 1.0'
+ gem 'rspec-rails', '~> 2.5.0'
+end
+
+# End devise_invitable's gemfile stuff
+
# Add dependencies to develop your gem here.
# Include everything needed to run rake, tests, features, etc.
group :development do
- gem "shoulda", ">= 0"
+ gem "shoulda", "~> 2.11.3"
gem "bundler", "~> 1.0.0"
gem "jeweler", "~> 1.6.4"
gem "rcov", ">= 0"
View
130 Gemfile.lock
@@ -1,42 +1,65 @@
GEM
remote: http://rubygems.org/
specs:
- actionmailer (3.1.1)
- actionpack (= 3.1.1)
+ actionmailer (3.1.3)
+ actionpack (= 3.1.3)
mail (~> 2.3.0)
- actionpack (3.1.1)
- activemodel (= 3.1.1)
- activesupport (= 3.1.1)
+ actionpack (3.1.3)
+ activemodel (= 3.1.3)
+ activesupport (= 3.1.3)
builder (~> 3.0.0)
erubis (~> 2.7.0)
i18n (~> 0.6)
- rack (~> 1.3.2)
+ rack (~> 1.3.5)
rack-cache (~> 1.1)
rack-mount (~> 0.8.2)
rack-test (~> 0.6.1)
- sprockets (~> 2.0.2)
- activemodel (3.1.1)
- activesupport (= 3.1.1)
+ sprockets (~> 2.0.3)
+ activemodel (3.1.3)
+ activesupport (= 3.1.3)
builder (~> 3.0.0)
i18n (~> 0.6)
- activerecord (3.1.1)
- activemodel (= 3.1.1)
- activesupport (= 3.1.1)
+ activerecord (3.1.3)
+ activemodel (= 3.1.3)
+ activesupport (= 3.1.3)
arel (~> 2.2.1)
tzinfo (~> 0.3.29)
- activeresource (3.1.1)
- activemodel (= 3.1.1)
- activesupport (= 3.1.1)
- activesupport (3.1.1)
+ activeresource (3.1.3)
+ activemodel (= 3.1.3)
+ activesupport (= 3.1.3)
+ activesupport (3.1.3)
multi_json (~> 1.0)
arel (2.2.1)
bcrypt-ruby (3.0.1)
+ bson (1.5.2)
+ bson_ext (1.5.2)
+ bson (= 1.5.2)
builder (3.0.0)
- devise (1.4.9)
+ capybara (0.4.1.2)
+ celerity (>= 0.7.9)
+ culerity (>= 0.2.4)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (>= 0.0.27)
+ xpath (~> 0.1.3)
+ celerity (0.9.2)
+ childprocess (0.3.0)
+ ffi (~> 1.0.6)
+ culerity (0.2.15)
+ devise (1.5.3)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3)
- warden (~> 1.0.3)
+ warden (~> 1.1)
+ diff-lcs (1.1.3)
erubis (2.7.0)
+ factory_girl (2.4.0)
+ activesupport
+ factory_girl_rails (1.5.0)
+ factory_girl (~> 2.4.0)
+ railties (>= 3.0.0)
+ ffi (1.0.11)
git (1.2.5)
hike (1.2.1)
i18n (0.6.0)
@@ -44,16 +67,24 @@ GEM
bundler (~> 1.0)
git (>= 1.2.5)
rake
- json (1.6.1)
+ json (1.6.4)
mail (2.3.0)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.17.2)
- multi_json (1.0.3)
- orm_adapter (0.0.5)
+ mocha (0.9.12)
+ mongo (1.5.2)
+ bson (= 1.5.2)
+ mongoid (2.4.0)
+ activemodel (~> 3.1)
+ mongo (~> 1.3)
+ tzinfo (~> 0.3.22)
+ multi_json (1.0.4)
+ nokogiri (1.5.0)
+ orm_adapter (0.0.6)
polyglot (0.3.3)
- rack (1.3.5)
+ rack (1.3.6)
rack-cache (1.1)
rack (>= 0.4)
rack-mount (0.8.3)
@@ -62,48 +93,77 @@ GEM
rack
rack-test (0.6.1)
rack (>= 1.0)
- rails (3.1.1)
- actionmailer (= 3.1.1)
- actionpack (= 3.1.1)
- activerecord (= 3.1.1)
- activeresource (= 3.1.1)
- activesupport (= 3.1.1)
+ rails (3.1.3)
+ actionmailer (= 3.1.3)
+ actionpack (= 3.1.3)
+ activerecord (= 3.1.3)
+ activeresource (= 3.1.3)
+ activesupport (= 3.1.3)
bundler (~> 1.0)
- railties (= 3.1.1)
- railties (3.1.1)
- actionpack (= 3.1.1)
- activesupport (= 3.1.1)
+ railties (= 3.1.3)
+ railties (3.1.3)
+ actionpack (= 3.1.3)
+ activesupport (= 3.1.3)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.6)
rake (0.9.2.2)
rcov (0.9.11)
- rdoc (3.11)
+ rdoc (3.12)
json (~> 1.4)
rotp (1.3.2)
+ rspec (2.5.0)
+ rspec-core (~> 2.5.0)
+ rspec-expectations (~> 2.5.0)
+ rspec-mocks (~> 2.5.0)
+ rspec-core (2.5.2)
+ rspec-expectations (2.5.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.5.0)
+ rspec-rails (2.5.0)
+ actionpack (~> 3.0)
+ activesupport (~> 3.0)
+ railties (~> 3.0)
+ rspec (~> 2.5.0)
+ rubyzip (0.9.5)
+ selenium-webdriver (2.16.0)
+ childprocess (>= 0.2.5)
+ ffi (~> 1.0.9)
+ multi_json (~> 1.0.4)
+ rubyzip
shoulda (2.11.3)
sprockets (2.0.3)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
+ sqlite3 (1.3.5)
thor (0.14.6)
tilt (1.3.3)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.31)
- warden (1.0.6)
+ warden (1.1.0)
rack (>= 1.0)
+ xpath (0.1.4)
+ nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
+ bson_ext (~> 1.3)
bundler (~> 1.0.0)
+ capybara (~> 0.4.0)
devise
+ factory_girl_rails (~> 1.0)
jeweler (~> 1.6.4)
+ mocha (~> 0.9.9)
+ mongoid (~> 2.0)
rails
rcov
rotp
- shoulda
+ rspec-rails (~> 2.5.0)
+ shoulda (~> 2.11.3)
+ sqlite3
View
3  Rakefile
@@ -15,12 +15,13 @@ require 'jeweler'
Jeweler::Tasks.new do |gem|
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
gem.name = "devise_google_authenticator"
- gem.homepage = "http://github.com/xntrik/devise_google_authenticator"
+ gem.homepage = "http://github.com/AsteriskLabs/devise_google_authenticator"
gem.license = "MIT"
gem.summary = %Q{Devise Google Authenticator Extension}
gem.description = %Q{Devise Google Authenticator Extension, for adding Google's OTP to your Rails apps!}
gem.email = "xntrik@gmail.com"
gem.authors = ["Christian Frichot"]
+ gem.files = Dir["{app,config,lib}/**/*"] + %w[LICENSE.txt README.rdoc]
# dependencies defined in Gemfile
end
Jeweler::RubygemsDotOrgTasks.new
View
2  VERSION
@@ -1 +1 @@
-0.1.0
+0.2.0
View
24 app/controllers/devise/checkga_controller.rb
@@ -3,11 +3,29 @@ class Devise::CheckgaController < Devise::SessionsController
include Devise::Controllers::InternalHelpers
def show
- render_with_scope :show
+ @tmpid = params[:id]
+ if @tmpid.nil?
+ redirect_to :root
+ else
+ render_with_scope :show
+ end
end
def update
- sign_in(resource_name, resource)
- respond_with resource, :location => redirect_location(resource_name, resource)
+ resource = resource_class.find_by_gauth_tmp(params[resource_name]['tmpid'])
+
+ if not resource.nil?
+
+ if resource.validate_token(params[resource_name]['token'].to_i)
+ set_flash_message(:notice, :signed_in) if is_navigational_format?
+ sign_in(resource_name,resource)
+ respond_with resource, :location => redirect_location(resource_name, resource)
+ else
+ redirect_to :root
+ end
+
+ else
+ redirect_to :root
+ end
end
end
View
7 app/views/devise/checkga/show.html.erb
@@ -1,6 +1,7 @@
-<h2>Submit QR Code</h2>
+<h2><%= I18n.t('submit_token_title', {:scope => 'devise'}) %></h2>
<%= form_for(resource, :as => resource_name, :url => [resource_name, :checkga], :html => { :method => :put }) do |f| %>
-
- <p><%= f.submit "Continue.." %></p>
+ <%= f.hidden_field :tmpid, {:value => @tmpid} %>
+ <%= f.text_field :token, :autocomplete => :off%>
+ <p><%= f.submit I18n.t('submit_token', {:scope => 'devise'}) %></p>
<% end %>
View
8 app/views/devise/displayqr/show.html.erb
@@ -1,12 +1,12 @@
-<h2>Your QR Code</h2>
+<h2><%= I18n.t('title', {:scope => 'devise.registration'}) %></h2>
<%= google_authenticator_qrcode(resource) %>
<%= form_for(resource, :as => resource_name, :url => [resource_name, :displayqr], :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
- <h3>Would you like to enable Google Authenticator?</h3>
- <p><%= f.label :gauth_enabled, "Google Authenticator Status" %><br />
+ <h3><%= I18n.t('nice_request', {:scope => 'devise.registration'}) %></h3>
+ <p><%= f.label :gauth_enabled, I18n.t('qrstatus', {:scope => 'devise.registration'}) %><br />
<%= f.check_box :gauth_enabled %></p>
- <p><%= f.submit "Continue.." %></p>
+ <p><%= f.submit I18n.t('submit', {:scope => 'devise.registration'}) %></p>
<% end %>
View
9 config/locales/en.yml
@@ -0,0 +1,9 @@
+en:
+ devise:
+ submit_token: "Check Token"
+ submit_token_title: "Please enter your Google Authenticator token:"
+ registration:
+ title: "Your QR Code:"
+ nice_request: "Would you like to enable Google Authenticator?"
+ qrstatus: "Google Authenticator Status:"
+ submit: "Continue..."
View
28 devise_google_authenticator.gemspec
@@ -9,38 +9,14 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Christian Frichot"]
- s.date = "2011-11-22"
+ s.date = "2012-01-22"
s.description = "Devise Google Authenticator Extension, for adding Google's OTP to your Rails apps!"
s.email = "xntrik@gmail.com"
s.extra_rdoc_files = [
"LICENSE.txt",
"README.rdoc"
]
- s.files = [
- ".document",
- "Gemfile",
- "Gemfile.lock",
- "LICENSE.txt",
- "README.rdoc",
- "Rakefile",
- "VERSION",
- "app/controllers/devise/checkga_controller.rb",
- "app/controllers/devise/displayqr_controller.rb",
- "app/views/devise/checkga/show.html.erb",
- "app/views/devise/displayqr/show.html.erb",
- "devise_google_authenticator.gemspec",
- "lib/devise_google_authenticatable/controllers/helpers.rb",
- "lib/devise_google_authenticatable/hooks/google_authenticatable.rb",
- "lib/devise_google_authenticatable/models/google_authenticatable.rb",
- "lib/devise_google_authenticatable/orm/active_record.rb",
- "lib/devise_google_authenticatable/patches.rb",
- "lib/devise_google_authenticatable/patches/check_ga.rb",
- "lib/devise_google_authenticatable/patches/display_qr.rb",
- "lib/devise_google_authenticatable/rails.rb",
- "lib/devise_google_authenticatable/routes.rb",
- "lib/devise_google_authenticatable/schema.rb",
- "lib/devise_google_authenticator.rb"
- ]
+ s.files = Dir["{app,config,lib}/**/*"] + %w[LICENSE.txt README.rdoc]
s.homepage = "http://github.com/AsteriskLabs/devise_google_authenticator"
s.licenses = ["MIT"]
s.require_paths = ["lib"]
View
32 lib/devise_google_authenticatable/models/google_authenticatable.rb
@@ -28,16 +28,44 @@ def login_phase_one
return "yep"
end
+ def assign_tmp
+ self.update_attributes(:gauth_tmp => ROTP::Base32.random_base32, :gauth_tmp_datetime => DateTime.now)
+ self.gauth_tmp
+ end
+
+ def validate_token(token)
+ if self.gauth_tmp_datetime < self.class.ga_timeout.ago
+ return false
+ else
+
+ valid_vals = []
+ valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now)
+ (1..self.class.ga_timedrift).each do |cc|
+ valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.ago(30*cc))
+ valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.in(30*cc))
+ end
+
+ if valid_vals.include?(token.to_i)
+ return true
+ else
+ return false
+ end
+ end
+ end
+
private
def assign_auth_secret
self.gauth_secret = ROTP::Base32.random_base32
end
-
+
end
module ClassMethods # :nodoc:
- #Something here?
+ def find_by_gauth_tmp(gauth_tmp)
+ find(:first, :conditions => {:gauth_tmp => gauth_tmp})
+ end
+ ::Devise::Models.config(self, :ga_timeout, :ga_timedrift)
end
end
end
View
81 lib/devise_google_authenticatable/patches/check_ga.rb
@@ -6,85 +6,26 @@ module CheckGA
# here the patch
alias_method :create_original, :create
-
- #Below is trial 1 .. over-writing most of the create method
- #Whilst this works, I wish it was about a gazillion times cleaner
-
+
define_method :create do
- #Okay, firstly we grab the resource, if the user stuffs up anything, this dies immediately.
- #This actually authenticates their password
+
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
- #Okay, check that the resource model includes the get_qr method
- if resource.respond_to?(:get_qr) #Therefore we can quiz for a QR
+ if resource.respond_to?(:get_qr) and resource.gauth_enabled.to_i != 0 #Therefore we can quiz for a QR
+ tmpid = resource.assign_tmp #assign a temporary key and fetch it
+ warden.logout #log the user out
- # Okay, we have the method to get the qr secret, lets check if the user has gauth enabled
- if resource.gauth_enabled.to_i != 0 #gauth_enabled is not set to zero, therefore it's ON!
-
- # Orite, At this point the user model includes the extension stuff
- # PLUS, gauth_enabled is ON, so lets try and authenticate .. but first
-
- # Lets check if the "POST" includes the gauth_submit parameter
- if params.fetch(resource_name).include?("gauth_submit") #Yep, the browser submitted the gauth_submit - OTP
-
- #Okay, lets get what they submitted in the form, crunch it into an int
- submitted_value = params.fetch(resource_name).fetch("gauth_submit").to_i
-
- #By default, gauth is not successful
- gauth_successful = false
-
- if submitted_value == 0 #We have a field, but they've left it blank..
- #Nothing, they left the field blank, and therefore will not sign in, gauth_successfull remains false
- else
- #Okay, they submitted something into the OTP field
-
- #Lets account for the fact the timing may not always be accurate, so go backwards, current and forwards
- #If the submitted OTP matches, then gauth_successful is true - YAY for you! .. Yay for you indeed.
-
- #CF TODO: Do some checking here, how late can these codes be??
- if submitted_value == ROTP::TOTP.new(resource.get_qr).at(Time.now.ago(30))
- gauth_successful = true
- elsif submitted_value == ROTP::TOTP.new(resource.get_qr).at(Time.now)
- gauth_successful = true
- elsif submitted_value == ROTP::TOTP.new(resource.get_qr).at(Time.now.in(30))
- gauth_successful = true
- end
- end
-
- if gauth_successful == true #That means the OTP actually worked, lets log 'em in
- set_flash_message(:notice, :signed_in) if is_navigational_format?
- sign_in(resource_name, resource)
- respond_with resource, :location => redirect_location(resource_name, resource)
- else #That means that, the OTP did NOT line up properly, lets kick 'em back to the start
- signed_in = signed_in?(resource_name)
- Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
- resource = build_resource
- clean_up_passwords(resource)
- respond_with resource, :location => {:controller => 'sessions', :action => 'new'}
- end
-
- else #Okay, this is odd, the user is all set to go, but the browser did NOT include the OTP, tampering occurred
- # OR - the developer did NOT modify the "sessions" view to include
- # TODO: What should they put in the view again?
-
- #At this point, we're going to log them back out and send them back to the start
- signed_in = signed_in?(resource_name)
- Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
- resource = build_resource
- clean_up_passwords(resource)
- respond_with resource, :location => {:controller => 'sessions', :action => 'new'}
- end
- else #gauth_enabled must have been set to zero, therefore it's off .. lets just continue with the original sign in process
- set_flash_message(:notice, :signed_in) if is_navigational_format?
- sign_in(resource_name, resource)
- respond_with resource, :location => redirect_location(resource_name, resource)
- end
- else #It looks like the model did NOT include the get_qr method .. lets just continue with the original sign in process
+ #we head back into the checkga controller with the temporary id
+ respond_with resource, :location => { :controller => 'checkga', :action => 'show', :id => tmpid}
+
+ else #It's not using, or not enabled for Google 2FA - carry on, nothing to see here.
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
end
+
end
+
end
end
end
View
8 lib/devise_google_authenticatable/schema.rb
@@ -26,5 +26,13 @@ def gauth_enabled
apply_devise_schema :gauth_enabled, Integer, {:default => 0}
end
+ def gauth_tmp
+ apply_devise_schema :gauth_tmp, String
+ end
+
+ def gauth_tmp_datetime
+ apply_devise_schema :gauth_tmp_datetime, Datetime
+ end
+
end
end
View
12 lib/devise_google_authenticator.rb
@@ -5,14 +5,18 @@
require 'active_support/concern'
require 'devise'
+module Devise # :nodoc:
+ mattr_accessor :ga_timeout
+ @@ga_timeout = 3.minutes
+
+ mattr_accessor :ga_timedrift
+ @@ga_timedrift = 3
+end
+
# a security extension for devise
module DeviseGoogleAuthenticator
autoload :Schema, 'devise_google_authenticatable/schema'
autoload :Patches, 'devise_google_authenticatable/patches'
-
-# module Controllers # :nodoc:
-# autoload :Helpers, 'devise_google_authenticatable/controllers/helpers'
-# end
end
View
13 lib/generators/active_record/devise_google_authenticator_generator.rb
@@ -0,0 +1,13 @@
+require 'rails/generators/active_record'
+
+module ActiveRecord
+ module Generators
+ class DeviseGoogleAuthenticatorGenerator < ActiveRecord::Generators::Base
+ source_root File.expand_path("../templates", __FILE__)
+
+ def copy_devise_migration
+ migration_template "migration.rb", "db/migrate/devise_google_authenticator_add_to_#{table_name}"
+ end
+ end
+ end
+end
View
17 lib/generators/active_record/templates/migration.rb
@@ -0,0 +1,17 @@
+class DeviseGoogleAuthenticatorAddTo<%= table_name.camelize %> < ActiveRecord::Migration
+ def self.up
+ change_table :<%= table_name %> do |t|
+ t.string :gauth_secret
+ t.string :gauth_enabled, :default => "f"
+ t.string :gauth_tmp
+ t.datetime :gauth_tmp_datetime
+ end
+
+ end
+
+ def self.down
+ change_table :<%= table_name %> do |t|
+ t.remove :gauth_secret, :gauth_enabled, :gauth_tmp, :gauth_tmp_datetime
+ end
+ end
+end
View
19 lib/generators/devise_google_authenticator/devise_google_authenticator_generator.rb
@@ -0,0 +1,19 @@
+module DeviseGoogleAuthenticator
+ module Generators
+ class DeviseGoogleAuthenticatorGenerator < Rails::Generators::NamedBase
+
+ namespace "devise_google_authenticator"
+
+ desc "Add :google_authenticatable directive in the given model, plus accessors. Also generate migration for ActiveRecord"
+
+ def inject_devise_google_authenticator_content
+ path = File.join("app","models","#{file_path}.rb")
+ inject_into_file(path, "google_authenticatable, :", :after => "devise :") if File.exists?(path)
+ inject_into_file(path, "gauth_enabled, :gauth_tmp, :gauth_tmp_datetime, :", :after => "attr_accessible :") if File.exists?(path)
+ end
+
+ hook_for :orm
+
+ end
+ end
+end
View
23 lib/generators/devise_google_authenticator/install_generator.rb
@@ -0,0 +1,23 @@
+module DeviseGoogleAuthenticator
+ module Generators # :nodoc:
+ # Install Generator
+ class InstallGenerator < Rails::Generators::Base
+ source_root File.expand_path("../../templates", __FILE__)
+
+ desc "Install the devise google authenticator extension"
+
+ def add_configs
+ inject_into_file "config/initializers/devise.rb", "\n # ==> Devise Google Authenticator Extension\n # Configure extension for devise\n\n" +
+ " # How long should the user have to enter their token. To change the default, uncomment and change the below:\n" +
+ " # config.ga_timeout = 3.minutes\n\n" +
+ " # Change time drift settings for valid token values. To change the default, uncomment and change the below:\n" +
+ " # config.ga_timedrift = 3\n\n" +
+ "\n", :before => /end[ |\n|]+\Z/
+ end
+
+ def copy_locale
+ copy_file "../../../config/locales/en.yml", "config/locales/devise.google_authenticator.en.yml"
+ end
+ end
+ end
+end
View
19 lib/generators/devise_google_authenticator/views_generator.rb
@@ -0,0 +1,19 @@
+require 'generators/devise/views_generator'
+
+module DeviseGoogleAuthenticator
+ module Generators
+ class ViewsGenerator < Rails::Generators::Base
+ desc 'Copies all Devise Google Authenticator views to your application.'
+
+ argument :scope, :required => false, :default => nil,
+ :desc => "The scope to copy views to"
+
+ include ::Devise::Generators::ViewPathTemplates
+ source_root File.expand_path("../../../../app/views/devise", __FILE__)
+ def copy_views
+ view_directory :checkga
+ view_directory :displayqr
+ end
+ end
+ end
+end
View
48 test/generators_test.rb
@@ -0,0 +1,48 @@
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
+require 'test_helper'
+require 'rails/generators'
+require 'generators/devise_invitable/devise_invitable_generator'
+
+class GeneratorsTest < ActiveSupport::TestCase
+ RAILS_APP_PATH = File.expand_path("../rails_app", __FILE__)
+
+ test "rails g should include the 3 generators" do
+ @output = `cd #{RAILS_APP_PATH} && rails g`
+ assert @output.match(%r|DeviseInvitable:\n devise_invitable\n devise_invitable:install\n devise_invitable:views|)
+ end
+
+ test "rails g devise_invitable:install" do
+ @output = `cd #{RAILS_APP_PATH} && rails g devise_invitable:install -p`
+ assert @output.match(%r{(inject|insert).+ config/initializers/devise\.rb\n})
+ assert @output.match(%r|create.+ config/locales/devise_invitable\.en\.yml\n|)
+ end
+
+ test "rails g devise_invitable:views not scoped" do
+ @output = `cd #{RAILS_APP_PATH} && rails g devise_invitable:views -p`
+ assert @output.match(%r|create.+ app/views/devise/invitations\n|)
+ assert @output.match(%r|create.+ app/views/devise/invitations/edit\.html\.erb\n|)
+ assert @output.match(%r|create.+ app/views/devise/invitations/new\.html\.erb\n|)
+ assert @output.match(%r|create.+ app/views/devise/mailer\n|)
+ assert @output.match(%r|create.+ app/views/devise/mailer/invitation_instructions\.html\.erb\n|)
+ end
+
+ test "rails g devise_invitable:views scoped" do
+ @output = `cd #{RAILS_APP_PATH} && rails g devise_invitable:views octopussies -p`
+ assert @output.match(%r|create.+ app/views/octopussies/invitations\n|)
+ assert @output.match(%r|create.+ app/views/octopussies/invitations/edit\.html\.erb\n|)
+ assert @output.match(%r|create.+ app/views/octopussies/invitations/new\.html\.erb\n|)
+ assert @output.match(%r|create.+ app/views/octopussies/mailer\n|)
+ assert @output.match(%r|create.+ app/views/octopussies/mailer/invitation_instructions\.html\.erb\n|)
+ end
+
+ test "rails g devise_invitable Octopussy" do
+ @output = `cd #{RAILS_APP_PATH} && rails g devise_invitable Octopussy -p`
+ assert @output.match(%r{(inject|insert).+ app/models/octopussy\.rb\n})
+ assert @output.match(%r|invoke.+ #{DEVISE_ORM}\n|)
+ if DEVISE_ORM == :active_record
+ assert @output.match(%r|create.+ db/migrate/\d{14}_devise_invitable_add_to_octopussies\.rb\n|)
+ elsif DEVISE_ORM == :mongoid
+ assert !@output.match(%r|create.+ db/migrate/\d{14}_devise_invitable_add_to_octopussies\.rb\n|)
+ end
+ end
+end
View
18 test/helper.rb
@@ -1,18 +0,0 @@
-require 'rubygems'
-require 'bundler'
-begin
- Bundler.setup(:default, :development)
-rescue Bundler::BundlerError => e
- $stderr.puts e.message
- $stderr.puts "Run `bundle install` to install missing gems"
- exit e.status_code
-end
-require 'test/unit'
-require 'shoulda'
-
-$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
-$LOAD_PATH.unshift(File.dirname(__FILE__))
-require 'devise_google_authenticator'
-
-class Test::Unit::TestCase
-end
View
207 test/integration/invitation_test.rb
@@ -0,0 +1,207 @@
+require 'test_helper'
+require 'integration_tests_helper'
+
+class InvitationTest < ActionDispatch::IntegrationTest
+ def teardown
+ Capybara.reset_sessions!
+ end
+
+ def send_invitation(url = new_user_invitation_path, &block)
+ visit url
+
+ fill_in 'user_email', :with => 'user@test.com'
+ yield if block_given?
+ click_button 'Send an invitation'
+ end
+
+ def set_password(options={}, &block)
+ unless options[:visit] == false
+ visit accept_user_invitation_path(:invitation_token => options[:invitation_token])
+ end
+
+ fill_in 'user_password', :with => '987654321'
+ fill_in 'user_password_confirmation', :with => '987654321'
+ yield if block_given?
+ click_button 'Set my password'
+ end
+
+ test 'not authenticated user should be able to send a free invitation' do
+ send_invitation new_free_invitation_path
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
+ end
+
+ test 'not authenticated user should not be able to send an invitation' do
+ get new_user_invitation_path
+ assert_redirected_to new_user_session_path
+ end
+
+ test 'authenticated user should be able to send an invitation' do
+ sign_in_as_user
+
+ send_invitation
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
+ end
+
+ test 'authenticated user with existing email should receive an error message' do
+ user = create_full_user
+ sign_in_as_user(user)
+ send_invitation do
+ fill_in 'user_email', :with => user.email
+ end
+
+ assert_equal user_invitation_path, current_path
+ assert page.has_css?("input[type=text][value='#{user.email}']")
+ assert page.has_css?('#error_explanation li', :text => 'Email has already been taken')
+ end
+
+ test 'authenticated user should not be able to visit edit invitation page' do
+ sign_in_as_user
+
+ visit accept_user_invitation_path
+
+ assert_equal root_path, current_path
+ end
+
+ test 'not authenticated user with invalid invitation token should not be able to set his password' do
+ user = User.invite!(:email => "valid@email.com")
+ user.accept_invitation!
+ visit accept_user_invitation_path(:invitation_token => 'invalid_token')
+
+ assert_equal root_path, current_path
+ assert page.has_css?('p#alert', :text => 'The invitation token provided is not valid!')
+ end
+
+ test 'not authenticated user with valid invitation token but invalid password should not be able to set his password' do
+ user = User.invite!(:email => "valid@email.com")
+ set_password :invitation_token => user.invitation_token do
+ fill_in 'Password confirmation', :with => 'other_password'
+ end
+ assert_equal user_invitation_path, current_path
+ assert page.has_css?('#error_explanation li', :text => 'Password doesn\'t match confirmation')
+ assert_blank user.encrypted_password
+ end
+
+ test 'not authenticated user with valid data should be able to change his password' do
+ user = User.invite!(:email => "valid@email.com")
+ set_password :invitation_token => user.invitation_token
+
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'Your password was set successfully. You are now signed in.')
+ assert user.reload.valid_password?('987654321')
+ end
+
+ test 'after entering invalid data user should still be able to set his password' do
+ user = User.invite!(:email => "valid@email.com")
+ set_password :invitation_token => user.invitation_token do
+ fill_in 'Password confirmation', :with => 'other_password'
+ end
+ assert_equal user_invitation_path, current_path
+ assert page.has_css?('#error_explanation')
+ assert_blank user.encrypted_password
+
+ set_password :visit => false
+ assert page.has_css?('p#notice', :text => 'Your password was set successfully. You are now signed in.')
+ assert user.reload.valid_password?('987654321')
+ end
+
+ test 'sign in user automatically after setting it\'s password' do
+ user = User.invite!(:email => "valid@email.com")
+ set_password :invitation_token => user.invitation_token
+ assert_equal root_path, current_path
+ end
+
+ test 'user with invites left should be able to send an invitation' do
+ User.stubs(:invitation_limit).returns(1)
+
+ user = create_full_user
+ user.invitation_limit = 1
+ user.save!
+ sign_in_as_user(user)
+
+ assert_difference 'User.count' do
+ send_invitation
+ end
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
+ user = User.find(user.id)
+ assert !user.has_invitations_left?
+ end
+
+ test 'user with no invites left should not be able to send an invitation' do
+ User.stubs(:invitation_limit).returns(1)
+
+ user = create_full_user
+ user.invitation_limit = 0
+ user.save!
+ sign_in_as_user(user)
+
+ assert_no_difference 'User.count' do
+ send_invitation
+ end
+ assert_equal user_invitation_path, current_path
+ assert page.has_css?('p#alert', :text => 'No invitations remaining')
+ end
+
+ test 'user with nil invitation_limit should default to User.invitation_limit' do
+ User.stubs(:invitation_limit).returns(3)
+
+ user = create_full_user
+ assert_nil user[:invitation_limit]
+ assert_equal 3, user.invitation_limit
+ sign_in_as_user(user)
+
+ send_invitation
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
+ user = User.find(user.id)
+ assert_equal 2, user.invitation_limit
+ end
+
+ test 'should not decrement invitation limit when trying to invite again a user which is invited' do
+ User.stubs(:invitation_limit).returns(3)
+
+ user = create_full_user
+ assert_nil user[:invitation_limit]
+ assert_equal 3, user.invitation_limit
+ sign_in_as_user(user)
+
+ send_invitation
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
+ user = User.find(user.id)
+ assert_equal 2, user.invitation_limit
+
+ send_invitation
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
+ user = User.find(user.id)
+ assert_equal 2, user.invitation_limit
+ end
+
+ test 'invited_by should be set when user invites someone' do
+ user = create_full_user
+ sign_in_as_user(user)
+ send_invitation
+
+ invited_user = User.where(:email => 'user@test.com').first
+ assert invited_user
+ assert_equal user, invited_user.invited_by
+ end
+
+ test 'authenticated user should not be able to send an admin invitation' do
+ sign_in_as_user
+
+ get new_admin_path
+ assert_redirected_to new_admin_session_path
+ end
+
+ test 'authenticated admin should be able to send an admin invitation' do
+ sign_in_as_user Admin.create(:email => 'admin@test.com', :password => '123456', :password_confirmation => '123456')
+
+ send_invitation new_admin_path
+ assert_equal root_path, current_path
+ assert page.has_css?('p#notice', :text => 'An invitation email has been sent to user@test.com.')
+ end
+end
View
48 test/integration_tests_helper.rb
@@ -0,0 +1,48 @@
+class ActionController::IntegrationTest
+
+ def warden
+ request.env['warden']
+ end
+
+ def create_full_user
+ @user ||= begin
+ user = User.create!(
+ :username => 'usertest',
+ :email => 'fulluser@test.com',
+ :password => '123456',
+ :password_confirmation => '123456',
+ :created_at => Time.now.utc
+ )
+ user.confirm!
+ user
+ end
+ end
+
+ def sign_in_as_user(user = nil)
+ user ||= create_full_user
+ resource_name = user.class.name.underscore
+ visit send("new_#{resource_name}_session_path")
+ fill_in "#{resource_name}_email", :with => user.email
+ fill_in "#{resource_name}_password", :with => user.password
+ click_button 'Sign in'
+ end
+
+ # Fix assert_redirect_to in integration sessions because they don't take into
+ # account Middleware redirects.
+ #
+ def assert_redirected_to(url)
+ assert [301, 302].include?(@integration_session.status),
+ "Expected status to be 301 or 302, got #{@integration_session.status}"
+
+ url = prepend_host(url)
+ location = prepend_host(@integration_session.headers["Location"])
+ assert_equal url, location
+ end
+
+ protected
+
+ def prepend_host(url)
+ url = "http://#{request.host}#{url}" if url[0] == ?/
+ url
+ end
+end
View
59 test/mailers/invitation_mail_test.rb
@@ -0,0 +1,59 @@
+require 'test_helper'
+require 'model_tests_helper'
+
+class InvitationMailTest < ActionMailer::TestCase
+
+ def setup
+ setup_mailer
+ Devise.mailer_sender = 'test@example.com'
+ end
+
+ def user
+ @user ||= User.invite!(:email => "valid@email.com")
+ end
+
+ def mail
+ @mail ||= begin
+ user
+ ActionMailer::Base.deliveries.last
+ end
+ end
+
+ test 'email sent after reseting the user password' do
+ assert_not_nil mail
+ end
+
+ test 'content type should be set to html' do
+ assert_equal 'text/html; charset=UTF-8', mail.content_type
+ end
+
+ test 'send invitation to the user email' do
+ assert_equal [user.email], mail.to
+ end
+
+ test 'setup sender from configuration' do
+ assert_equal ['test@example.com'], mail.from
+ end
+
+ test 'setup subject from I18n' do
+ store_translations :en, :devise => { :mailer => { :invitation_instructions => { :subject => 'Localized Invitation' } } } do
+ assert_equal 'Localized Invitation', mail.subject
+ end
+ end
+
+ test 'subject namespaced by model' do
+ store_translations :en, :devise => { :mailer => { :invitation_instructions => { :user_subject => 'User Invitation' } } } do
+ assert_equal 'User Invitation', mail.subject
+ end
+ end
+
+ test 'body should have user info' do
+ assert_match /#{user.email}/, mail.body.decoded
+ end
+
+ test 'body should have link to confirm the account' do
+ host = ActionMailer::Base.default_url_options[:host]
+ invitation_url_regexp = %r{<a href=\"http://#{host}/users/invitation/accept\?invitation_token=#{user.invitation_token}">}
+ assert_match invitation_url_regexp, mail.body.decoded
+ end
+end
View
33 test/model_tests_helper.rb
@@ -0,0 +1,33 @@
+class ActiveSupport::TestCase
+ def setup_mailer
+ ActionMailer::Base.deliveries = []
+ end
+
+ def store_translations(locale, translations, &block)
+ begin
+ I18n.backend.store_translations locale, translations
+ yield
+ ensure
+ I18n.reload!
+ end
+ end
+
+ # Helpers for creating new users
+ #
+ def generate_unique_email
+ @@email_count ||= 0
+ @@email_count += 1
+ "test#{@@email_count}@email.com"
+ end
+
+ def valid_attributes(attributes={})
+ { :email => generate_unique_email,
+ :password => '123456',
+ :password_confirmation => '123456' }.update(attributes)
+ end
+
+ def new_user(attributes={})
+ User.new(valid_attributes(attributes))
+ end
+
+end
View
372 test/models/invitable_test.rb
@@ -0,0 +1,372 @@
+require 'test_helper'
+require 'model_tests_helper'
+
+class InvitableTest < ActiveSupport::TestCase
+
+ def setup
+ setup_mailer
+ end
+
+ test 'should not generate invitation token after creating a record' do
+ assert_nil new_user.invitation_token
+ end
+
+ test 'should not regenerate invitation token each time' do
+ user = new_user
+ user.invite!
+ token = user.invitation_token
+ assert_not_nil user.invitation_token
+ assert_not_nil user.invitation_sent_at
+ 3.times do
+ user.invite!
+ assert_equal token, user.invitation_token
+ end
+ end
+
+ test 'should set invitation sent at each time' do
+ user = new_user
+ user.invite!
+ old_invitation_sent_at = 3.days.ago
+ user.update_attributes(:invitation_sent_at => old_invitation_sent_at)
+ 3.times do
+ user.invite!
+ assert_not_equal old_invitation_sent_at, user.invitation_sent_at
+ user.update_attributes(:invitation_sent_at => old_invitation_sent_at)
+ end
+ end
+
+ test 'should not regenerate invitation token even after the invitation token is not valid' do
+ User.stubs(:invite_for).returns(1.day)
+ user = new_user
+ user.invite!
+ token = user.invitation_token
+ user.invitation_sent_at = 3.days.ago
+ user.save
+ user.invite!
+ assert_equal token, user.invitation_token
+ end
+
+ test 'should test invitation sent at with invite_for configuration value' do
+ user = User.invite!(:email => "valid@email.com")
+
+ User.stubs(:invite_for).returns(nil)
+ user.invitation_sent_at = Time.now.utc
+ assert user.valid_invitation?
+
+ User.stubs(:invite_for).returns(nil)
+ user.invitation_sent_at = 1.year.ago
+ assert user.valid_invitation?
+
+ User.stubs(:invite_for).returns(0)
+ user.invitation_sent_at = Time.now.utc
+ assert user.valid_invitation?
+
+ User.stubs(:invite_for).returns(0)
+ user.invitation_sent_at = 1.day.ago
+ assert user.valid_invitation?
+
+ User.stubs(:invite_for).returns(1.day)
+ user.invitation_sent_at = Time.now.utc
+ assert user.valid_invitation?
+
+ User.stubs(:invite_for).returns(1.day)
+ user.invitation_sent_at = 1.day.ago
+ assert !user.valid_invitation?
+ end
+
+ test 'should never generate the same invitation token for different users' do
+ invitation_tokens = []
+ 3.times do
+ user = new_user
+ user.invite!
+ token = user.invitation_token
+ assert !invitation_tokens.include?(token)
+ invitation_tokens << token
+ end
+ end
+
+ test 'should return mail object' do
+ mail = User.invite_mail!(:email => 'valid@email.com')
+ assert mail.class.name == 'Mail::Message'
+ end
+
+ test 'should disallow login when invited' do
+ invited_user = User.invite!(:email => "valid@email.com")
+ assert !invited_user.valid_password?('1234')
+ end
+
+ test 'should set password and password confirmation from params' do
+ invited_user = User.invite!(:email => "valid@email.com")
+ user = User.accept_invitation!(:invitation_token => invited_user.invitation_token, :password => '123456789', :password_confirmation => '123456789')
+ assert user.valid_password?('123456789')
+ end
+
+ test 'should set password and save the record' do
+ user = User.invite!(:email => "valid@email.com")
+ old_encrypted_password = user.encrypted_password
+ user = User.accept_invitation!(:invitation_token => user.invitation_token, :password => '123456789', :password_confirmation => '123456789')
+ assert_not_equal old_encrypted_password, user.encrypted_password
+ end
+
+ test 'should clear invitation token and set invitation_accepted_at while accepting the password' do
+ user = User.invite!(:email => "valid@email.com")
+ assert_present user.invitation_token
+ assert_nil user.invitation_accepted_at
+ user.accept_invitation!
+ user.reload
+ assert_nil user.invitation_token
+ assert_present user.invitation_accepted_at
+ end
+
+ test 'should not clear invitation token or set accepted_at if record is invalid' do
+ user = User.invite!(:email => "valid@email.com")
+ assert_present user.invitation_token
+ assert_nil user.invitation_accepted_at
+ User.accept_invitation!(:invitation_token => user.invitation_token, :password => '123456789', :password_confirmation => '987654321')
+ user.reload
+ assert_present user.invitation_token
+ assert_nil user.invitation_accepted_at
+ end
+
+ test 'should clear invitation token while resetting the password' do
+ user = User.invite!(:email => "valid@email.com")
+ user.send(:generate_reset_password_token!)
+ assert_present user.reset_password_token
+ assert_present user.invitation_token
+ User.reset_password_by_token(:reset_password_token => user.reset_password_token, :password => '123456789', :password_confirmation => '123456789')
+ assert_nil user.reload.invitation_token
+ end
+
+ test 'should reset invitation token and send invitation by email' do
+ user = new_user
+ assert_difference('ActionMailer::Base.deliveries.size') do
+ token = user.invitation_token
+ user.invite!
+ assert_not_equal token, user.invitation_token
+ end
+ end
+
+ test 'should return a record with invitation token and no errors to send invitation by email' do
+ invited_user = User.invite!(:email => "valid@email.com")
+ assert invited_user.errors.blank?
+ assert_present invited_user.invitation_token
+ assert_equal 'valid@email.com', invited_user.email
+ assert invited_user.persisted?
+ end
+
+ test 'should set all attributes with no errors' do
+ invited_user = User.invite!(:email => "valid@email.com", :username => 'first name')
+ assert invited_user.errors.blank?
+ assert_equal 'first name', invited_user.username
+ assert invited_user.persisted?
+ end
+
+ test 'should not validate other attributes when validate_on_invite is disabled' do
+ validate_on_invite = User.validate_on_invite
+ User.validate_on_invite = false
+ invited_user = User.invite!(:email => "valid@email.com", :username => "a"*50)
+ assert invited_user.errors.empty?
+ User.validate_on_invite = validate_on_invite
+ end
+
+ test 'should validate other attributes when validate_on_invite is enabled' do
+ validate_on_invite = User.validate_on_invite
+ User.validate_on_invite = true
+ invited_user = User.invite!(:email => "valid@email.com", :username => "a"*50)
+ assert invited_user.errors[:username].present?
+ User.validate_on_invite = validate_on_invite
+ end
+
+ test 'should validate other attributes when validate_on_invite is enabled and email is not present' do
+ validate_on_invite = User.validate_on_invite
+ User.validate_on_invite = true
+ invited_user = User.invite!(:email => "", :username => "a"*50)
+ assert invited_user.errors[:email].present?
+ assert invited_user.errors[:username].present?
+ User.validate_on_invite = validate_on_invite
+ end
+
+ test 'should return a record with errors if user was found by e-mail' do
+ existing_user = User.new(:email => "valid@email.com")
+ existing_user.save(:validate => false)
+ user = User.invite!(:email => "valid@email.com")
+ assert_equal user, existing_user
+ assert_equal ['has already been taken'], user.errors[:email]
+ end
+
+ test 'should return a record with errors if user with pending invitation was found by e-mail' do
+ existing_user = User.invite!(:email => "valid@email.com")
+ user = User.invite!(:email => "valid@email.com")
+ assert_equal user, existing_user
+ assert_equal [], user.errors[:email]
+ resend_invitation = User.resend_invitation
+ begin
+ User.resend_invitation = false
+
+ user = User.invite!(:email => "valid@email.com")
+ assert_equal user, existing_user
+ assert_equal ['has already been taken'], user.errors[:email]
+ ensure
+ User.resend_invitation = resend_invitation
+ end
+ end
+
+ test 'should return a record with errors if user was found by e-mail with validate_on_invite' do
+ begin
+ validate_on_invite = User.validate_on_invite
+ User.validate_on_invite = true
+ existing_user = User.new(:email => "valid@email.com")
+ existing_user.save(:validate => false)
+ user = User.invite!(:email => "valid@email.com", :username => "a"*50)
+ assert_equal user, existing_user
+ assert_equal ['has already been taken'], user.errors[:email]
+ assert user.errors[:username].present?
+ ensure
+ User.validate_on_invite = validate_on_invite
+ end
+ end
+
+ test 'should return a new record with errors if e-mail is blank' do
+ invited_user = User.invite!(:email => '')
+ assert invited_user.new_record?
+ assert_equal ["can't be blank"], invited_user.errors[:email]
+ end
+
+ test 'should return a new record with errors if e-mail is invalid' do
+ invited_user = User.invite!(:email => 'invalid_email')
+ assert invited_user.new_record?
+ assert_equal ["is invalid"], invited_user.errors[:email]
+ end
+
+ test 'should set all attributes with errors if e-mail is invalid' do
+ invited_user = User.invite!(:email => "invalid_email.com", :username => 'first name')
+ assert invited_user.new_record?
+ assert_equal 'first name', invited_user.username
+ assert invited_user.errors.present?
+ end
+
+ test 'should find a user to set his password based on invitation_token' do
+ user = new_user
+ user.invite!
+ invited_user = User.accept_invitation!(:invitation_token => user.invitation_token)
+ assert_equal invited_user, user
+ end
+
+ test 'should return a new record with errors if no invitation_token is found' do
+ invited_user = User.accept_invitation!(:invitation_token => 'invalid_token')
+ assert invited_user.new_record?
+ assert_equal ['is invalid'], invited_user.errors[:invitation_token]
+ end
+
+ test 'should return a new record with errors if invitation_token is blank' do
+ invited_user = User.accept_invitation!(:invitation_token => '')
+ assert invited_user.new_record?
+ assert_equal ["can't be blank"], invited_user.errors[:invitation_token]
+ end
+
+ test 'should return record with errors if invitation_token has expired' do
+ User.stubs(:invite_for).returns(10.hours)
+ invited_user = User.invite!(:email => "valid@email.com")
+ invited_user.invitation_sent_at = 2.days.ago
+ invited_user.save(:validate => false)
+ user = User.accept_invitation!(:invitation_token => invited_user.invitation_token)
+ assert_equal user, invited_user
+ assert_equal ["is invalid"], user.errors[:invitation_token]
+ end
+
+ test 'should allow record modification using block' do
+ invited_user = User.invite!(:email => "valid@email.com", :username => "a"*50) do |u|
+ u.password = '123123'
+ u.password_confirmation = '123123'
+ end
+ assert_equal '123123', invited_user.reload.password
+ end
+
+ test 'should set successfully user password given the new password and confirmation' do
+ user = new_user(:password => nil, :password_confirmation => nil)
+ user.invite!
+
+ invited_user = User.accept_invitation!(
+ :invitation_token => user.invitation_token,
+ :password => 'new_password',
+ :password_confirmation => 'new_password'
+ )
+ user.reload
+
+ assert user.valid_password?('new_password')
+ end
+
+ test 'should return errors on other attributes even when password is valid' do
+ user = new_user(:password => nil, :password_confirmation => nil)
+ user.invite!
+
+ invited_user = User.accept_invitation!(
+ :invitation_token => user.invitation_token,
+ :password => 'new_password',
+ :password_confirmation => 'new_password',
+ :username => 'a'*50
+ )
+ assert invited_user.errors[:username].present?
+
+ assert !user.valid_password?('new_password')
+ end
+
+ test 'user.has_invitations_left? test' do
+ # By default with invitation_limit nil, users can send unlimited invitations
+ user = new_user
+ assert_nil user.invitation_limit
+ assert user.has_invitations_left?
+
+ # With invitation_limit set to a value, all users can send that many invitations
+ User.stubs(:invitation_limit).returns(2)
+ assert user.has_invitations_left?
+
+ # With an individual invitation_limit of 0, a user shouldn't be able to send an invitation
+ user.invitation_limit = 0
+ assert user.save
+ assert !user.has_invitations_left?
+
+ # With in invitation_limit of 2, a user should be able to send two invitations
+ user.invitation_limit = 2
+ assert user.save
+ assert user.has_invitations_left?
+ end
+
+ test 'should not send an invitation if we want to skip the invitation' do
+ assert_no_difference('ActionMailer::Base.deliveries.size') do
+ invited_user = User.invite!(:email => "valid@email.com", :username => "a"*50, :skip_invitation => true)
+ end
+ end
+
+ test 'should not send an invitation if we want to skip the invitation with block' do
+ assert_no_difference('ActionMailer::Base.deliveries.size') do
+ invited_user = User.invite!(:email => "valid@email.com", :username => "a"*50) do |u|
+ u.skip_invitation = true
+ end
+ end
+ end
+
+ test 'user.invite! should not send an invitation if we want to skip the invitation' do
+ user = new_user
+ user.skip_invitation = true
+ assert_no_difference('ActionMailer::Base.deliveries.size') do
+ user.invite!
+ end
+ end
+
+ test 'user.accept_invitation! should trigger callbacks' do
+ user = User.invite!(:email => "valid@email.com")
+ assert !user.callback_works
+ user.accept_invitation!
+ assert user.callback_works
+ end
+
+ test 'user.accept_invitation! should not trigger callbacks if validation fails' do
+ user = User.invite!(:email => "valid@email.com")
+ assert !user.callback_works
+ user.username='a'*50
+ user.accept_invitation!
+ assert !user.callback_works
+ end
+
+end
View
74 test/models_test.rb
@@ -0,0 +1,74 @@
+require 'test_helper'
+
+class Invitable < User
+ devise :invitable, :invite_for => 5.days, :validate_on_invite => true
+end
+
+class ModelsTest < ActiveSupport::TestCase
+ def include_module?(klass, mod)
+ klass.devise_modules.include?(mod) &&
+ klass.included_modules.include?(Devise::Models::const_get(mod.to_s.classify))
+ end
+
+ def assert_include_modules(klass, *modules)
+ modules.each do |mod|
+ assert include_module?(klass, mod), "#{klass} not include #{mod}"
+ end
+
+ (Devise::ALL - modules).each do |mod|
+ assert !include_module?(klass, mod), "#{klass} include #{mod}"
+ end
+ end
+
+ test 'should include Devise modules' do
+ assert_include_modules User, :database_authenticatable, :registerable, :validatable, :confirmable, :invitable, :recoverable
+ end
+
+ test 'should have a default value for invite_for' do
+ assert_equal 0, User.invite_for
+ end
+
+ test 'should have a default value for invitation_limit' do
+ assert_nil User.invitation_limit
+ end
+
+ test 'should have a default value for invite_key' do
+ assert !User.invite_key.nil?
+ end
+
+ test 'set a custom value for invite_for' do
+ old_invite_for = User.invite_for
+ User.invite_for = 5.days
+
+ assert_equal 5.days, User.invite_for
+
+ User.invite_for = old_invite_for
+ end
+
+ test 'set a custom value for invite_key' do
+ old_invite_key = User.invite_key
+ User.invite_key = :username
+
+ assert_equal :username, User.invite_key
+
+ User.invite_key = old_invite_key
+ end
+
+ test 'set a custom value for invitation_limit' do
+ old_invitation_limit = User.invitation_limit
+ User.invitation_limit = 2
+
+ assert_equal 2, User.invitation_limit
+
+ User.invitation_limit = old_invitation_limit
+ end
+
+ test 'set a default value for validate_on_invite' do
+ assert_equal true, Invitable.validate_on_invite
+ end
+
+ test 'invitable attributes' do
+ assert_nil User.new.invitation_token
+ assert_nil User.new.invitation_sent_at
+ end
+end
View
4 test/orm/active_record.rb
@@ -0,0 +1,4 @@
+ActiveRecord::Migration.verbose = false
+ActiveRecord::Base.logger = Logger.new(nil)
+
+ActiveRecord::Migrator.migrate(File.expand_path("../../rails_app/db/migrate/", __FILE__))
View
10 test/orm/mongoid.rb
@@ -0,0 +1,10 @@
+Mongoid.configure do |config|
+ config.master = Mongo::Connection.new('127.0.0.1', 27017).db("devise_invitable-test-suite")
+end
+
+class ActiveSupport::TestCase
+ setup do
+ User.delete_all
+ Admin.delete_all
+ end
+end
View
6 test/rails_app/app/controllers/admins_controller.rb
@@ -0,0 +1,6 @@
+class AdminsController < Devise::InvitationsController
+ protected
+ def authenticate_inviter!
+ authenticate_admin!(:force => true)
+ end
+end
View
3  test/rails_app/app/controllers/application_controller.rb
@@ -0,0 +1,3 @@
+class ApplicationController < ActionController::Base
+ protect_from_forgery
+end
View
6 test/rails_app/app/controllers/free_invitations_controller.rb
@@ -0,0 +1,6 @@
+class FreeInvitationsController < Devise::InvitationsController
+ protected
+ def authenticate_inviter!
+ # everyone can invite
+ end
+end
View
4 test/rails_app/app/controllers/home_controller.rb
@@ -0,0 +1,4 @@
+class HomeController < ApplicationController
+ def index
+ end
+end
View
12 test/rails_app/app/controllers/users_controller.rb
@@ -0,0 +1,12 @@
+class UsersController < ApplicationController
+ before_filter :authenticate_user!
+
+ def index
+ user_session[:cart] = "Cart"
+ end
+
+ def expire
+ user_session['last_request_at'] = 31.minutes.ago.utc
+ render :text => 'User will be expired on next request'
+ end
+end
View
2  test/rails_app/app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
View
5 test/rails_app/app/models/admin.rb
@@ -0,0 +1,5 @@
+class Admin < PARENT_MODEL_CLASS
+ include Mongoid::Document if DEVISE_ORM == :mongoid
+ devise :database_authenticatable, :validatable
+ include DeviseInvitable::Inviter
+end
View
5 test/rails_app/app/models/octopussy.rb
@@ -0,0 +1,5 @@
+# This model is here for the generators' specs
+class Octopussy < PARENT_MODEL_CLASS
+ include Mongoid::Document if DEVISE_ORM == :mongoid
+ devise :database_authenticatable, :validatable, :confirmable, :encryptable
+end
View
15 test/rails_app/app/models/user.rb
@@ -0,0 +1,15 @@
+class User < PARENT_MODEL_CLASS
+ if DEVISE_ORM == :mongoid
+ include Mongoid::Document
+ field :username, :type => String
+ end
+ devise :database_authenticatable, :registerable, :validatable, :confirmable, :invitable, :recoverable
+
+ attr_accessible :email, :username, :password, :password_confirmation, :skip_invitation
+ attr_accessor :callback_works
+ validates :username, :length => { :maximum => 20 }
+
+ after_invitation_accepted do |object|
+ object.callback_works = true
+ end
+end
View
12 test/rails_app/app/views/admins/new.html.erb
@@ -0,0 +1,12 @@
+<h2>Send invitation</h2>
+
+<%= form_for resource, :as => resource_name, :url => admin_path do |f| %>
+ <%= devise_error_messages! %>
+
+ <p><%= f.label :email %><br />
+ <%= f.text_field :email %></p>
+
+ <p><%= f.submit "Send an invitation" %></p>
+<% end %>
+
+<%= link_to "Home", after_sign_in_path_for(resource_name) %><br />
View
12 test/rails_app/app/views/free_invitations/new.html.erb
@@ -0,0 +1,12 @@
+<h2>Send invitation</h2>
+
+<%= form_for resource, :as => resource_name, :url => free_invitation_path do |f| %>
+ <%= devise_error_messages! %>
+
+ <p><%= f.label :email %><br />
+ <%= f.text_field :email %></p>
+
+ <p><%= f.submit "Send an invitation" %></p>
+<% end %>
+
+<%= link_to "Home", after_sign_in_path_for(resource_name) %><br />
View
0  test/rails_app/app/views/home/index.html.erb
No changes.
View
16 test/rails_app/app/views/layouts/application.html.erb
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>RailsApp</title>
+ <%= stylesheet_link_tag :all %>
+ <%= javascript_include_tag :defaults %>
+ <%= csrf_meta_tag %>
+</head>
+<body>
+
+<%= content_tag :p, flash[:notice], :id => 'notice' unless flash[:notice].blank? %>
+<%= content_tag :p, flash[:alert], :id => 'alert' unless flash[:alert].blank? %>
+<%= yield %>
+
+</body>
+</html>
View
15 test/rails_app/app/views/users/invitations/new.html.erb
@@ -0,0 +1,15 @@
+<h2>Send an invitation</h2>
+
+<%= form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => { :method => :post } do |f| %>
+ <%= devise_error_messages! %>
+
+ <p><%= f.label :username %></p>
+ <p><%= f.text_field :username %></p>
+
+ <p><%= f.label :email %></p>
+ <p><%= f.text_field :email %></p>
+
+ <p><%= f.submit "Send an invitation" %></p>
+<% end %>
+
+<%= link_to "Home", after_sign_in_path_for(resource_name) %><br />
View
4 test/rails_app/config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run TestApp::Application
View
25 test/rails_app/config/application.rb
@@ -0,0 +1,25 @@
+require File.expand_path('../boot', __FILE__)
+
+require "action_controller/railtie"
+require "action_mailer/railtie"
+require "active_resource/railtie"
+require "rails/test_unit/railtie"
+
+Bundler.require(:default, DEVISE_ORM) if defined?(Bundler)
+
+begin
+ require "#{DEVISE_ORM}/railtie"
+rescue LoadError
+end
+PARENT_MODEL_CLASS = DEVISE_ORM == :active_record ? ActiveRecord::Base : Object
+
+require "devise"
+require "devise_invitable"
+
+module RailsApp
+ class Application < Rails::Application
+ config.filter_parameters << :password
+
+ config.action_mailer.default_url_options = { :host => "localhost:3000" }
+ end
+end
View
11 test/rails_app/config/boot.rb
@@ -0,0 +1,11 @@
+unless defined?(DEVISE_ORM)
+ DEVISE_ORM = (ENV["DEVISE_ORM"] || :active_record).to_sym
+end
+
+begin
+ require File.expand_path("../../../../.bundle/environment", __FILE__)
+rescue LoadError
+ require 'rubygems'
+ require 'bundler'
+ Bundler.setup :default, :test, DEVISE_ORM
+end
View
22 test/rails_app/config/database.yml
@@ -0,0 +1,22 @@
+# SQLite version 3.x
+# gem install sqlite3-ruby (not necessary on OS X Leopard)
+development:
+ adapter: sqlite3
+ database: ":memory:"
+ pool: 5
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: sqlite3
+ database: ":memory:"
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
View
5 test/rails_app/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+RailsApp::Application.initialize!
View
26 test/rails_app/config/environments/development.rb
@@ -0,0 +1,26 @@
+RailsApp::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the webserver when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_view.debug_rjs = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+end
+
View
49 test/rails_app/config/environments/production.rb
@@ -0,0 +1,49 @@
+RailsApp::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # The production environment is meant for finished, "live" apps.
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Specifies the header that your server uses for sending files
+ config.action_dispatch.x_sendfile_header = "X-Sendfile"
+
+ # For nginx:
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
+
+ # If you have no front-end server that supports something like X-Sendfile,
+ # just comment this out and Rails will serve the files
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Use a different logger for distributed setups
+ # config.logger = SyslogLogger.new
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Disable Rails's static asset server
+ # In production, Apache or nginx will already do this
+ config.serve_static_assets = false
+
+ # Enable serving of images, stylesheets, and javascripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+end
View
35 test/rails_app/config/environments/test.rb
@@ -0,0 +1,35 @@
+RailsApp::Application.configure do
+ # Settings specified here will take precedence over those in config/environment.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
View
7 test/rails_app/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+Rails.backtrace_cleaner.remove_silencers!
View
184 test/rails_app/config/initializers/devise.rb
@@ -0,0 +1,184 @@
+# Use this hook to configure devise mailer, warden hooks and so forth. The first
+# four configuration values can also be set straight in your models.
+Devise.setup do |config|
+ # ==> Mailer Configuration
+ # Configure the e-mail address which will be shown in DeviseMailer.
+ config.mailer_sender = "please-change-me@config-initializers-devise.com"
+
+ # Configure the class responsible to send e-mails.
+ # config.mailer = "Devise::Mailer"
+
+ # ==> ORM configuration
+ # Load and configure the ORM. Supports :active_record (default) and
+ # :mongoid (bson_ext recommended) by default. Other ORMs may be
+ # available as additional gems.
+ require "devise/orm/#{DEVISE_ORM}"
+
+ # ==> Configuration for any authentication mechanism
+ # Configure which keys are used when authenticating an user. By default is
+ # just :email. You can configure it to use [:username, :subdomain], so for
+ # authenticating an user, both parameters are required. Remember that those
+ # parameters are used only when authenticating and not when retrieving from
+ # session. If you need permissions, you should implement that in a before filter.
+ # You can also supply hash where the value is a boolean expliciting if authentication
+ # should be aborted or not if the value is not present. By default is empty.
+ # config.authentication_keys = [ :email ]
+
+ # Configure parameters from the request object used for authentication. Each entry
+ # given should be a request method and it will automatically be passed to
+ # find_for_authentication method and considered in your model lookup. For instance,
+ # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
+ # The same considerations mentioned for authentication_keys also apply to request_keys.
+ # config.request_keys = []
+
+ # Tell if authentication through request.params is enabled. True by default.
+ # config.params_authenticatable = true
+
+ # Tell if authentication through HTTP Basic Auth is enabled. False by default.
+ # config.http_authenticatable = false
+
+ # If http headers should be returned for AJAX requests. True by default.
+ # config.http_authenticatable_on_xhr = true
+
+ # The realm used in Http Basic Authentication. "Application" by default.
+ # config.http_authentication_realm = "Application"
+
+ # ==> Configuration for :database_authenticatable
+ # For bcrypt, this is the cost for hashing the password and defaults to 10. If
+ # using other encryptors, it sets how many times you want the password re-encrypted.
+ config.stretches = 10
+
+ # ==> Configuration for :invitable
+ # The period the generated invitation token is valid, after
+ # this period, the invited resource won't be able to accept the invitation.
+ # When invite_for is 0 (the default), the invitation won't expire.
+ # config.invite_for = 2.weeks
+
+ # Number of invitations users can send.
+ # If invitation_limit is nil, users can send unlimited invitations.
+ # If invitation_limit is 0, users can't send invitations.
+ # If invitation_limit n > 0, users can send n invitations.
+ # Default: nil
+ # config.invitation_limit = 5
+
+ # The key to be used to check existing users when sending an invitation
+ # config.invite_key = :email
+
+ # ==> Configuration for :confirmable
+ # The time you want to give your user to confirm his account. During this time
+ # he will be able to access your application without confirming. Default is nil.
+ # When confirm_within is zero, the user won't be able to sign in without confirming.
+ # You can use this to let your user access some features of your application
+ # without confirming the account, but blocking it after a certain period
+ # (ie 2 days).
+ # config.confirm_within = 2.days
+
+ # ==> Configuration for :rememberable
+ # The time the user will be remembered without asking for credentials again.
+ # config.remember_for = 2.weeks
+
+ # If true, a valid remember token can be re-used between multiple browsers.
+ # config.remember_across_browsers = true
+
+ # If true, extends the user's remember period when remembered via cookie.
+ # config.extend_remember_period = false
+
+ # If true, uses the password salt as remember token. This should be turned
+ # to false if you are not using database authenticatable.
+ config.use_salt_as_remember_token = false
+
+ # ==> Configuration for :validatable
+ # Range for password length. Default is 6..20.
+ # config.password_length = 6..20
+
+ # Regex to use to validate the email address
+ # config.email_regexp = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
+
+ # ==> Configuration for :timeoutable
+ # The time you want to timeout the user session without activity. After this
+ # time the user will be asked for credentials again. Default is 30 minutes.
+ # config.timeout_in = 30.minutes
+
+ # ==> Configuration for :lockable
+ # Defines which strategy will be used to lock an account.
+ # :failed_attempts = Locks an account after a number of failed attempts to sign in.
+ # :none = No lock strategy. You should handle locking by yourself.
+ # config.lock_strategy = :failed_attempts
+
+ # Defines which strategy will be used to unlock an account.
+ # :email = Sends an unlock link to the user email
+ # :time = Re-enables login after a certain amount of time (see :unlock_in below)
+ # :both = Enables both strategies
+ # :none = No unlock strategy. You should handle unlocking by yourself.
+ # config.unlock_strategy = :both
+
+ # Number of authentication tries before locking an account if lock_strategy
+ # is failed attempts.
+ # config.maximum_attempts = 20
+
+ # Time interval to unlock the account if :time is enabled as unlock_strategy.
+ # config.unlock_in = 1.hour
+
+ # ==> Configuration for :encryptable
+ # Allow you to use another encryption algorithm besides bcrypt (default). You can use
+ # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
+ # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
+ # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
+ # REST_AUTH_SITE_KEY to pepper)
+ # config.encryptor = :sha1
+
+ # Setup a pepper to generate the encrypted password.
+ config.pepper = "c9ed39f2a5faea59e2f9634cd5466703ead30a1fe25ab08cad00fe4d41d23467401fd731eaca1b1326d97b3065217daa81a18368ecc435978e6e868442b753ac"
+
+ # ==> Configuration for :token_authenticatable
+ # Defines name of the authentication token params key
+ # config.token_authentication_key = :auth_token
+
+ # If true, authentication through token does not store user in session and needs
+ # to be supplied on each request. Useful if you are using the token as API token.
+ # config.stateless_token = false
+
+ # ==> Scopes configuration
+ # Turn scoped views on. Before rendering "sessions/new", it will first check for
+ # "users/sessions/new". It's turned off by default because it's slower if you
+ # are using only default views.
+ # config.scoped_views = false
+
+ # Configure the default scope given to Warden. By default it's the first
+ # devise role declared in your routes (usually :user).
+ # config.default_scope = :user
+
+ # Configure sign_out behavior.
+ # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope).
+ # The default is true, which means any logout action will sign out all active scopes.
+ # config.sign_out_all_scopes = true
+
+ # ==> Navigation configuration
+ # Lists the formats that should be treated as navigational. Formats like
+ # :html, should redirect to the sign in page when the user does not have
+ # access, but formats like :xml or :json, should return 401.
+ # If you have any extra navigational formats, like :iphone or :mobile, you
+ # should add them to the navigational formats lists. Default is [:html]
+ # config.navigational_formats = [:html, :iphone]
+
+ # The default HTTP method used to sign out a resource. Default is :get.
+ # config.sign_out_via = :get
+
+ # ==> OAuth2
+ # Add a new OAuth2 provider. Check the README for more information on setting
+ # up on your models and hooks. By default this is not set.
+ # config.oauth :github, 'APP_ID', 'APP_SECRET',
+ # :site => 'https://github.com/',
+ # :authorize_path => '/login/oauth/authorize',
+ # :access_token_path => '/login/oauth/access_token',
+ # :scope => %w(user public_repo)
+
+ # ==> Warden configuration
+ # If you want to use other strategies, that are not supported by Devise, or
+ # change the failure app, you can configure them inside the config.warden block.
+ #
+ # config.warden do |manager|
+ # manager.failure_app = AnotherApp
+ # manager.default_strategies(:scope => :user).unshift :some_external_strategy
+ # end
+end
View
10 test/rails_app/config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
View
5 test/rails_app/config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone