authpwn_rails 0.12.0 → 0.12.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/.travis.yml +7 -2
- data/VERSION +1 -1
- data/app/models/credentials/password.rb +16 -8
- data/app/models/credentials/token.rb +8 -0
- data/app/models/tokens/email_verification.rb +3 -0
- data/app/models/tokens/password_reset.rb +5 -2
- data/app/models/tokens/session_uid.rb +54 -0
- data/authpwn_rails.gemspec +8 -2
- data/lib/authpwn_rails.rb +3 -2
- data/lib/authpwn_rails/current_user.rb +1 -10
- data/lib/authpwn_rails/engine.rb +2 -2
- data/lib/authpwn_rails/expires.rb +23 -0
- data/lib/authpwn_rails/generators/all_generator.rb +9 -4
- data/lib/authpwn_rails/generators/templates/credential.rb +1 -1
- data/lib/authpwn_rails/generators/templates/credentials.yml +16 -0
- data/lib/authpwn_rails/generators/templates/initializer.rb +18 -0
- data/lib/authpwn_rails/generators/templates/session/forbidden.html.erb +1 -1
- data/lib/authpwn_rails/generators/templates/session/home.html.erb +1 -1
- data/lib/authpwn_rails/generators/templates/session/new.html.erb +3 -3
- data/lib/authpwn_rails/generators/templates/session/welcome.html.erb +1 -1
- data/lib/authpwn_rails/generators/templates/session_controller.rb +13 -4
- data/lib/authpwn_rails/generators/templates/session_controller_test.rb +12 -2
- data/lib/authpwn_rails/generators/templates/session_mailer.rb +3 -3
- data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.html.erb +3 -3
- data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.html.erb +3 -3
- data/lib/authpwn_rails/generators/templates/session_mailer_test.rb +4 -4
- data/lib/authpwn_rails/routes.rb +4 -4
- data/lib/authpwn_rails/session.rb +31 -8
- data/lib/authpwn_rails/session_controller.rb +27 -18
- data/lib/authpwn_rails/test_extensions.rb +16 -6
- data/lib/authpwn_rails/user_model.rb +10 -10
- data/test/cookie_controller_test.rb +165 -16
- data/test/credentials/email_verification_token_test.rb +11 -11
- data/test/credentials/password_credential_test.rb +31 -12
- data/test/credentials/session_uid_token_test.rb +98 -0
- data/test/credentials/token_crendential_test.rb +46 -12
- data/test/helpers/db_setup.rb +6 -5
- data/test/helpers/routes.rb +5 -2
- data/test/initializer_test.rb +18 -0
- data/test/session_controller_api_test.rb +127 -53
- data/test/test_extensions_test.rb +41 -0
- data/test/test_helper.rb +3 -0
- data/test/user_test.rb +11 -10
- metadata +9 -3
data/.travis.yml
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.12.
|
1
|
+
0.12.1
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# :namespace
|
2
2
|
module Credentials
|
3
|
-
|
3
|
+
|
4
4
|
# Associates a password with the user account.
|
5
5
|
class Password < ::Credential
|
6
6
|
# Virtual attribute: the user's password.
|
@@ -11,9 +11,16 @@ class Password < ::Credential
|
|
11
11
|
# Virtual attribute: confirmation for the user's password.
|
12
12
|
attr_accessor :password_confirmation
|
13
13
|
|
14
|
-
# A user can have a single password
|
14
|
+
# A user can have a single password.
|
15
15
|
validates :user_id, :uniqueness => true
|
16
16
|
|
17
|
+
# Passwords can expire, if users don't change them often enough.
|
18
|
+
include Authpwn::Expires
|
19
|
+
# Passwords don't expire by default, because it is non-trivial to get e-mail
|
20
|
+
# delivery working in Rails, which is necessary for recovering from expired
|
21
|
+
# passwords.
|
22
|
+
self.expires_after = nil
|
23
|
+
|
17
24
|
# Compares a plain-text password against the password hash in this credential.
|
18
25
|
#
|
19
26
|
# Returns +true+ for a match, +false+ otherwise.
|
@@ -21,16 +28,17 @@ class Password < ::Credential
|
|
21
28
|
return false unless key
|
22
29
|
key == self.class.hash_password(password, key.split('|', 2).first)
|
23
30
|
end
|
24
|
-
|
31
|
+
|
25
32
|
# Compares a plain-text password against the password hash in this credential.
|
26
33
|
#
|
27
34
|
# Returns the authenticated User instance, or a symbol indicating the reason
|
28
35
|
# why the (potentially valid) password was rejected.
|
29
36
|
def authenticate(password)
|
37
|
+
return :expired if expired?
|
30
38
|
return :invalid unless check_password(password)
|
31
39
|
user.auth_bounce_reason(self) || user
|
32
40
|
end
|
33
|
-
|
41
|
+
|
34
42
|
# Password virtual attribute.
|
35
43
|
def password=(new_password)
|
36
44
|
@password = new_password
|
@@ -50,7 +58,7 @@ class Password < ::Credential
|
|
50
58
|
def self.authenticate_email(email, password)
|
51
59
|
user = Credentials::Email.authenticate email
|
52
60
|
return user if user.is_a? Symbol
|
53
|
-
|
61
|
+
|
54
62
|
credential = user.credentials.find { |c| c.kind_of? Credentials::Password }
|
55
63
|
credential ? credential.authenticate(password) : :invalid
|
56
64
|
end
|
@@ -59,14 +67,14 @@ class Password < ::Credential
|
|
59
67
|
def self.hash_password(password, salt)
|
60
68
|
salt + '|' + Digest::SHA2.hexdigest(password + salt)
|
61
69
|
end
|
62
|
-
|
70
|
+
|
63
71
|
# Generates a random salt value.
|
64
72
|
def self.random_salt
|
65
73
|
[(0...12).map { |i| 1 + rand(255) }.pack('C*')].pack('m').strip
|
66
74
|
end
|
67
|
-
|
75
|
+
|
68
76
|
# Forms can only change the plain-text password fields.
|
69
|
-
attr_accessible :password, :password_confirmation
|
77
|
+
attr_accessible :password, :password_confirmation
|
70
78
|
end # class Credentials::Password
|
71
79
|
|
72
80
|
end # namespace Credentials
|
@@ -29,6 +29,10 @@ class Token < ::Credential
|
|
29
29
|
validates :name, :format => /^[A-Za-z0-9\_\-]+$/, :presence => true,
|
30
30
|
:uniqueness => true
|
31
31
|
|
32
|
+
# Tokens can expire. This is a good idea most of the time, because token
|
33
|
+
# codes are supposed to be used quickly.
|
34
|
+
include Authpwn::Expires
|
35
|
+
|
32
36
|
# Authenticates a user using a secret token code.
|
33
37
|
#
|
34
38
|
# The token will be spent on successful authentication. One-time tokens are
|
@@ -67,6 +71,10 @@ class Token < ::Credential
|
|
67
71
|
# Returns the authenticated User instance, or a symbol indicating the reason
|
68
72
|
# why the (potentially valid) token code was rejected.
|
69
73
|
def authenticate
|
74
|
+
if expired?
|
75
|
+
destroy
|
76
|
+
return :invalid
|
77
|
+
end
|
70
78
|
if bounce = user.auth_bounce_reason(self)
|
71
79
|
return bounce
|
72
80
|
end
|
@@ -12,6 +12,9 @@ class EmailVerification < OneTime
|
|
12
12
|
alias_attribute :email, :key
|
13
13
|
validates :email, :presence => true
|
14
14
|
|
15
|
+
# Decent compromise between convenience and security.
|
16
|
+
self.expires_after = 3.days
|
17
|
+
|
15
18
|
# Creates a token with a random code that verifies the given e-mail address.
|
16
19
|
def self.random_for(email_credential)
|
17
20
|
super email_credential.user, email_credential.email, self
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# :namespace
|
2
2
|
module Tokens
|
3
|
-
|
3
|
+
|
4
4
|
# Lets the user to change their password without knowing the old one.
|
5
5
|
class PasswordReset < OneTime
|
6
|
+
# Decent compromise between convenience and security.
|
7
|
+
self.expires_after = 3.days
|
8
|
+
|
6
9
|
# Blanks the user's old password, so the new password form won't ask for it.
|
7
10
|
#
|
8
11
|
# Returns the token instance.
|
@@ -14,7 +17,7 @@ class PasswordReset < OneTime
|
|
14
17
|
super
|
15
18
|
end
|
16
19
|
end
|
17
|
-
|
20
|
+
|
18
21
|
# The credential that is removed by this token.
|
19
22
|
#
|
20
23
|
# This method might return nil if a user initiates password recovery multiple
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# :namespace
|
2
|
+
module Tokens
|
3
|
+
|
4
|
+
class SessionUid < Credentials::Token
|
5
|
+
# The session UID.
|
6
|
+
alias_attribute :suid, :name
|
7
|
+
|
8
|
+
# The IP address and User-Agent string of the browser using this session.
|
9
|
+
store :key, :accessors => [:browser_ip, :browser_ua]
|
10
|
+
|
11
|
+
# The User-Agent header of the browser that received this suid.
|
12
|
+
validates :browser_ua, :presence => true
|
13
|
+
|
14
|
+
# The IP of the computer that received this suid.
|
15
|
+
validates :browser_ip, :presence => true
|
16
|
+
|
17
|
+
# Decent compromise between convenience and security.
|
18
|
+
self.expires_after = 14.days
|
19
|
+
|
20
|
+
# Creates a new session UID token for a user.
|
21
|
+
#
|
22
|
+
# @param [User] user the user authenticated using this session
|
23
|
+
# @param [String] browser_ip the IP of the session
|
24
|
+
# @param [String] browser_ua the User-Agent of the browser used for this
|
25
|
+
# session
|
26
|
+
def self.random_for(user, browser_ip, browser_ua)
|
27
|
+
browser_ua = browser_ua[0, 1536] if browser_ua.length > 1536
|
28
|
+
key = { :browser_ip => browser_ip, :browser_ua => browser_ua }
|
29
|
+
super user, key, self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Refresh precision for the updated_at timestamp, in seconds.
|
33
|
+
#
|
34
|
+
# When a session UID is used to authenticate a user, its updated_at time is
|
35
|
+
# refreshed if it differs from the current time by this much.
|
36
|
+
class_attribute :updates_after, :instance_writer => false
|
37
|
+
self.updates_after = 1.hour
|
38
|
+
|
39
|
+
# Updates the time associated with the session.
|
40
|
+
def spend
|
41
|
+
self.touch if Time.now - updated_at >= updates_after
|
42
|
+
end
|
43
|
+
|
44
|
+
# Garbage-collects database records of expired sessions.
|
45
|
+
#
|
46
|
+
# This method should be called periodically to keep the size of the session
|
47
|
+
# table under control.
|
48
|
+
def self.remove_expired
|
49
|
+
self.where('updated_at < ?', Time.now - expires_after).delete_all
|
50
|
+
self
|
51
|
+
end
|
52
|
+
end # class Tokens::SessionUid
|
53
|
+
|
54
|
+
end # namespace Tokens
|
data/authpwn_rails.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "authpwn_rails"
|
8
|
-
s.version = "0.12.
|
8
|
+
s.version = "0.12.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Victor Costan"]
|
12
|
-
s.date = "2012-
|
12
|
+
s.date = "2012-10-05"
|
13
13
|
s.description = "Works with Facebook."
|
14
14
|
s.email = "victor@costan.us"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
|
|
34
34
|
"app/models/tokens/email_verification.rb",
|
35
35
|
"app/models/tokens/one_time.rb",
|
36
36
|
"app/models/tokens/password_reset.rb",
|
37
|
+
"app/models/tokens/session_uid.rb",
|
37
38
|
"authpwn_rails.gemspec",
|
38
39
|
"legacy/migrate_011_to_012.rb",
|
39
40
|
"legacy/migrate_09_to_010.rb",
|
@@ -41,12 +42,14 @@ Gem::Specification.new do |s|
|
|
41
42
|
"lib/authpwn_rails/credential_model.rb",
|
42
43
|
"lib/authpwn_rails/current_user.rb",
|
43
44
|
"lib/authpwn_rails/engine.rb",
|
45
|
+
"lib/authpwn_rails/expires.rb",
|
44
46
|
"lib/authpwn_rails/facebook_session.rb",
|
45
47
|
"lib/authpwn_rails/generators/all_generator.rb",
|
46
48
|
"lib/authpwn_rails/generators/templates/001_create_users.rb",
|
47
49
|
"lib/authpwn_rails/generators/templates/003_create_credentials.rb",
|
48
50
|
"lib/authpwn_rails/generators/templates/credential.rb",
|
49
51
|
"lib/authpwn_rails/generators/templates/credentials.yml",
|
52
|
+
"lib/authpwn_rails/generators/templates/initializer.rb",
|
50
53
|
"lib/authpwn_rails/generators/templates/session/forbidden.html.erb",
|
51
54
|
"lib/authpwn_rails/generators/templates/session/home.html.erb",
|
52
55
|
"lib/authpwn_rails/generators/templates/session/new.html.erb",
|
@@ -79,6 +82,7 @@ Gem::Specification.new do |s|
|
|
79
82
|
"test/credentials/one_time_token_credential_test.rb",
|
80
83
|
"test/credentials/password_credential_test.rb",
|
81
84
|
"test/credentials/password_reset_token_test.rb",
|
85
|
+
"test/credentials/session_uid_token_test.rb",
|
82
86
|
"test/credentials/token_crendential_test.rb",
|
83
87
|
"test/facebook_controller_test.rb",
|
84
88
|
"test/fixtures/bare_session/forbidden.html.erb",
|
@@ -94,9 +98,11 @@ Gem::Specification.new do |s|
|
|
94
98
|
"test/helpers/routes.rb",
|
95
99
|
"test/helpers/view_helpers.rb",
|
96
100
|
"test/http_basic_controller_test.rb",
|
101
|
+
"test/initializer_test.rb",
|
97
102
|
"test/routes_test.rb",
|
98
103
|
"test/session_controller_api_test.rb",
|
99
104
|
"test/session_mailer_api_test.rb",
|
105
|
+
"test/test_extensions_test.rb",
|
100
106
|
"test/test_helper.rb",
|
101
107
|
"test/user_extensions/email_field_test.rb",
|
102
108
|
"test/user_extensions/facebook_fields_test.rb",
|
data/lib/authpwn_rails.rb
CHANGED
@@ -3,8 +3,10 @@ require 'active_support'
|
|
3
3
|
# :nodoc: namespace
|
4
4
|
module Authpwn
|
5
5
|
extend ActiveSupport::Autoload
|
6
|
-
|
6
|
+
|
7
7
|
autoload :CredentialModel, 'authpwn_rails/credential_model.rb'
|
8
|
+
autoload :CurrentUser, 'authpwn_rails/current_user.rb'
|
9
|
+
autoload :Expires, 'authpwn_rails/expires.rb'
|
8
10
|
autoload :SessionController, 'authpwn_rails/session_controller.rb'
|
9
11
|
autoload :SessionMailer, 'authpwn_rails/session_mailer.rb'
|
10
12
|
autoload :UserModel, 'authpwn_rails/user_model.rb'
|
@@ -17,7 +19,6 @@ module Authpwn
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
require 'authpwn_rails/current_user.rb'
|
21
22
|
require 'authpwn_rails/facebook_session.rb'
|
22
23
|
require 'authpwn_rails/http_basic.rb'
|
23
24
|
require 'authpwn_rails/routes.rb'
|
@@ -3,16 +3,7 @@ module Authpwn
|
|
3
3
|
|
4
4
|
# The unofficial Rails convention for tracking the authenticated user.
|
5
5
|
module CurrentUser
|
6
|
-
|
7
|
-
|
8
|
-
def current_user=(user)
|
9
|
-
@current_user = user
|
10
|
-
if user
|
11
|
-
session[:user_exuid] = user.to_param
|
12
|
-
else
|
13
|
-
session.delete :user_exuid
|
14
|
-
end
|
15
|
-
end
|
6
|
+
attr_accessor :current_user
|
16
7
|
end # module Authpwn::CurrentUser
|
17
8
|
|
18
9
|
end # namespace Authpwn
|
data/lib/authpwn_rails/engine.rb
CHANGED
@@ -8,11 +8,11 @@ class Engine < Rails::Engine
|
|
8
8
|
generators do
|
9
9
|
require 'authpwn_rails/generators/all_generator.rb'
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
initializer 'authpwn.rspec.extensions' do
|
13
13
|
begin
|
14
14
|
require 'rspec'
|
15
|
-
|
15
|
+
|
16
16
|
RSpec.configure do |c|
|
17
17
|
c.include Authpwn::TestExtensions
|
18
18
|
c.include Authpwn::ControllerTestExtensions
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module Authpwn
|
3
|
+
|
4
|
+
# Common code for credentials that expire.
|
5
|
+
module Expires
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Number of seconds after which a credential becomes unusable.
|
10
|
+
#
|
11
|
+
# Users can reset this timer by updating their credentials, e.g. changing
|
12
|
+
# their password.
|
13
|
+
class_attribute :expires_after, :instance_writer => false
|
14
|
+
end
|
15
|
+
|
16
|
+
# True if this password is too old and should not be used for authentication.
|
17
|
+
def expired?
|
18
|
+
return false unless expires_after
|
19
|
+
updated_at < Time.now - expires_after
|
20
|
+
end
|
21
|
+
end # module Authpwn::Expires
|
22
|
+
|
23
|
+
end # namespace Authpwn
|
@@ -11,7 +11,7 @@ class AllGenerator < Rails::Generators::Base
|
|
11
11
|
File.join('db', 'migrate', '20100725000001_create_users.rb')
|
12
12
|
copy_file 'users.yml', File.join('test', 'fixtures', 'users.yml')
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def create_credential_model
|
16
16
|
copy_file 'credential.rb', File.join('app', 'models', 'credential.rb')
|
17
17
|
copy_file '003_create_credentials.rb',
|
@@ -19,17 +19,17 @@ class AllGenerator < Rails::Generators::Base
|
|
19
19
|
copy_file 'credentials.yml',
|
20
20
|
File.join('test', 'fixtures', 'credentials.yml')
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def create_session_controller
|
24
24
|
copy_file 'session_controller.rb',
|
25
|
-
File.join('app', 'controllers', 'session_controller.rb')
|
25
|
+
File.join('app', 'controllers', 'session_controller.rb')
|
26
26
|
copy_file File.join('session_controller_test.rb'),
|
27
27
|
File.join('test', 'functional', 'session_controller_test.rb')
|
28
28
|
|
29
29
|
route "authpwn_session"
|
30
30
|
route "root :to => 'session#show'"
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def create_session_views
|
34
34
|
copy_file File.join('session', 'forbidden.html.erb'),
|
35
35
|
File.join('app', 'views', 'session', 'forbidden.html.erb')
|
@@ -58,6 +58,11 @@ class AllGenerator < Rails::Generators::Base
|
|
58
58
|
File.join('app', 'views', 'session_mailer',
|
59
59
|
'reset_password_email.text.erb')
|
60
60
|
end
|
61
|
+
|
62
|
+
def create_initializer
|
63
|
+
copy_file 'initializer.rb',
|
64
|
+
File.join('config', 'initializers', 'authpwn.rb')
|
65
|
+
end
|
61
66
|
end # class Authpwn::AllGenerator
|
62
67
|
|
63
68
|
end # namespace Authpwn
|
@@ -53,3 +53,19 @@ jane_password_token:
|
|
53
53
|
user: jane
|
54
54
|
type: Tokens::PasswordReset
|
55
55
|
name: nbMLTKN18tYy9plBAbsrwT6zdE2jZqoKPk6Ze4lHMSQ
|
56
|
+
|
57
|
+
john_session_token:
|
58
|
+
user: john
|
59
|
+
type: Tokens::SessionUid
|
60
|
+
name: iyHvfTnYoF1f1jL9Vnb55hnXobf2Ld6HxIW-PXya6dw
|
61
|
+
key: <%= { :browser_ip => '18.241.1.121',
|
62
|
+
:browser_ua => 'Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1'
|
63
|
+
}.to_yaml.inspect %>
|
64
|
+
|
65
|
+
jane_session_token:
|
66
|
+
user: jane
|
67
|
+
type: Tokens::SessionUid
|
68
|
+
name: sNIfh6UavUSceL0TpubJ-DnZRuxPSTAddoHBb-twEIg
|
69
|
+
key: <%= { :browser_ip => '18.70.0.160',
|
70
|
+
:browser_ua => 'Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1'
|
71
|
+
}.to_yaml.inspect %>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Tweak the password expiration interval, or comment out the line to disable
|
2
|
+
# password expiration altogether.
|
3
|
+
#
|
4
|
+
# NOTE: when a user's password expires, he will need to use the password reset
|
5
|
+
# flow, which relies on e-mail delivery. If your application doesn't implement
|
6
|
+
# password reset, or doesn't have working e-mail delivery, disable password
|
7
|
+
# expiration.
|
8
|
+
Credentials::Password.expires_after = 1.year
|
9
|
+
|
10
|
+
# These codes are sent in plaintext in e-mails, be somewhat aggressive.
|
11
|
+
Tokens::EmailVerification.expires_after = 3.days
|
12
|
+
Tokens::PasswordReset.expires_after = 3.days
|
13
|
+
|
14
|
+
# Users are identified by cookies whose codes are looked up in the database.
|
15
|
+
Tokens::SessionUid.expires_after = 14.days
|
16
|
+
# This knob is a compromise between accurate session expiration and write
|
17
|
+
# workload on the database. Keep it below 1% of expires_after.
|
18
|
+
Tokens::SessionUid.updates_after = 1.hour
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<p>
|
2
|
-
This view gets displayed when the user is logged in. Right now,
|
2
|
+
This view gets displayed when the user is logged in. Right now,
|
3
3
|
user <%= current_user.exuid %> is logged in. You should allow the user to
|
4
4
|
<%= link_to 'Log out', session_path, :method => :delete %>.
|
5
5
|
</p>
|