mirror of
https://github.com/heartcombo/devise.git
synced 2022-11-09 12:18:31 -05:00
Initial support for authorization using "authentication token" (a.k.a. "single access token") - new module. Corresponding changes to Devise core to hook events like "after_changed_password" (only one added now - only one that makes much sense for latest module) easily. Unit and integration tests included. NOTE: One failing test for hooking Warden::Manager.after_authentication - gets ignored for some reason.
Signed-off-by: José Valim <jose.valim@gmail.com>
This commit is contained in:
parent
c03b4ff339
commit
e1440fb430
16 changed files with 420 additions and 11 deletions
|
@ -18,7 +18,7 @@ class SessionsController < ApplicationController
|
||||||
set_flash_message :notice, :signed_in
|
set_flash_message :notice, :signed_in
|
||||||
sign_in_and_redirect(resource_name, resource, true)
|
sign_in_and_redirect(resource_name, resource, true)
|
||||||
else
|
else
|
||||||
set_now_flash_message :alert, warden.message || :invalid
|
set_now_flash_message :alert, (warden.message || :invalid)
|
||||||
build_resource
|
build_resource
|
||||||
render_with_scope :new
|
render_with_scope :new
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,21 +27,22 @@ module Devise
|
||||||
end
|
end
|
||||||
|
|
||||||
ALL = [:authenticatable, :activatable, :confirmable, :recoverable,
|
ALL = [:authenticatable, :activatable, :confirmable, :recoverable,
|
||||||
:rememberable, :validatable, :trackable, :timeoutable, :lockable]
|
:rememberable, :validatable, :trackable, :timeoutable, :lockable, :token_authenticatable]
|
||||||
|
|
||||||
# Maps controller names to devise modules
|
# Maps controller names to devise modules
|
||||||
CONTROLLERS = {
|
CONTROLLERS = {
|
||||||
:sessions => [:authenticatable],
|
:sessions => [:authenticatable, :token_authenticatable],
|
||||||
:passwords => [:recoverable],
|
:passwords => [:recoverable],
|
||||||
:confirmations => [:confirmable],
|
:confirmations => [:confirmable],
|
||||||
:unlocks => [:lockable]
|
:unlocks => [:lockable]
|
||||||
}
|
}
|
||||||
|
|
||||||
STRATEGIES = [:rememberable, :authenticatable]
|
STRATEGIES = [:rememberable, :token_authenticatable, :authenticatable]
|
||||||
|
|
||||||
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE']
|
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE']
|
||||||
|
|
||||||
# Maps the messages types that are used in flash message.
|
# Maps the messages types that are used in flash message.
|
||||||
FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid, :timeout, :inactive, :locked ]
|
FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid, :invalid_token, :timeout, :inactive, :locked ]
|
||||||
|
|
||||||
# Declare encryptors length which are used in migrations.
|
# Declare encryptors length which are used in migrations.
|
||||||
ENCRYPTORS_LENGTH = {
|
ENCRYPTORS_LENGTH = {
|
||||||
|
@ -131,6 +132,21 @@ module Devise
|
||||||
mattr_accessor :mailer_sender
|
mattr_accessor :mailer_sender
|
||||||
@@mailer_sender
|
@@mailer_sender
|
||||||
|
|
||||||
|
# Array of known events that should trigger a authentication token reset.
|
||||||
|
#
|
||||||
|
# == Valid events:
|
||||||
|
#
|
||||||
|
# Warden: :after_set_user, :before_logout
|
||||||
|
# Authenticatable: :after_changed_password
|
||||||
|
#
|
||||||
|
# Note: If set to nil, authentication token will never be reset automatically.
|
||||||
|
mattr_accessor :reset_authentication_token_on
|
||||||
|
@@reset_authentication_token_on = nil
|
||||||
|
|
||||||
|
# Authentication token params key name of choice. E.g. /users/sign_in?some_key=...
|
||||||
|
mattr_accessor :authentication_token_param_key
|
||||||
|
@@authentication_token_param_key = :auth_token
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Default way to setup Devise. Run script/generate devise_install to create
|
# Default way to setup Devise. Run script/generate devise_install to create
|
||||||
# a fresh initializer with all configuration values.
|
# a fresh initializer with all configuration values.
|
||||||
|
|
23
lib/devise/hooks/token_authenticatable.rb
Normal file
23
lib/devise/hooks/token_authenticatable.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# After each Warden-sign-in: Ensure authentication token is set - if this is enabled.
|
||||||
|
Warden::Manager.after_authentication do |record, warden, options|
|
||||||
|
scope = options[:scope]
|
||||||
|
puts "#"
|
||||||
|
if Devise.mappings[scope].try(:token_authenticatable?) && warden.authenticated?(scope)
|
||||||
|
Devise.reset_authentication_token_on ||= []
|
||||||
|
|
||||||
|
if Devise.reset_authentication_token_on.include?(:after_set_user)
|
||||||
|
record.reset_authentication_token!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# After each Authenticatable-password-change: Ensure authentication token is re-set - if this is enabled.
|
||||||
|
Devise.after_changed_password do |record, scope|
|
||||||
|
if Devise.mappings[scope].try(:token_authenticatable?)
|
||||||
|
Devise.reset_authentication_token_on ||= []
|
||||||
|
|
||||||
|
if Devise.reset_authentication_token_on.include?(:after_changed_password)
|
||||||
|
record.reset_authentication_token!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end if Devise.respond_to?(:after_changed_password)
|
|
@ -8,6 +8,7 @@ en:
|
||||||
unconfirmed: 'You have to confirm your account before continuing.'
|
unconfirmed: 'You have to confirm your account before continuing.'
|
||||||
locked: 'Your account is locked.'
|
locked: 'Your account is locked.'
|
||||||
invalid: 'Invalid email or password.'
|
invalid: 'Invalid email or password.'
|
||||||
|
invalid_token: 'Invalid authentication token.'
|
||||||
timeout: 'Your session expired, please sign in again to continue.'
|
timeout: 'Your session expired, please sign in again to continue.'
|
||||||
inactive: 'Your account was not activated yet.'
|
inactive: 'Your account was not activated yet.'
|
||||||
passwords:
|
passwords:
|
||||||
|
|
|
@ -46,6 +46,53 @@ module Devise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Creates events/hooks for Devise and for the given module.
|
||||||
|
#
|
||||||
|
# Devise::Models.events(Devise::Authenticable, :after_changed_password, :after_timeout_hooks)
|
||||||
|
#
|
||||||
|
# The line above creates:
|
||||||
|
#
|
||||||
|
# 1) Accessor for each hook holding any callback hooks (see +Devise::Models::config+), or explicit:
|
||||||
|
#
|
||||||
|
# Devise::Models.config(Devise::Authenticable, :after_changed_password_hooks, :after_timeout_hooks)
|
||||||
|
#
|
||||||
|
# 1) Setup module accessor hook holding any callback hooks (default fallback config that is):
|
||||||
|
#
|
||||||
|
# Devise.after_changed_password_hooks = []
|
||||||
|
# Devise.after_timeout_hooks = []
|
||||||
|
#
|
||||||
|
# 2) Callback hooks: +Devise::Authenticable.after_changed_password_hooks+ and +Devise::Authenticable.on_timeout+,
|
||||||
|
# used in same manner as +Warden::Manager::after_set_user+, etc.
|
||||||
|
#
|
||||||
|
# To add the class methods you need to have a module ClassMethods defined
|
||||||
|
# inside the given class.
|
||||||
|
#
|
||||||
|
def self.events(mod, *events)
|
||||||
|
::Devise::Models.config(mod, *events.collect { |event| :"#{event}_hooks" })
|
||||||
|
|
||||||
|
events.each do |event|
|
||||||
|
::Devise.class_eval <<-METHOD, __FILE__, __LINE__
|
||||||
|
mattr_accessor :#{event}_hooks
|
||||||
|
@@#{event}_hooks = []
|
||||||
|
|
||||||
|
# Hook for changed password event.
|
||||||
|
def self.#{event}(options = {}, &block)
|
||||||
|
raise BlockNotGiven unless block_given?
|
||||||
|
self.#{event}_hooks << [block, options]
|
||||||
|
end
|
||||||
|
METHOD
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Triggers a named event for a Devise model instance, or more explicitly
|
||||||
|
# triggers all callback hooks for this event.
|
||||||
|
#
|
||||||
|
def self.event!(object, event, *args)
|
||||||
|
object.class.send(:"#{event}_hooks").each { |hook| hook.first.call(*args[0..hook.first.arity]) }
|
||||||
|
rescue
|
||||||
|
# raise "An invalid event was triggered: #{event}. See Devise::Models::events() for usage."
|
||||||
|
end
|
||||||
|
|
||||||
# Include the chosen devise modules in your model:
|
# Include the chosen devise modules in your model:
|
||||||
#
|
#
|
||||||
# devise :authenticatable, :confirmable, :recoverable
|
# devise :authenticatable, :confirmable, :recoverable
|
||||||
|
|
|
@ -36,7 +36,8 @@ module Devise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Regenerates password salt and encrypted password each time password is set.
|
# Regenerates password salt and encrypted password each time password is set,
|
||||||
|
# and then trigger any "after_changed_password"-callbacks.
|
||||||
def password=(new_password)
|
def password=(new_password)
|
||||||
@password = new_password
|
@password = new_password
|
||||||
|
|
||||||
|
@ -44,11 +45,19 @@ module Devise
|
||||||
self.password_salt = self.class.encryptor_class.salt
|
self.password_salt = self.class.encryptor_class.salt
|
||||||
self.encrypted_password = password_digest(@password)
|
self.encrypted_password = password_digest(@password)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
::Devise::Models.event!(self, :after_changed_password, self, self.class.name.underscore.to_sym)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Verifies whether an incoming_password (ie from sign in) is the user password.
|
# Verifies whether an incoming_password (ie from sign in) is the user password.
|
||||||
def valid_password?(incoming_password)
|
def valid_password?(incoming_password)
|
||||||
password_digest(incoming_password) == encrypted_password
|
password_digest(incoming_password) == self.encrypted_password
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
|
||||||
|
# is the user authentication token.
|
||||||
|
def valid_authentication_token?(incoming_auth_token)
|
||||||
|
incoming_auth_token == self.authentication_token
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if a resource is valid upon authentication.
|
# Checks if a resource is valid upon authentication.
|
||||||
|
@ -74,7 +83,15 @@ module Devise
|
||||||
self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper)
|
self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def password_changed?
|
||||||
|
!valid_password?(params[:old_password])
|
||||||
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
|
Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys)
|
||||||
|
Devise::Models.events(self, :after_changed_password)
|
||||||
|
|
||||||
# Authenticate a user based on configured attribute keys. Returns the
|
# Authenticate a user based on configured attribute keys. Returns the
|
||||||
# authenticated user if it's valid or nil. Attributes are by default
|
# authenticated user if it's valid or nil. Attributes are by default
|
||||||
# :email and :password, but the latter is always required.
|
# :email and :password, but the latter is always required.
|
||||||
|
@ -106,7 +123,6 @@ module Devise
|
||||||
find(:first, :conditions => conditions)
|
find(:first, :conditions => conditions)
|
||||||
end
|
end
|
||||||
|
|
||||||
Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
87
lib/devise/models/token_authenticatable.rb
Normal file
87
lib/devise/models/token_authenticatable.rb
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
require 'devise/strategies/token_authenticatable'
|
||||||
|
require 'devise/hooks/token_authenticatable'
|
||||||
|
|
||||||
|
module Devise
|
||||||
|
module Models
|
||||||
|
# Token Authenticatable Module, responsible for generate authentication token and validating
|
||||||
|
# authenticity of a user while signing in using a authentication token (say follows an URL).
|
||||||
|
#
|
||||||
|
# == Configuration:
|
||||||
|
#
|
||||||
|
# You can overwrite configuration values by setting in globally in Devise (+Devise.setup+),
|
||||||
|
# using devise method, or overwriting the respective instance method.
|
||||||
|
#
|
||||||
|
# +authentication_token_param_key+ - Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
|
||||||
|
#
|
||||||
|
# +reset_authentication_token_on+ - Defines which callback hooks that should trigger a authentication token reset.
|
||||||
|
#
|
||||||
|
# == Examples:
|
||||||
|
#
|
||||||
|
# User.authenticate_with_token(:auth_token => '123456789') # returns authenticated user or nil
|
||||||
|
# User.find(1).valid_authentication_token?('rI1t6PKQ8yP7VetgwdybB') # returns true/false
|
||||||
|
#
|
||||||
|
module TokenAuthenticatable
|
||||||
|
def self.included(base)
|
||||||
|
base.class_eval do
|
||||||
|
extend ClassMethods
|
||||||
|
|
||||||
|
before_save :ensure_authentication_token!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate authentication token unless already exists.
|
||||||
|
#
|
||||||
|
def ensure_authentication_token!
|
||||||
|
self.reset_authentication_token!(false) if self.authentication_token.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate new authentication token (a.k.a. "single access token").
|
||||||
|
#
|
||||||
|
def reset_authentication_token!(do_save = true)
|
||||||
|
self.authentication_token = self.class.authentication_token
|
||||||
|
self.save if do_save
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
|
||||||
|
# is the user authentication token.
|
||||||
|
#
|
||||||
|
def valid_authentication_token?(incoming_auth_token)
|
||||||
|
incoming_auth_token.present? && incoming_auth_token == self.authentication_token
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
::Devise::Models.config(self, :authentication_token_param_key, :reset_authentication_token_on)
|
||||||
|
|
||||||
|
# Authenticate a user based on authentication token.
|
||||||
|
#
|
||||||
|
def authenticate_with_token(attributes = {})
|
||||||
|
token = attributes[::Devise.authentication_token_param_key]
|
||||||
|
resource = self.find_for_token_authentication(token)
|
||||||
|
resource if resource.try(:valid_authentication_token?, token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authentication_token
|
||||||
|
::Devise.friendly_token
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# Find first record based on conditions given (ie by the sign in form).
|
||||||
|
# Overwrite to add customized conditions, create a join, or maybe use a
|
||||||
|
# namedscope to filter records while authenticating.
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# def self.find_for_token_authentication(token, conditions = {})
|
||||||
|
# conditions = {:active => true}
|
||||||
|
# self.find_by_authentication_token(token, :conditions => conditions)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def find_for_token_authentication(token, conditions = {})
|
||||||
|
self.find_by_authentication_token(token, :conditions => conditions)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,6 +17,11 @@ module Devise
|
||||||
apply_schema :password_salt, String, :null => null
|
apply_schema :password_salt, String, :null => null
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Creates authentication_token.
|
||||||
|
def token_authenticatable
|
||||||
|
apply_schema :authentication_token, String, :limit => 20
|
||||||
|
end
|
||||||
|
|
||||||
# Creates confirmation_token, confirmed_at and confirmation_sent_at.
|
# Creates confirmation_token, confirmed_at and confirmation_sent_at.
|
||||||
def confirmable
|
def confirmable
|
||||||
apply_schema :confirmation_token, String, :limit => 20
|
apply_schema :confirmation_token, String, :limit => 20
|
||||||
|
|
37
lib/devise/strategies/token_authenticatable.rb
Normal file
37
lib/devise/strategies/token_authenticatable.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
require 'devise/strategies/base'
|
||||||
|
|
||||||
|
module Devise
|
||||||
|
module Strategies
|
||||||
|
# Strategy for signing in a user, based on a authenticatable token.
|
||||||
|
# Redirects to sign_in page if it's not authenticated.
|
||||||
|
class TokenAuthenticatable < Base
|
||||||
|
def valid?
|
||||||
|
super && authentication_token(scope).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Authenticate a user based on authenticatable token params, returning to warden
|
||||||
|
# success and the authenticated user if everything is okay. Otherwise redirect
|
||||||
|
# to sign in page.
|
||||||
|
def authenticate!
|
||||||
|
if resource = mapping.to.authenticate_with_token(params[scope] || params)
|
||||||
|
success!(resource)
|
||||||
|
else
|
||||||
|
fail!(:invalid_token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def authentication_token(scope)
|
||||||
|
if params[scope]
|
||||||
|
params[scope][::Devise.authentication_token_param_key]
|
||||||
|
else
|
||||||
|
params[::Devise.authentication_token_param_key]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Warden::Strategies.add(:token_authenticatable, Devise::Strategies::TokenAuthenticatable)
|
|
@ -25,7 +25,7 @@ class DeviseTest < ActiveSupport::TestCase
|
||||||
Devise.configure_warden(config)
|
Devise.configure_warden(config)
|
||||||
|
|
||||||
assert_equal Devise::FailureApp, config.failure_app
|
assert_equal Devise::FailureApp, config.failure_app
|
||||||
assert_equal [:rememberable, :authenticatable], config.default_strategies
|
assert_equal [:rememberable, :token_authenticatable, :authenticatable], config.default_strategies
|
||||||
assert_equal :user, config.default_scope
|
assert_equal :user, config.default_scope
|
||||||
assert config.silence_missing_strategies?
|
assert config.silence_missing_strategies?
|
||||||
end
|
end
|
||||||
|
|
126
test/integration/token_authenticatable_test.rb
Normal file
126
test/integration/token_authenticatable_test.rb
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
require 'test/test_helper'
|
||||||
|
|
||||||
|
class TokenAuthenticationTest < ActionController::IntegrationTest
|
||||||
|
|
||||||
|
test 'sign in user should authenticate with valid authentication token and proper authentication token key' do
|
||||||
|
swap Devise, :authentication_token_param_key => :secret_token do
|
||||||
|
sign_in_as_new_user_with_token(:auth_token_key => :secret_token, :auth_token => VALID_AUTHENTICATION_TOKEN)
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_template 'users/index'
|
||||||
|
assert_contain 'Welcome'
|
||||||
|
assert warden.authenticated?(:user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'user signing in with valid authentication token - but improper authentication token key - return to sign in form with error message' do
|
||||||
|
# FIXME: For some reason I18n value is not respected. Always render defalt one. =S
|
||||||
|
# store_translations :en, :devise => {:sessions => {:unauthenticated => 'Ouch!'}} do
|
||||||
|
# assert 'Ouch!', I18n.t('devise.sessions.unauthenticated') # for paranoia
|
||||||
|
|
||||||
|
swap Devise, :authentication_token_param_key => :donald_duck_token do
|
||||||
|
sign_in_as_new_user_with_token(:auth_token_key => :secret_token, :auth_token => VALID_AUTHENTICATION_TOKEN)
|
||||||
|
|
||||||
|
assert_redirected_to new_user_session_path(:unauthenticated => true)
|
||||||
|
follow_redirect!
|
||||||
|
|
||||||
|
# assert_contain 'Ouch!'
|
||||||
|
assert_contain 'Sign in'
|
||||||
|
assert_not warden.authenticated?(:user)
|
||||||
|
end
|
||||||
|
# end
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'user signing in with invalid authentication token should return to sign in form with error message' do
|
||||||
|
store_translations :en, :devise => {:sessions => {:invalid_token => 'LOL, that was not a single character correct.'}} do
|
||||||
|
sign_in_as_new_user_with_token(:auth_token => '*** INVALID TOKEN ***')
|
||||||
|
|
||||||
|
assert_redirected_to new_user_session_path(:invalid_token => true)
|
||||||
|
follow_redirect!
|
||||||
|
assert_equal users_path(Devise.authentication_token_param_key => '*** INVALID TOKEN ***'), session[:"user.return_to"]
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_contain 'LOL, that was not a single character correct.'
|
||||||
|
assert_contain 'Sign in'
|
||||||
|
assert_not warden.authenticated?(:user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "authentication token should not be reset - if not set to do so if enabled" do
|
||||||
|
swap Devise, :reset_authentication_token_on => [] do
|
||||||
|
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
user = create_user
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
|
||||||
|
# after_set_user-event
|
||||||
|
user = sign_in_as_existing_user_with_token(:auth_token => VALID_AUTHENTICATION_TOKEN)
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
|
||||||
|
# after_changed_password-event
|
||||||
|
user.password = "new_pass"
|
||||||
|
user.save
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "authentication token should be reset after changed password if enabled" do
|
||||||
|
swap Devise, :reset_authentication_token_on => [:after_changed_password] do
|
||||||
|
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
user = create_user
|
||||||
|
assert_not_blank user.authentication_token
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
|
||||||
|
# after_set_user-event
|
||||||
|
user = sign_in_as_existing_user_with_token(:auth_token => VALID_AUTHENTICATION_TOKEN)
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
|
||||||
|
# after_changed_password-event
|
||||||
|
User.expects(:authentication_token).returns("*** NEW TOKEN / CHANGED PASSWORD ***")
|
||||||
|
user.password = "new_pass"
|
||||||
|
user.save
|
||||||
|
assert_not_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Problem: Warden::Manager.after_authenticate and/or Warden::Manager.after_set_user ignores my hook. Why? =(
|
||||||
|
# See: lib/devise/hooks/token_authenticatable.rb
|
||||||
|
test "authentication token should be reset after logging in if enabled" do
|
||||||
|
swap Devise, :reset_authentication_token_on => [:after_set_user] do
|
||||||
|
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
user = create_user
|
||||||
|
assert_not_blank user.authentication_token
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
|
||||||
|
# after_changed_password-event
|
||||||
|
user.password = "new_pass"
|
||||||
|
user.save
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
|
||||||
|
# FIXME: after_set_user-event
|
||||||
|
User.expects(:authentication_token).returns("*** NEW TOKEN / SIGN IN ***")
|
||||||
|
user = sign_in_as_existing_user_with_token(:auth_token => VALID_AUTHENTICATION_TOKEN)
|
||||||
|
assert_not_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def sign_in_as_new_user_with_token(options = {}, &block)
|
||||||
|
options[:auth_token_key] ||= Devise.authentication_token_param_key
|
||||||
|
user = create_user(options)
|
||||||
|
user.authentication_token = VALID_AUTHENTICATION_TOKEN
|
||||||
|
user.save
|
||||||
|
visit users_path(options[:auth_token_key].to_sym => (options[:auth_token] || VALID_AUTHENTICATION_TOKEN))
|
||||||
|
yield if block_given?
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_in_as_existing_user_with_token(options = {}, &block)
|
||||||
|
options[:auth_token_key] ||= Devise.authentication_token_param_key
|
||||||
|
options[:auth_token] ||= VALID_AUTHENTICATION_TOKEN
|
||||||
|
user = User.authenticate_with_token(options[:auth_token_key].to_sym => options[:auth_token])
|
||||||
|
yield if block_given?
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
45
test/models/token_authenticatable_test.rb
Normal file
45
test/models/token_authenticatable_test.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
require 'test/test_helper'
|
||||||
|
|
||||||
|
class TokenAuthenticatableTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
|
test 'should generate friendly authentication token on create' do
|
||||||
|
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
user = create_user
|
||||||
|
assert_present user.authentication_token
|
||||||
|
assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'should reset authentication token' do
|
||||||
|
user = new_user
|
||||||
|
|
||||||
|
user.reset_authentication_token!(false)
|
||||||
|
previous_token = user.authentication_token
|
||||||
|
|
||||||
|
user.reset_authentication_token!(false)
|
||||||
|
assert_not_equal previous_token, user.authentication_token
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'should test for a valid authentication token' do
|
||||||
|
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
user = create_user
|
||||||
|
assert user.valid_authentication_token?(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
assert_not user.valid_authentication_token?(VALID_AUTHENTICATION_TOKEN.reverse)
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'should authenticate a valid user with authentication token and return it' do
|
||||||
|
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
user = create_user
|
||||||
|
User.any_instance.stubs(:confirmed?).returns(true)
|
||||||
|
authenticated_user = User.authenticate_with_token(:auth_token => user.authentication_token)
|
||||||
|
assert_equal authenticated_user, user
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'should return nil when authenticating an invalid user by authentication token' do
|
||||||
|
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||||
|
user = create_user
|
||||||
|
User.any_instance.stubs(:confirmed?).returns(true)
|
||||||
|
authenticated_user = User.authenticate_with_token(:auth_token => user.authentication_token.reverse)
|
||||||
|
assert_nil authenticated_user
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -17,6 +17,7 @@ ActiveRecord::Schema.define(:version => 1) do
|
||||||
t.rememberable
|
t.rememberable
|
||||||
t.trackable
|
t.trackable
|
||||||
t.lockable
|
t.lockable
|
||||||
|
t.token_authenticatable
|
||||||
end
|
end
|
||||||
|
|
||||||
t.timestamps
|
t.timestamps
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class User < ActiveRecord::Base
|
class User < ActiveRecord::Base
|
||||||
devise :authenticatable, :confirmable, :recoverable, :rememberable, :trackable,
|
devise :authenticatable, :confirmable, :recoverable, :rememberable, :trackable,
|
||||||
:validatable, :timeoutable, :lockable
|
:validatable, :timeoutable, :lockable, :token_authenticatable
|
||||||
attr_accessible :username, :email, :password, :password_confirmation
|
attr_accessible :username, :email, :password, :password_confirmation
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,6 @@ class User
|
||||||
include MongoMapper::Document
|
include MongoMapper::Document
|
||||||
key :created_at, DateTime
|
key :created_at, DateTime
|
||||||
devise :authenticatable, :confirmable, :recoverable, :rememberable, :trackable,
|
devise :authenticatable, :confirmable, :recoverable, :rememberable, :trackable,
|
||||||
:validatable, :timeoutable, :lockable
|
:validatable, :timeoutable, :lockable, :token_authenticatable
|
||||||
# attr_accessible :username, :email, :password, :password_confirmation
|
# attr_accessible :username, :email, :password, :password_confirmation
|
||||||
end
|
end
|
||||||
|
|
5
test/support/tests_helper.rb
Normal file
5
test/support/tests_helper.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class ActiveSupport::TestCase
|
||||||
|
|
||||||
|
VALID_AUTHENTICATION_TOKEN = 'AbCdEfGhIjKlMnOpQrSt'.freeze
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in a new issue