janus 0.5.0
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/README.rdoc +144 -0
- data/lib/janus/config.rb +26 -0
- data/lib/janus/controllers/confirmations_controller.rb +56 -0
- data/lib/janus/controllers/helpers.rb +54 -0
- data/lib/janus/controllers/internal_helpers.rb +33 -0
- data/lib/janus/controllers/passwords_controller.rb +60 -0
- data/lib/janus/controllers/registrations_controller.rb +55 -0
- data/lib/janus/controllers/sessions_controller.rb +94 -0
- data/lib/janus/controllers/url_helpers.rb +61 -0
- data/lib/janus/helper.rb +11 -0
- data/lib/janus/hooks/rememberable.rb +20 -0
- data/lib/janus/hooks/remote_authenticatable.rb +27 -0
- data/lib/janus/hooks/trackable.rb +3 -0
- data/lib/janus/hooks.rb +58 -0
- data/lib/janus/mailer.rb +13 -0
- data/lib/janus/manager.rb +97 -0
- data/lib/janus/models/base.rb +31 -0
- data/lib/janus/models/confirmable.rb +45 -0
- data/lib/janus/models/database_authenticatable.rb +98 -0
- data/lib/janus/models/rememberable.rb +54 -0
- data/lib/janus/models/remote_authenticatable.rb +99 -0
- data/lib/janus/models/remote_token.rb +17 -0
- data/lib/janus/models/trackable.rb +37 -0
- data/lib/janus/routes.rb +78 -0
- data/lib/janus/strategies/base.rb +40 -0
- data/lib/janus/strategies/database_authenticatable.rb +20 -0
- data/lib/janus/strategies/rememberable.rb +52 -0
- data/lib/janus/strategies/remote_authenticatable.rb +28 -0
- data/lib/janus/strategies.rb +33 -0
- data/lib/janus/test_helper.rb +25 -0
- data/lib/janus.rb +60 -0
- data/test/functional/home_controller_test.rb +8 -0
- data/test/functional/janus/mailer_test.rb +14 -0
- data/test/functional/janus/manager_test.rb +94 -0
- data/test/functional/users/confirmations_controller_test.rb +59 -0
- data/test/functional/users/passwords_controller_test.rb +101 -0
- data/test/functional/users/registrations_controller_test.rb +112 -0
- data/test/functional/users/sessions_controller_test.rb +100 -0
- data/test/functional/users_controller_test.rb +22 -0
- data/test/integration/users/rememberable_test.rb +32 -0
- data/test/integration/users/remote_test.rb +72 -0
- data/test/integration/users/sessions_test.rb +18 -0
- data/test/integration/users/trackable_test.rb +22 -0
- data/test/rails_app/app/controllers/application_controller.rb +9 -0
- data/test/rails_app/app/controllers/blogs_controller.rb +6 -0
- data/test/rails_app/app/controllers/home_controller.rb +4 -0
- data/test/rails_app/app/controllers/users/confirmations_controller.rb +3 -0
- data/test/rails_app/app/controllers/users/passwords_controller.rb +3 -0
- data/test/rails_app/app/controllers/users/registrations_controller.rb +7 -0
- data/test/rails_app/app/controllers/users/sessions_controller.rb +11 -0
- data/test/rails_app/app/controllers/users_controller.rb +9 -0
- data/test/rails_app/app/helpers/application_helper.rb +2 -0
- data/test/rails_app/app/mailers/janus_mailer.rb +2 -0
- data/test/rails_app/app/models/remote_token.rb +6 -0
- data/test/rails_app/app/models/user.rb +8 -0
- data/test/rails_app/config/application.rb +42 -0
- data/test/rails_app/config/boot.rb +6 -0
- data/test/rails_app/config/environment.rb +5 -0
- data/test/rails_app/config/environments/development.rb +26 -0
- data/test/rails_app/config/environments/production.rb +49 -0
- data/test/rails_app/config/environments/test.rb +36 -0
- data/test/rails_app/config/initializers/janus.rb +11 -0
- data/test/rails_app/config/initializers/secret_token.rb +7 -0
- data/test/rails_app/config/initializers/session_store.rb +8 -0
- data/test/rails_app/config/routes.rb +12 -0
- data/test/rails_app/db/migrate/20110323153820_create_users.rb +34 -0
- data/test/rails_app/db/migrate/20110331153546_create_remote_tokens.rb +15 -0
- data/test/rails_app/db/schema.rb +45 -0
- data/test/rails_app/db/seeds.rb +7 -0
- data/test/test_helper.rb +103 -0
- data/test/unit/confirmable_test.rb +36 -0
- data/test/unit/janus_test.rb +27 -0
- data/test/unit/rememberable_test.rb +50 -0
- data/test/unit/remote_authenticatable_test.rb +37 -0
- data/test/unit/remote_token_test.rb +9 -0
- data/test/unit/reset_password_test.rb +45 -0
- data/test/unit/trackable_test.rb +21 -0
- data/test/unit/user_test.rb +60 -0
- metadata +232 -0
data/lib/janus/hooks.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Janus
|
2
|
+
module Hooks # :nodoc:
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# Hooks allow you the react at the different steps of a user session.
|
6
|
+
# All callbacks will receive the same arguments: +user+, +manager+ and
|
7
|
+
# +options+.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Janus::Manager.after_login do |user, manager, options|
|
12
|
+
# session = manager.session(options[:scope])
|
13
|
+
#
|
14
|
+
# # write some great code here
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Options:
|
18
|
+
#
|
19
|
+
# - +:scope+
|
20
|
+
#
|
21
|
+
module ClassMethods
|
22
|
+
# Executed after a strategy succeeds to authenticate a user.
|
23
|
+
def after_authenticate(&block)
|
24
|
+
add_callback(:authenticate, block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Executed the first time an authenticated user is fetched from session.
|
28
|
+
def after_fetch(&block)
|
29
|
+
add_callback(:fetch, block)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Executed after a user is logged in.
|
33
|
+
def after_login(&block)
|
34
|
+
add_callback(:login, block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Executed after a user is logged out. after_logout will be executed for
|
38
|
+
# each scope when logging out from multiple scopes at once.
|
39
|
+
def after_logout(&block)
|
40
|
+
add_callback(:logout, block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_callbacks(kind, *args) # :nodoc:
|
44
|
+
callbacks(kind).each { |block| block.call(*args) }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def add_callback(kind, block)
|
49
|
+
callbacks(kind) << block
|
50
|
+
end
|
51
|
+
|
52
|
+
def callbacks(kind)
|
53
|
+
@callbacks ||= {}
|
54
|
+
@callbacks[kind] ||= []
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/janus/mailer.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class Janus::Mailer < ActionMailer::Base
|
2
|
+
default_from = Janus::Config.contact_email
|
3
|
+
|
4
|
+
def reset_password_instructions(user)
|
5
|
+
@user = user
|
6
|
+
mail :to => @user.email, :subject => I18n.t('janus.mailer.reset_password_instructions.subject')
|
7
|
+
end
|
8
|
+
|
9
|
+
def confirmation_instructions(user)
|
10
|
+
@user = user
|
11
|
+
mail :to => @user.email, :subject => I18n.t('janus.mailer.confirmation_instructions.subject')
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Janus
|
2
|
+
class Manager
|
3
|
+
include Janus::Hooks
|
4
|
+
include Janus::Strategies
|
5
|
+
|
6
|
+
attr_reader :request, :cookies
|
7
|
+
|
8
|
+
def initialize(request, cookies)
|
9
|
+
@request, @cookies = request, cookies
|
10
|
+
end
|
11
|
+
|
12
|
+
# Tries to authenticate the user using strategies, before returning
|
13
|
+
# the current user or nil.
|
14
|
+
def authenticate(scope)
|
15
|
+
run_strategies(scope) unless authenticated?(scope)
|
16
|
+
user(scope)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raises a Janus::NotAuthenticated exception unless a user is authenticated.
|
20
|
+
def authenticate!(scope)
|
21
|
+
raise Janus::NotAuthenticated.new(scope) unless authenticate?(scope)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Tries to authenticate the user before checking if it's authenticated.
|
25
|
+
def authenticate?(scope)
|
26
|
+
authenticate(scope)
|
27
|
+
authenticated?(scope)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns true if a user is authenticated.
|
31
|
+
def authenticated?(scope) # :nodoc:
|
32
|
+
!!session(scope)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Logs a user in.
|
36
|
+
#
|
37
|
+
# FIXME: what should happen when a user signs in but a user is already signed in?!
|
38
|
+
def login(user, options = {})
|
39
|
+
options[:scope] ||= Janus.scope_for(user)
|
40
|
+
set_user(user, options)
|
41
|
+
Janus::Manager.run_callbacks(:login, user, self, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Logs a user out from the given scopes or from all scopes at once
|
45
|
+
# if no scope is defined. If no scope is left after logout, then the
|
46
|
+
# whole session will be resetted.
|
47
|
+
def logout(*scopes)
|
48
|
+
scopes = janus_sessions.keys if scopes.empty?
|
49
|
+
|
50
|
+
scopes.each do |scope|
|
51
|
+
_user = user(scope)
|
52
|
+
unset_user(scope)
|
53
|
+
Janus::Manager.run_callbacks(:logout, _user, self, :scope => scope)
|
54
|
+
end
|
55
|
+
|
56
|
+
request.reset_session if janus_sessions.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Manually sets a user without going throught the whole login or
|
60
|
+
# authenticate process.
|
61
|
+
def set_user(user, options = {})
|
62
|
+
scope = options[:scope] || Janus.scope_for(user)
|
63
|
+
janus_sessions[scope.to_sym] = { :user_class => user.class, :user_id => user.id }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Manually removes the user without going throught the whole logout process.
|
67
|
+
def unset_user(scope)
|
68
|
+
janus_sessions.delete(scope.to_sym)
|
69
|
+
@users.delete(scope.to_sym) unless @users.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the currently connected user.
|
73
|
+
def user(scope)
|
74
|
+
scope = scope.to_sym
|
75
|
+
@users ||= {}
|
76
|
+
|
77
|
+
if authenticated?(scope)
|
78
|
+
if @users[scope].nil?
|
79
|
+
@users[scope] = session(scope)[:user_class].find(session(scope)[:user_id])
|
80
|
+
Janus::Manager.run_callbacks(:fetch, @users[scope], self, :scope => scope)
|
81
|
+
end
|
82
|
+
|
83
|
+
@users[scope]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the current session for user.
|
88
|
+
def session(scope)
|
89
|
+
janus_sessions[scope.to_sym]
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def janus_sessions
|
94
|
+
request.session['janus'] ||= {}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Janus
|
2
|
+
module Models
|
3
|
+
module Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def generate_token(column_name, size = 32)
|
8
|
+
loop do
|
9
|
+
token = SecureRandom.hex(size)
|
10
|
+
return token unless where(column_name => token).any?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def janus_config(*keys)
|
15
|
+
keys.each do |key|
|
16
|
+
class_eval <<-EOV
|
17
|
+
def self.#{key}
|
18
|
+
@#{key} || Janus::Config.#{key}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.#{key}=(value)
|
22
|
+
@#{key} = value
|
23
|
+
end
|
24
|
+
EOV
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Janus
|
2
|
+
module Models
|
3
|
+
# = Confirmable
|
4
|
+
#
|
5
|
+
# Confirms an account's email by sending an email with an unique token.
|
6
|
+
# This is necessary to be sure the user can be contacted on that email.
|
7
|
+
#
|
8
|
+
# IMPROVE: reconfirm whenever email changes.
|
9
|
+
module Confirmable
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
attr_protected :confirmation_token, :confirmation_sent_at, :confirmed_at
|
14
|
+
janus_config(:confirmation_key)
|
15
|
+
|
16
|
+
before_create :generate_confirmation_token
|
17
|
+
# before_update :generate_confirmation_token, :if => :email_changed?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Generates the confirmation token, but won't save the record.
|
21
|
+
def generate_confirmation_token
|
22
|
+
self.confirmation_token = self.class.generate_token(:confirmation_token)
|
23
|
+
self.confirmation_sent_at = Time.now
|
24
|
+
end
|
25
|
+
|
26
|
+
# Confirms the record.
|
27
|
+
def confirm!
|
28
|
+
self.confirmation_token = self.confirmation_sent_at = nil
|
29
|
+
self.confirmed_at = Time.now
|
30
|
+
save
|
31
|
+
end
|
32
|
+
|
33
|
+
# Checks wether the email of this user if confirmed, or not.
|
34
|
+
def confirmed?
|
35
|
+
confirmed_at?
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
def find_for_confirmation(token)
|
40
|
+
where(:confirmation_token => token).first unless token.blank?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'bcrypt'
|
2
|
+
|
3
|
+
module Janus
|
4
|
+
module Models
|
5
|
+
# = DatabaseAuthenticatable
|
6
|
+
#
|
7
|
+
# This is the initial part and is required for email + password registration
|
8
|
+
# and logins. Passwords are automatically encrypted following Devise's
|
9
|
+
# default encryption logic, which relies on bcrypt.
|
10
|
+
#
|
11
|
+
# == Required columns:
|
12
|
+
#
|
13
|
+
# - email
|
14
|
+
# - encrypted_password
|
15
|
+
#
|
16
|
+
# == Configuration
|
17
|
+
#
|
18
|
+
# - +stretches+
|
19
|
+
# - +pepper+
|
20
|
+
# - +authentication_keys+ - required keys for authenticating a user, defaults to <tt>[:email]</tt>
|
21
|
+
#
|
22
|
+
module DatabaseAuthenticatable
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
included do
|
26
|
+
attr_protected :encrypted_password, :reset_password_token, :reset_password_sent_at
|
27
|
+
attr_reader :password
|
28
|
+
attr_accessor :current_password
|
29
|
+
|
30
|
+
validates :password, :presence => true, :confirmation => true, :if => :password_required?
|
31
|
+
validate :validate_current_password, :on => :update, :if => :current_password
|
32
|
+
|
33
|
+
janus_config(:authentication_keys, :stretches, :pepper)
|
34
|
+
end
|
35
|
+
|
36
|
+
def password=(password)
|
37
|
+
@password = password
|
38
|
+
self.encrypted_password = digest_password(@password) unless @password.blank?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks if a given password matches this user password.
|
42
|
+
def valid_password?(password)
|
43
|
+
::BCrypt::Password.new(encrypted_password) == "#{password}#{self.class.pepper}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def digest_password(password)
|
47
|
+
::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def clean_up_passwords
|
51
|
+
self.current_password = self.password = self.password_confirmation = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def generate_reset_password_token!
|
55
|
+
self.reset_password_token = self.class.generate_token(:reset_password_token)
|
56
|
+
self.reset_password_sent_at = Time.now
|
57
|
+
save
|
58
|
+
end
|
59
|
+
|
60
|
+
def reset_password!(params)
|
61
|
+
params.each do |key, value|
|
62
|
+
send("#{key}=", value) if [:password, :password_confirmation].include?(key.to_sym)
|
63
|
+
end
|
64
|
+
|
65
|
+
self.reset_password_sent_at = self.reset_password_token = nil
|
66
|
+
save
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def password_required?
|
71
|
+
!persisted? || !!password || !!password_confirmation
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_current_password
|
75
|
+
errors.add(:current_password, :invalid) unless valid_password?(current_password)
|
76
|
+
end
|
77
|
+
|
78
|
+
module ClassMethods
|
79
|
+
def find_for_database_authentication(params)
|
80
|
+
params = params.reject { |k,v| !authentication_keys.include?(k.to_sym) }
|
81
|
+
where(params).first
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_for_password_reset(token)
|
85
|
+
user = find_by_reset_password_token(token) unless token.blank?
|
86
|
+
|
87
|
+
if user && user.reset_password_sent_at < 2.days.ago
|
88
|
+
user.reset_password_token = user.reset_password_sent_at = nil
|
89
|
+
user.save
|
90
|
+
user = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
user
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Janus
|
2
|
+
module Models
|
3
|
+
# = Rememberable
|
4
|
+
#
|
5
|
+
# Allows a user to check a remember me check box when she logs in through
|
6
|
+
# DatabaseAuthenticatable. It will set a cookie with a configurable
|
7
|
+
# expiration date.
|
8
|
+
#
|
9
|
+
# == Required columns
|
10
|
+
#
|
11
|
+
# - remember_token
|
12
|
+
# - remember_created_at
|
13
|
+
#
|
14
|
+
# == Configuration
|
15
|
+
#
|
16
|
+
# - remember_for - how long to remember the user, for instance <tt>1.week</tt>.
|
17
|
+
# - :extend_remember_period - set to true to extend the remember cookie every time the user logs in.
|
18
|
+
#
|
19
|
+
module Rememberable
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
included do
|
23
|
+
attr_protected :remember_token, :remember_created_at
|
24
|
+
janus_config :remember_for, :extend_remember_period
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generates an unique remote_token.
|
28
|
+
def remember_me!
|
29
|
+
self.remember_token = self.class.generate_token(:remember_token)
|
30
|
+
self.remember_created_at = Time.now
|
31
|
+
save
|
32
|
+
end
|
33
|
+
|
34
|
+
# Nullifies remote_token.
|
35
|
+
def forget_me!
|
36
|
+
self.remember_token = self.remember_created_at = nil
|
37
|
+
save
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
def find_for_remember_authentication(token)
|
42
|
+
user = where(:remember_token => token).first unless token.blank?
|
43
|
+
|
44
|
+
if user && user.remember_created_at < remember_for.ago
|
45
|
+
user.forget_me!
|
46
|
+
user = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
user
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'janus/hooks/remote_authenticatable'
|
2
|
+
|
3
|
+
module Janus
|
4
|
+
module Models
|
5
|
+
# = RemoteAuthenticatable
|
6
|
+
#
|
7
|
+
# Keeping a user connected on subdomains is an easy task, all you need to do
|
8
|
+
# is define the session cookie to <tt>.example.com</tt> for instance. But
|
9
|
+
# keeping a user connected on multiple top level domains is a harder task.
|
10
|
+
#
|
11
|
+
# Hopefully RemoteAuthenticatable takes care of all the hassle.
|
12
|
+
#
|
13
|
+
# == Single Sign In
|
14
|
+
#
|
15
|
+
# The authentication must happen on a single domain, for instance
|
16
|
+
# <tt>login.example.com</tt>, then other domains must redirect to that
|
17
|
+
# domain's new session url. For instance:
|
18
|
+
#
|
19
|
+
# redirect_to new_user_session_url(:host => "login.example.com") unless user_signed_in?
|
20
|
+
#
|
21
|
+
# And that's it! The user shall be redirected with an unique +remote_token+,
|
22
|
+
# which shall log her in. Actually the user won't be logged in through
|
23
|
+
# Janus::Manager#login but through Janus::Manager#set_user which won't
|
24
|
+
# run the login hooks. This is useful for not tracking the user everytime
|
25
|
+
# it gets authenticated on each remote site.
|
26
|
+
#
|
27
|
+
# == Single Sign Out
|
28
|
+
#
|
29
|
+
# Session state is maintained across domains through the session_token
|
30
|
+
# column of your User model. If a session token is invalid the session
|
31
|
+
# is simply resetted, thus logging out the user on remote domains. Actually
|
32
|
+
# the user is logged out using Janus::Manager#unset_user before
|
33
|
+
# resetting the session.
|
34
|
+
#
|
35
|
+
# == Required columns and models:
|
36
|
+
#
|
37
|
+
# A +session_token+ column (string) is required, as well as a RemoteToken
|
38
|
+
# model like so:
|
39
|
+
#
|
40
|
+
# class RemoteToken < ActiveRecord::Base
|
41
|
+
# include Janus::Models::RemoteToken
|
42
|
+
#
|
43
|
+
# belongs_to :user
|
44
|
+
# validates_presence_of :user
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# With the associated table:
|
48
|
+
#
|
49
|
+
# create_table :remote_tokens do |t|
|
50
|
+
# t.references :user
|
51
|
+
# t.string :token
|
52
|
+
# t.datetime :created_at
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# add_index :remote_tokens, :token, :unique => true
|
56
|
+
module RemoteAuthenticatable
|
57
|
+
extend ActiveSupport::Concern
|
58
|
+
|
59
|
+
included do |klass|
|
60
|
+
attr_protected :session_token
|
61
|
+
klass.class_eval { has_many :remote_tokens }
|
62
|
+
janus_config :remote_authentication_key
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generates an unique session token. This token will be used to validate
|
66
|
+
# the current session, and must be generated whenever a user signs in on
|
67
|
+
# the main site.
|
68
|
+
#
|
69
|
+
# The token won't be regenerated if it already exists.
|
70
|
+
def generate_session_token!
|
71
|
+
update_attribute(:session_token, self.class.generate_token(:session_token)) unless session_token
|
72
|
+
session_token
|
73
|
+
end
|
74
|
+
|
75
|
+
# Destroys the session token. This must be called whenever the user signs
|
76
|
+
# out. Doing so will invalidate all sessions using this token at once
|
77
|
+
# --hence single sign out.
|
78
|
+
def destroy_session_token!
|
79
|
+
update_attribute(:session_token, nil)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a temporary token to be used with find_for_remote_authentication.
|
83
|
+
def generate_remote_token!
|
84
|
+
remote_tokens.create.token
|
85
|
+
end
|
86
|
+
|
87
|
+
module ClassMethods
|
88
|
+
def find_for_remote_authentication(token)
|
89
|
+
remote_token = ::RemoteToken.where(:token => token).first
|
90
|
+
|
91
|
+
if remote_token
|
92
|
+
remote_token.destroy
|
93
|
+
remote_token.user unless remote_token.created_at < 30.seconds.ago
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Janus
|
2
|
+
module Models
|
3
|
+
module RemoteToken
|
4
|
+
def self.included(klass)
|
5
|
+
klass.class_eval do
|
6
|
+
include Janus::Models::Base
|
7
|
+
before_save :reset_token
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Generates an unique token.
|
12
|
+
def reset_token
|
13
|
+
self.token = self.class.generate_token(:token)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'janus/hooks/trackable'
|
2
|
+
|
3
|
+
module Janus
|
4
|
+
module Models
|
5
|
+
# = Trackable
|
6
|
+
#
|
7
|
+
# Simple hook to update some columns of your model whenever a user logs in.
|
8
|
+
#
|
9
|
+
# == Required columns
|
10
|
+
#
|
11
|
+
# - +sign_in_count+
|
12
|
+
# - +current_sign_in_ip+
|
13
|
+
# - +current_sign_in_at+
|
14
|
+
# - +last_sign_in_ip+
|
15
|
+
# - +last_sign_in_at+
|
16
|
+
#
|
17
|
+
module Trackable
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
included do
|
21
|
+
attr_protected :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip
|
22
|
+
end
|
23
|
+
|
24
|
+
def track!(ip)
|
25
|
+
self.sign_in_count += 1
|
26
|
+
|
27
|
+
self.last_sign_in_at = self.current_sign_in_at
|
28
|
+
self.last_sign_in_ip = self.current_sign_in_ip
|
29
|
+
|
30
|
+
self.current_sign_in_at = Time.now
|
31
|
+
self.current_sign_in_ip = ip
|
32
|
+
|
33
|
+
save(:validate => false)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/janus/routes.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActionDispatch # :nodoc:
|
2
|
+
module Routing # :nodoc:
|
3
|
+
class Mapper
|
4
|
+
# Creates the routes for a Janus capable resource.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# MyApp::Application.routes.draw do
|
9
|
+
# janus :users, :session => true, :registration => false
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# Options:
|
13
|
+
#
|
14
|
+
# - +session+ - true to generate session routes
|
15
|
+
# - +registration+ - true to generate registration routes
|
16
|
+
# - +confirmation+ - true to generate confirmation routes
|
17
|
+
# - +password+ - true to generate password reset routes
|
18
|
+
#
|
19
|
+
# Generated session routes:
|
20
|
+
#
|
21
|
+
# new_user_session GET /users/sign_in(.:format) {:controller=>"users/sessions", :action=>"new"}
|
22
|
+
# user_session POST /users/sign_in(.:format) {:controller=>"users/sessions", :action=>"create"}
|
23
|
+
# destroy_user_session /users/sign_out(.:format) {:controller=>"users/sessions", :action=>"destroy"}
|
24
|
+
#
|
25
|
+
# Generated registration routes:
|
26
|
+
#
|
27
|
+
# new_user_registration GET /users/sign_up(.:format) {:controller=>"users/registrations", :action=>"new"}
|
28
|
+
# user_registration POST /users(.:format) {:controller=>"users/registrations", :action=>"create"}
|
29
|
+
# edit_user_registration GET /users/edit(.:format) {:controller=>"users/registrations", :action=>"edit"}
|
30
|
+
# PUT /users(.:format) {:controller=>"users/registrations", :action=>"update"}
|
31
|
+
# DELETE /users(.:format) {:controller=>"users/registrations", :action=>"destroy"}
|
32
|
+
#
|
33
|
+
# Generated confirmation routes:
|
34
|
+
#
|
35
|
+
# user_confirmation POST /users/confirmation(.:format) {:controller=>"users/confirmations", :action=>"create"}
|
36
|
+
# new_user_confirmation GET /users/confirmation/new(.:format) {:controller=>"users/confirmations", :action=>"new"}
|
37
|
+
# GET /users/confirmation(.:format) {:controller=>"users/confirmations", :action=>"show"}
|
38
|
+
#
|
39
|
+
# Generated password reset routes:
|
40
|
+
#
|
41
|
+
# user_password POST /users/password(.:format) {:controller=>"users/passwords", :action=>"create"}
|
42
|
+
# new_user_password GET /users/password/new(.:format) {:controller=>"users/passwords", :action=>"new"}
|
43
|
+
# edit_user_password GET /users/password/edit(.:format) {:controller=>"users/passwords", :action=>"edit"}
|
44
|
+
# PUT /users/password(.:format) {:controller=>"users/passwords", :action=>"update"}
|
45
|
+
# DELETE /users/password(.:format) {:controller=>"users/passwords", :action=>"destroy"}
|
46
|
+
#
|
47
|
+
def janus(*resources)
|
48
|
+
ActionController::Base.send(:include, Janus::Helpers) unless ActionController::Base.include?(Janus::Helpers)
|
49
|
+
ActionController::Base.send(:include, Janus::UrlHelpers) unless ActionController::Base.include?(Janus::UrlHelpers)
|
50
|
+
options = resources.extract_options!
|
51
|
+
|
52
|
+
resources.each do |plural|
|
53
|
+
singular = plural.to_s.singularize
|
54
|
+
|
55
|
+
if options[:session]
|
56
|
+
scope :path => plural, :controller => "#{plural}/sessions" do
|
57
|
+
match "/sign_in(.:format)", :action => "new", :via => :get, :as => "new_#{singular}_session"
|
58
|
+
match "/sign_in(.:format)", :action => "create", :via => :post, :as => "#{singular}_session"
|
59
|
+
match "/sign_out(.:format)", :action => "destroy", :as => "destroy_#{singular}_session"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
namespace plural, :as => singular do
|
64
|
+
if options[:registration]
|
65
|
+
resource :registration, :except => [:index, :show], :path => "",
|
66
|
+
:path_names => { :new => 'sign_up' }
|
67
|
+
end
|
68
|
+
|
69
|
+
resource :confirmation, :only => [:show, :new, :create] if options[:confirmation]
|
70
|
+
resource :password, :except => [:index, :show] if options[:password]
|
71
|
+
end
|
72
|
+
|
73
|
+
ActionController::Base.janus(singular)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Janus
|
2
|
+
module Strategies
|
3
|
+
# Base class for writing authentication strategies.
|
4
|
+
class Base
|
5
|
+
attr_reader :scope, :manager, :request, :cookies, :user
|
6
|
+
|
7
|
+
def initialize(scope, manager) # :nodoc:
|
8
|
+
@scope, @manager = scope, manager
|
9
|
+
@request, @cookies = manager.request, manager.cookies
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def pass
|
17
|
+
end
|
18
|
+
|
19
|
+
def success!(user)
|
20
|
+
@user = user
|
21
|
+
end
|
22
|
+
|
23
|
+
def success?
|
24
|
+
!@user.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def resource
|
28
|
+
@resource ||= scope.to_s.camelize.constantize
|
29
|
+
end
|
30
|
+
|
31
|
+
def authenticate!
|
32
|
+
raise StandardError.new("You must define the #{self.class.name}#authenticate! method.")
|
33
|
+
end
|
34
|
+
|
35
|
+
def auth_method
|
36
|
+
:login
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#module Janus
|
2
|
+
# module Strategies
|
3
|
+
# class DatabaseAuthenticatable < Base
|
4
|
+
# def valid?
|
5
|
+
# if params[scope].blank? ||
|
6
|
+
# false
|
7
|
+
# else
|
8
|
+
# keys = resource.authentication_keys
|
9
|
+
# keys << :password
|
10
|
+
# keys.each { |key| return false if request.params[scope][key].blank? }
|
11
|
+
# true
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
|
15
|
+
# def authenticate!
|
16
|
+
# if params[]
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#end
|