revise 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|