revise 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/padrino/revise.rb +8 -0
- data/lib/revise.rb +110 -0
- data/lib/revise/controllers/accounts.rb +59 -0
- data/lib/revise/controllers/confirmations.rb +26 -0
- data/lib/revise/controllers/invitations.rb +55 -0
- data/lib/revise/controllers/main.rb +13 -0
- data/lib/revise/controllers/recovery.rb +60 -0
- data/lib/revise/controllers/sessions.rb +22 -0
- data/lib/revise/core_ext/module.rb +52 -0
- data/lib/revise/core_ext/string.rb +14 -0
- data/lib/revise/helpers/authentication.rb +21 -0
- data/lib/revise/helpers/core.rb +48 -0
- data/lib/revise/inviter.rb +40 -0
- data/lib/revise/locale/en.yml +11 -0
- data/lib/revise/mailers/confirmable.rb +18 -0
- data/lib/revise/mailers/invitable.rb +18 -0
- data/lib/revise/mailers/recoverable.rb +18 -0
- data/lib/revise/models.rb +99 -0
- data/lib/revise/models/authenticatable.rb +137 -0
- data/lib/revise/models/confirmable.rb +236 -0
- data/lib/revise/models/database_authenticatable.rb +107 -0
- data/lib/revise/models/invitable.rb +237 -0
- data/lib/revise/models/recoverable.rb +99 -0
- data/lib/revise/orm/mongo_mapper.rb +2 -0
- data/lib/revise/param_filter.rb +41 -0
- data/test/controllers/accounts_test.rb +148 -0
- data/test/controllers/invitations_test.rb +105 -0
- data/test/controllers/sessions_test.rb +35 -0
- data/test/factories/account.rb +15 -0
- data/test/models/account_test.rb +45 -0
- data/test/models/invitation_test.rb +423 -0
- data/test/test.rake +13 -0
- data/test/test_config.rb +23 -0
- metadata +181 -0
data/lib/revise.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'revise/core_ext/string'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext/numeric/time'
|
4
|
+
require 'orm_adapter'
|
5
|
+
require 'set'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'revise/core_ext/module'
|
8
|
+
require 'revise/helpers/core'
|
9
|
+
require 'padrino/revise'
|
10
|
+
|
11
|
+
module Revise
|
12
|
+
MODULES = {}
|
13
|
+
HELPERS = []
|
14
|
+
CONTROLERS = []
|
15
|
+
MAILERS = []
|
16
|
+
|
17
|
+
autoload :Models, 'revise/models'
|
18
|
+
autoload :ParamFilter, 'revise/param_filter'
|
19
|
+
|
20
|
+
module Controllers
|
21
|
+
autoload :Accounts, 'revise/controllers/accounts'
|
22
|
+
autoload :Confirmations, 'revise/controllers/confirmations'
|
23
|
+
autoload :Main, 'revise/controllers/main'
|
24
|
+
autoload :Recovery, 'revise/controllers/recovery'
|
25
|
+
autoload :Sessions, 'revise/controllers/sessions'
|
26
|
+
autoload :Invitations, 'revise/controllers/invitations'
|
27
|
+
end
|
28
|
+
|
29
|
+
module Helpers
|
30
|
+
autoload :Authentication, 'revise/helpers/authentication'
|
31
|
+
end
|
32
|
+
|
33
|
+
module Mailers
|
34
|
+
autoload :Confirmable, 'revise/mailers/confirmable'
|
35
|
+
autoload :Recoverable, 'revise/mailers/recoverable'
|
36
|
+
end
|
37
|
+
|
38
|
+
module Models
|
39
|
+
autoload :Authenticatable, 'revise/models/authenticatable'
|
40
|
+
autoload :Confirmable, 'revise/models/confirmable'
|
41
|
+
autoload :DatabaseAuthenticatable, 'revise/models/database_authenticatable'
|
42
|
+
autoload :Recoverable, 'revise/models/recoverable'
|
43
|
+
autoload :Invitable, 'revise/models/invitable'
|
44
|
+
end
|
45
|
+
|
46
|
+
mattr_accessor :app
|
47
|
+
mattr_accessor :mailer_from
|
48
|
+
@@mailer_from = nil
|
49
|
+
|
50
|
+
mattr_accessor :pepper
|
51
|
+
@@pepper = nil
|
52
|
+
|
53
|
+
mattr_accessor :stretches
|
54
|
+
@@stretches = 10
|
55
|
+
|
56
|
+
mattr_accessor :allow_unconfirmed_access_for
|
57
|
+
@@allow_unconfirmed_access_for = 0.days
|
58
|
+
|
59
|
+
mattr_accessor :confirm_within
|
60
|
+
@@confirm_within = nil
|
61
|
+
|
62
|
+
mattr_accessor :confirmation_keys
|
63
|
+
@@confirmation_keys = [:email]
|
64
|
+
|
65
|
+
mattr_accessor :reconfirmable
|
66
|
+
@@reconfirmable = false
|
67
|
+
|
68
|
+
mattr_accessor :reset_password_keys
|
69
|
+
@@reset_password_keys = [:email]
|
70
|
+
|
71
|
+
mattr_accessor :reset_password_within
|
72
|
+
@@reset_password_within = 6.hours
|
73
|
+
|
74
|
+
mattr_accessor :case_insensitive_keys
|
75
|
+
@@case_insensitive_keys = [:email]
|
76
|
+
|
77
|
+
mattr_accessor :strip_whitespace_keys
|
78
|
+
@@strip_whitespace_keys = []
|
79
|
+
|
80
|
+
mattr_accessor :invite_for
|
81
|
+
@@invite_for = 0
|
82
|
+
|
83
|
+
mattr_accessor :validate_on_invite
|
84
|
+
@@validate_on_invite = false
|
85
|
+
|
86
|
+
mattr_accessor :invitation_limit
|
87
|
+
@@invitation_limit = 5
|
88
|
+
|
89
|
+
mattr_accessor :email_regexp
|
90
|
+
@@email_regexp = /\A[^@]+@[^@]+\z/
|
91
|
+
|
92
|
+
mattr_accessor :invite_key
|
93
|
+
@@invite_key = {:email => @@email_regexp}
|
94
|
+
|
95
|
+
mattr_accessor :resend_invitation
|
96
|
+
@@resend_invitation = true
|
97
|
+
|
98
|
+
mattr_accessor :invited_by_class_name
|
99
|
+
@@invited_by_class_name = nil
|
100
|
+
|
101
|
+
def self.setup
|
102
|
+
yield self
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
require 'revise/models'
|
107
|
+
require 'revise/orm/mongo_mapper'
|
108
|
+
require 'padrino/revise'
|
109
|
+
|
110
|
+
I18n.load_path += Dir["#{File.dirname(__FILE__)}/revise/locale/**/*.yml"]
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Revise
|
2
|
+
module Controllers
|
3
|
+
module Accounts
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.controllers :accounts do
|
6
|
+
before(:show, :edit, :update, :destroy) do
|
7
|
+
@account = current_account
|
8
|
+
halt 403, 'Login first' unless @account
|
9
|
+
end
|
10
|
+
|
11
|
+
get :new, :map => '/accounts/new', :priority => :low do
|
12
|
+
@account = Account.new
|
13
|
+
render 'accounts/new'
|
14
|
+
end
|
15
|
+
|
16
|
+
post :create, :map => '/accounts', :priority => :low do
|
17
|
+
if current_account
|
18
|
+
flash[:notice] = "You are already registered"
|
19
|
+
redirect_back_or_default(url(:main, :index))
|
20
|
+
end
|
21
|
+
|
22
|
+
@account = Account.new(params[:account])
|
23
|
+
if @account.save
|
24
|
+
redirect url(:main, :index)
|
25
|
+
else
|
26
|
+
status 400
|
27
|
+
render 'accounts/new'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
get :edit, :map => '/accounts/edit', :priority => :low do
|
32
|
+
respond(@account)
|
33
|
+
end
|
34
|
+
|
35
|
+
put :update, :map => '/accounts', :priority => :low do
|
36
|
+
@account.update_attributes!(params[:account])
|
37
|
+
respond(@account, url(:accounts, :edit))
|
38
|
+
end
|
39
|
+
|
40
|
+
delete :destroy, :map => '/accounts', :priority => :low do
|
41
|
+
if current_account.respond_to?(:archive)
|
42
|
+
destroyed = current_account.archive
|
43
|
+
else
|
44
|
+
destroyed = current_account.destroy
|
45
|
+
end
|
46
|
+
|
47
|
+
if destroyed
|
48
|
+
flash[:notice] = "You have successfully deleted your account. It is disabled for now and will be completely
|
49
|
+
removed within 48 hours."
|
50
|
+
else
|
51
|
+
flash[:warning] = "Couldn't Delete Your Account"
|
52
|
+
end
|
53
|
+
redirect_back_or_default(url(:main, :index))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Revise
|
2
|
+
module Controllers
|
3
|
+
module Confirmations
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.controllers :accounts do
|
6
|
+
get :confirm, :map => '/accounts/confirm/:confirmation_token', :priority => :low do
|
7
|
+
account = Account.confirm_by_token(params[:confirmation_token])
|
8
|
+
if account
|
9
|
+
if account.errors.empty?
|
10
|
+
flash[:notice] = "Account Confirmed Please Login"
|
11
|
+
render 'accounts/confirmed'
|
12
|
+
else
|
13
|
+
flash[:error] = account.errors.full_messages()
|
14
|
+
render 'accounts/confirmed'
|
15
|
+
end
|
16
|
+
else
|
17
|
+
flash[:error] = "Token Does Not Exist"
|
18
|
+
status 404
|
19
|
+
render 'accounts/confirmation_token_404'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Revise
|
2
|
+
module Controllers
|
3
|
+
module Invitations
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.controllers :invitations do
|
6
|
+
before(:new, :create) do
|
7
|
+
halt 403, 'Login first' unless current_account
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:create) do
|
11
|
+
halt 403, "You've no invitations left" unless current_account.has_invitations_left? || current_account.role?(:admin)
|
12
|
+
end
|
13
|
+
|
14
|
+
get :new, :map => '/invitations/new', :priority => :low do
|
15
|
+
@account = Account.new
|
16
|
+
render 'invitations/new'
|
17
|
+
end
|
18
|
+
|
19
|
+
post :create, :map => '/invitations', :priority => :low do
|
20
|
+
@account = Account.invite!(params[:account], current_account)
|
21
|
+
|
22
|
+
if @account.errors.empty?
|
23
|
+
flash[:notice] = "You've invited #{params[:account][:email]}"
|
24
|
+
redirect url(:main, :index)
|
25
|
+
else
|
26
|
+
status 400
|
27
|
+
render 'invitations/new'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
get :edit, :map => '/invitations/:invitation_token', :priority => :low do
|
32
|
+
if params[:invitation_token] && @account = Account.to_adapter.find_first(:invitation_token => params[:invitation_token])
|
33
|
+
render 'invitations/edit'
|
34
|
+
else
|
35
|
+
status 404
|
36
|
+
flash.now[:alert] = "Invitation Token Invalid"
|
37
|
+
render 'invitations/404'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
put :update, :map => '/invitations/:invitation_token', :priority => :low do
|
42
|
+
@account = Account.accept_invitation!(params[:account])
|
43
|
+
|
44
|
+
if @account.errors.empty?
|
45
|
+
flash[:notice] = "Accepted Invitation. Now login"
|
46
|
+
respond(@account, url(:sessions, :new))
|
47
|
+
else
|
48
|
+
render 'invitations/edit'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Revise
|
2
|
+
module Controllers
|
3
|
+
module Recovery
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.controllers :accounts do
|
6
|
+
before do
|
7
|
+
halt 403, "You're already logged in?" if current_account
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:forgot_password, :reset_password) do
|
11
|
+
@account = Account.new
|
12
|
+
end
|
13
|
+
|
14
|
+
get :forgot_password, :map => '/accounts/forgot-password', :priority => :low do
|
15
|
+
render 'accounts/forgot_pass'
|
16
|
+
end
|
17
|
+
|
18
|
+
post :send_reset_password_instructions, :map => '/accounts/forgot-password', :priority => :low do
|
19
|
+
account = Account.find_by_email(params[:email])
|
20
|
+
|
21
|
+
if account
|
22
|
+
account.send_reset_password_instructions
|
23
|
+
flash[:notice] = "You've been sent an email with instructions"
|
24
|
+
else
|
25
|
+
status 404
|
26
|
+
flash[:warning] = "No email by that name was found"
|
27
|
+
end
|
28
|
+
|
29
|
+
redirect(url(:main, :index))
|
30
|
+
end
|
31
|
+
|
32
|
+
get :reset_password, :map => '/accounts/reset-password/:reset_password_token', :priority => :low do
|
33
|
+
flash[:error] = "Reset Password Token Does Not Exist" unless Account.find_by_reset_password_token(params[:reset_password_token])
|
34
|
+
render 'accounts/reset_password'
|
35
|
+
end
|
36
|
+
|
37
|
+
put :new_password, :map => '/accounts/reset-password/:reset_password_token', :priority => :low do
|
38
|
+
account = Account.reset_password_by_token({:password => params[:password],
|
39
|
+
:password_confirmation => params[:password_confirmation],
|
40
|
+
:reset_password_token => params[:reset_password_token]})
|
41
|
+
|
42
|
+
if account
|
43
|
+
if account.errors.empty?
|
44
|
+
flash[:notice] = "Account Password Has Been Reset"
|
45
|
+
redirect(url(:main, :index))
|
46
|
+
else
|
47
|
+
flash[:error] = account.errors.full_messages()
|
48
|
+
redirect(url(:main, :index))
|
49
|
+
end
|
50
|
+
else
|
51
|
+
flash[:error] = "Reset Password Token Does Not Exist"
|
52
|
+
status 404
|
53
|
+
render 'accounts/reset_password_token_404'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Revise
|
2
|
+
module Controllers
|
3
|
+
module Sessions
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.controllers :sessions do
|
6
|
+
get :new, :map => '/sessions/new', :priority => :low do
|
7
|
+
render "/sessions/new", nil, :layout => false
|
8
|
+
end
|
9
|
+
|
10
|
+
post :create, :map => '/sessions', :priority => :low do
|
11
|
+
authenticate()
|
12
|
+
end
|
13
|
+
|
14
|
+
delete :destroy, :map => '/sessions', :priority => :low do
|
15
|
+
set_current_account()
|
16
|
+
redirect url(:sessions, :new)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
2
|
+
|
3
|
+
class Module
|
4
|
+
def mattr_reader(*syms)
|
5
|
+
options = syms.extract_options!
|
6
|
+
syms.each do |sym|
|
7
|
+
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
|
8
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
9
|
+
@@#{sym} = nil unless defined? @@#{sym}
|
10
|
+
|
11
|
+
def self.#{sym}
|
12
|
+
@@#{sym}
|
13
|
+
end
|
14
|
+
EOS
|
15
|
+
|
16
|
+
unless options[:instance_reader] == false || options[:instance_accessor] == false
|
17
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
18
|
+
def #{sym}
|
19
|
+
@@#{sym}
|
20
|
+
end
|
21
|
+
EOS
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def mattr_writer(*syms)
|
27
|
+
options = syms.extract_options!
|
28
|
+
syms.each do |sym|
|
29
|
+
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
|
30
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
31
|
+
def self.#{sym}=(obj)
|
32
|
+
@@#{sym} = obj
|
33
|
+
end
|
34
|
+
EOS
|
35
|
+
|
36
|
+
unless options[:instance_writer] == false || options[:instance_accessor] == false
|
37
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
38
|
+
def #{sym}=(obj)
|
39
|
+
@@#{sym} = obj
|
40
|
+
end
|
41
|
+
EOS
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Extends the module object with module and instance accessors for class attributes,
|
47
|
+
# just like the native attr* accessors for instance attributes.
|
48
|
+
def mattr_accessor(*syms)
|
49
|
+
mattr_reader(*syms)
|
50
|
+
mattr_writer(*syms)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class String
|
2
|
+
def self.friendly_token
|
3
|
+
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.secure_compare(a, b)
|
7
|
+
return false if a.blank? || b.blank? || a.bytesize != b.bytesize
|
8
|
+
l = a.unpack "C#{a.bytesize}"
|
9
|
+
|
10
|
+
res = 0
|
11
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
12
|
+
res == 0
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Revise
|
2
|
+
module Helpers
|
3
|
+
module Authentication
|
4
|
+
def authenticate()
|
5
|
+
if account = Account.authenticate(params[:email], params[:password])
|
6
|
+
set_current_account(account)
|
7
|
+
redirect url(:main, :index)
|
8
|
+
elsif Padrino.env == :development && params[:bypass] || Padrino.env == :test && params[:bypass]
|
9
|
+
account = Account.find_by_email(params[:email])
|
10
|
+
halt 400, "Email does not exist." unless account
|
11
|
+
set_current_account(account)
|
12
|
+
redirect url(:main, :index)
|
13
|
+
else
|
14
|
+
params[:email], params[:password] = h(params[:email]), h(params[:password])
|
15
|
+
flash[:warning] = "Login or password wrong."
|
16
|
+
redirect url(:sessions, :new)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|