knock 1.4.2 → 1.5
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.
- checksums.yaml +8 -8
- data/app/controllers/knock/application_controller.rb +1 -1
- data/app/controllers/knock/auth_token_controller.rb +32 -7
- data/app/model/knock/auth_token.rb +31 -7
- data/lib/generators/knock/token_controller_generator.rb +25 -0
- data/lib/generators/templates/entity_token_controller.rb.erb +2 -0
- data/lib/generators/templates/knock.rb +25 -3
- data/lib/knock.rb +7 -0
- data/lib/knock/authenticable.rb +45 -6
- data/lib/knock/version.rb +1 -1
- data/test/controllers/knock/auth_token_controller_test.rb +11 -0
- data/test/dummy/app/controllers/admin_protected_controller.rb +7 -0
- data/test/dummy/app/controllers/admin_token_controller.rb +2 -0
- data/test/dummy/app/controllers/composite_name_entity_protected_controller.rb +7 -0
- data/test/dummy/app/controllers/vendor_protected_controller.rb +11 -0
- data/test/dummy/app/controllers/vendor_token_controller.rb +2 -0
- data/test/dummy/app/models/admin.rb +16 -0
- data/test/dummy/app/models/composite_name_entity.rb +3 -0
- data/test/dummy/app/models/vendor.rb +3 -0
- data/test/dummy/config/initializers/knock.rb +10 -0
- data/test/dummy/config/routes.rb +8 -0
- data/test/dummy/db/migrate/20160519075733_create_admins.rb +10 -0
- data/test/dummy/db/migrate/20160522051816_create_vendors.rb +10 -0
- data/test/dummy/db/migrate/20160522181712_create_composite_name_entities.rb +10 -0
- data/test/dummy/db/schema.rb +22 -1
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +333 -91
- data/test/dummy/test/controllers/admin_protected_controller_test.rb +49 -0
- data/test/dummy/test/controllers/admin_token_controller_test.rb +22 -0
- data/test/dummy/test/controllers/composite_name_entity_protected_controller_test.rb +49 -0
- data/test/dummy/test/controllers/vendor_protected_controller_test.rb +55 -0
- data/test/dummy/test/controllers/vendor_token_controller_test.rb +22 -0
- data/test/dummy/test/models/admin_test.rb +7 -0
- data/test/dummy/test/models/vendor_test.rb +7 -0
- data/test/{dummy/test/fixtures/users.yml → fixtures/admins.yml} +1 -5
- data/test/fixtures/composite_name_entities.yml +5 -0
- data/test/fixtures/vendors.yml +5 -0
- data/test/generators/token_controller_generator_test.rb +31 -0
- data/test/model/knock/auth_token_test.rb +33 -9
- data/test/support/generators_test_helper.rb +9 -0
- data/test/test_helper.rb +9 -0
- data/test/tmp/app/controllers/admin_token_controller.rb +2 -0
- data/test/tmp/app/controllers/admin_user_token_controller.rb +2 -0
- data/test/tmp/app/controllers/user_admin_token_controller.rb +2 -0
- data/test/tmp/app/controllers/user_token_controller.rb +2 -0
- data/test/tmp/config/routes.rb +17 -0
- metadata +76 -6
- data/test/tmp/config/initializers/knock.rb +0 -86
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MzZjZmQ3ZDIxMmQ3NDBjMTc2NmU1NGUxNmQwNWM0NmMyNzc5ZGNkYw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YzcwZWUzNDRlYjFiOWIwMjdkZGI0NWFkY2RkZGMwMTBiYzgyYTIyMw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YjYyMjJkZjM0ZDRmNzA4OTk4YTFiZTc3NTUzMDQzNWEwMmEyMmRjYzc1OTMw
|
10
|
+
YzMzMTZhNzgzM2ZjZDhjZGIxNTAwZjdkMGEwZDIyZjVkMmYzZDI1ZWMxMWZj
|
11
|
+
YTQ3MTE3OWY5YWRiODk1ZDkwMjZlZTkzM2ZjYzA5MjdhNzU5NjA=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NDM1NGRjNjlkYWM2NWY3YTVhZTNkNTFiNGYyY2M3NjdhMmVlNmJlMDE0MTc1
|
14
|
+
ZTJhY2I0M2RlNTY4NDEzYzIzZGI2Mjk4NTkyNGVmODljOWYwNWNmMDYzNjgz
|
15
|
+
OTFmMmU5OGY3MjQ3MTFlNWNlMDQyMDRlMWJhNzIxOTJlYjlmZWM=
|
@@ -2,23 +2,48 @@ require_dependency "knock/application_controller"
|
|
2
2
|
|
3
3
|
module Knock
|
4
4
|
class AuthTokenController < ApplicationController
|
5
|
-
before_action :authenticate
|
5
|
+
before_action :authenticate
|
6
6
|
|
7
7
|
def create
|
8
|
-
render json:
|
8
|
+
render json: auth_token, status: :created
|
9
9
|
end
|
10
10
|
|
11
11
|
private
|
12
|
-
def authenticate
|
13
|
-
|
12
|
+
def authenticate
|
13
|
+
unless entity.present? && entity.authenticate(auth_params[:password])
|
14
|
+
raise Knock.not_found_exception_class
|
15
|
+
end
|
14
16
|
end
|
15
17
|
|
16
18
|
def auth_token
|
17
|
-
|
19
|
+
if entity.respond_to? :to_token_payload
|
20
|
+
AuthToken.new payload: entity.to_token_payload
|
21
|
+
else
|
22
|
+
AuthToken.new payload: { sub: entity.id }
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
|
-
def
|
21
|
-
|
26
|
+
def entity
|
27
|
+
@entity ||=
|
28
|
+
if self.class.name == "Knock::AuthTokenController"
|
29
|
+
warn "[DEPRECATION]: Routing to `AuthTokenController` directly is deprecated. Please use `<Entity Name>TokenController` inheriting from it instead. E.g. `UserTokenController`"
|
30
|
+
warn "[DEPRECATION]: Relying on `Knock.current_user_from_handle` is deprecated. Please implement `User#from_token_request` instead."
|
31
|
+
Knock.current_user_from_handle.call auth_params[Knock.handle_attr]
|
32
|
+
else
|
33
|
+
if entity_class.respond_to? :from_token_request
|
34
|
+
entity_class.from_token_request request
|
35
|
+
else
|
36
|
+
entity_class.find_by email: auth_params[:email]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def entity_class
|
42
|
+
entity_name.constantize
|
43
|
+
end
|
44
|
+
|
45
|
+
def entity_name
|
46
|
+
self.class.name.split('TokenController').first
|
22
47
|
end
|
23
48
|
|
24
49
|
def auth_params
|
@@ -3,6 +3,7 @@ require 'jwt'
|
|
3
3
|
module Knock
|
4
4
|
class AuthToken
|
5
5
|
attr_reader :token
|
6
|
+
attr_reader :payload
|
6
7
|
|
7
8
|
def initialize payload: {}, token: nil
|
8
9
|
if token.present?
|
@@ -16,8 +17,21 @@ module Knock
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
-
|
20
|
+
def entity_for entity_class
|
21
|
+
if entity_class.respond_to? :from_token_payload
|
22
|
+
entity_class.from_token_payload @payload
|
23
|
+
else
|
24
|
+
if entity_class.to_s == "User" && Knock.respond_to?(:current_user_from_token)
|
25
|
+
warn "[DEPRECATION]: `Knock.current_user_from_token` is deprecated. Please implement `User.from_token_payload` instead."
|
26
|
+
Knock.current_user_from_token.call @payload
|
27
|
+
else
|
28
|
+
entity_class.find @payload['sub']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_json options = {}
|
34
|
+
{jwt: @token}.to_json
|
21
35
|
end
|
22
36
|
|
23
37
|
private
|
@@ -36,15 +50,25 @@ module Knock
|
|
36
50
|
end
|
37
51
|
|
38
52
|
def claims
|
39
|
-
{
|
40
|
-
|
41
|
-
|
42
|
-
|
53
|
+
_claims = {}
|
54
|
+
_claims[:exp] = token_lifetime if verify_lifetime?
|
55
|
+
_claims[:aud] = token_audience if verify_audience?
|
56
|
+
_claims
|
57
|
+
end
|
58
|
+
|
59
|
+
def token_lifetime
|
60
|
+
Knock.token_lifetime.from_now.to_i if verify_lifetime?
|
61
|
+
end
|
62
|
+
|
63
|
+
def verify_lifetime?
|
64
|
+
!Knock.token_lifetime.nil?
|
43
65
|
end
|
44
66
|
|
45
67
|
def verify_claims
|
46
68
|
{
|
47
|
-
aud: token_audience,
|
69
|
+
aud: token_audience,
|
70
|
+
verify_aud: verify_audience?,
|
71
|
+
verify_expiration: verify_lifetime?
|
48
72
|
}
|
49
73
|
end
|
50
74
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Knock
|
2
|
+
class TokenControllerGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path("../../templates", __FILE__)
|
4
|
+
argument :name, type: :string
|
5
|
+
|
6
|
+
desc <<-DESC
|
7
|
+
Creates a Knock token controller for the given entity
|
8
|
+
and add the corresponding routes.
|
9
|
+
DESC
|
10
|
+
|
11
|
+
def copy_controller_file
|
12
|
+
template 'entity_token_controller.rb.erb', "app/controllers/#{name.underscore}_token_controller.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_route
|
16
|
+
route "post '#{name.underscore}_token' => '#{name.underscore}_token#create'"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def entity_name
|
22
|
+
name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
Knock.setup do |config|
|
2
2
|
|
3
|
+
## [DEPRECATED]
|
4
|
+
## This is deprecated in favor of `User.from_token_request`.
|
5
|
+
##
|
3
6
|
## User handle attribute
|
4
7
|
## ---------------------
|
5
8
|
##
|
@@ -8,6 +11,9 @@ Knock.setup do |config|
|
|
8
11
|
## Default:
|
9
12
|
# config.handle_attr = :email
|
10
13
|
|
14
|
+
## [DEPRECATED]
|
15
|
+
## This is deprecated in favor of `User.from_token_request`.
|
16
|
+
##
|
11
17
|
## Current user retrieval from handle when signing in
|
12
18
|
## --------------------------------------------------
|
13
19
|
##
|
@@ -18,11 +24,16 @@ Knock.setup do |config|
|
|
18
24
|
## AuthTokenController parameters. It also uses the same variable to enforce
|
19
25
|
## permitted values in the controller.
|
20
26
|
##
|
21
|
-
## You must raise
|
27
|
+
## You must raise an exception if the resource cannot be retrieved.
|
28
|
+
## The type of the exception is configured in config.not_found_exception_class_name,
|
29
|
+
## and it is ActiveRecord::RecordNotFound by default
|
22
30
|
##
|
23
31
|
## Default:
|
24
32
|
# config.current_user_from_handle = -> (handle) { User.find_by! Knock.handle_attr => handle }
|
25
33
|
|
34
|
+
## [DEPRECATED]
|
35
|
+
## This is depreacted in favor of `User.from_token_payload`.
|
36
|
+
##
|
26
37
|
## Current user retrieval when validating token
|
27
38
|
## --------------------------------------------
|
28
39
|
##
|
@@ -30,7 +41,9 @@ Knock.setup do |config|
|
|
30
41
|
## By default, it assumes you have a model called `User` and that
|
31
42
|
## the user_id is stored in the 'sub' claim.
|
32
43
|
##
|
33
|
-
## You must raise
|
44
|
+
## You must raise an exception if the resource cannot be retrieved.
|
45
|
+
## The type of the exception is configured in config.not_found_exception_class_name,
|
46
|
+
## and it is ActiveRecord::RecordNotFound by default
|
34
47
|
##
|
35
48
|
## Default:
|
36
49
|
# config.current_user_from_token = -> (claims) { User.find claims['sub'] }
|
@@ -39,7 +52,8 @@ Knock.setup do |config|
|
|
39
52
|
## Expiration claim
|
40
53
|
## ----------------
|
41
54
|
##
|
42
|
-
## How long before a token is expired.
|
55
|
+
## How long before a token is expired. If nil is provided, token will
|
56
|
+
## last forever.
|
43
57
|
##
|
44
58
|
## Default:
|
45
59
|
# config.token_lifetime = 1.day
|
@@ -83,4 +97,12 @@ Knock.setup do |config|
|
|
83
97
|
##
|
84
98
|
## Default:
|
85
99
|
# config.token_public_key = nil
|
100
|
+
|
101
|
+
## Exception Class
|
102
|
+
## ---------------
|
103
|
+
##
|
104
|
+
## Configure the exception to be used when user cannot be found.
|
105
|
+
##
|
106
|
+
## Default:
|
107
|
+
# config.not_found_exception_class_name = 'ActiveRecord::RecordNotFound'
|
86
108
|
end
|
data/lib/knock.rb
CHANGED
@@ -26,6 +26,13 @@ module Knock
|
|
26
26
|
mattr_accessor :token_public_key
|
27
27
|
self.token_public_key = nil
|
28
28
|
|
29
|
+
mattr_accessor :not_found_exception_class_name
|
30
|
+
self.not_found_exception_class_name = 'ActiveRecord::RecordNotFound'
|
31
|
+
|
32
|
+
def self.not_found_exception_class
|
33
|
+
not_found_exception_class_name.to_s.constantize
|
34
|
+
end
|
35
|
+
|
29
36
|
# Default way to setup Knock. Run `rails generate knock:install` to create
|
30
37
|
# a fresh initializer with all configuration values.
|
31
38
|
def self.setup
|
data/lib/knock/authenticable.rb
CHANGED
@@ -1,14 +1,53 @@
|
|
1
1
|
module Knock::Authenticable
|
2
|
-
def
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
def authenticate
|
3
|
+
warn "[DEPRECATION]: `authenticate` is deprecated. Please use `authenticate_user` instead."
|
4
|
+
head(:unauthorized) unless authenticate_for(User)
|
5
|
+
end
|
6
|
+
|
7
|
+
def authenticate_for entity_class
|
8
|
+
token = params[:token] || token_from_request_headers
|
9
|
+
return nil if token.nil?
|
10
|
+
|
11
|
+
begin
|
12
|
+
@entity = Knock::AuthToken.new(token: token).entity_for(entity_class)
|
13
|
+
define_current_entity_getter(entity_class)
|
14
|
+
@entity
|
6
15
|
rescue
|
7
16
|
nil
|
8
17
|
end
|
9
18
|
end
|
10
19
|
|
11
|
-
|
12
|
-
|
20
|
+
private
|
21
|
+
|
22
|
+
def method_missing(method, *args)
|
23
|
+
prefix, entity_name = method.to_s.split('_', 2)
|
24
|
+
case prefix
|
25
|
+
when 'authenticate'
|
26
|
+
head(:unauthorized) unless authenticate_entity(entity_name)
|
27
|
+
when 'current'
|
28
|
+
authenticate_entity(entity_name)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def authenticate_entity(entity_name)
|
35
|
+
entity_class = entity_name.camelize.constantize
|
36
|
+
send(:authenticate_for, entity_class)
|
37
|
+
end
|
38
|
+
|
39
|
+
def token_from_request_headers
|
40
|
+
unless request.headers['Authorization'].nil?
|
41
|
+
request.headers['Authorization'].split.last
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def define_current_entity_getter entity_class
|
46
|
+
getter_name = "current_#{entity_class.to_s.underscore}"
|
47
|
+
unless self.respond_to?(getter_name)
|
48
|
+
self.class.send(:define_method, getter_name) do
|
49
|
+
@entity ||= nil
|
50
|
+
end
|
51
|
+
end
|
13
52
|
end
|
14
53
|
end
|
data/lib/knock/version.rb
CHANGED
@@ -10,6 +10,10 @@ module Knock
|
|
10
10
|
@user ||= users(:one)
|
11
11
|
end
|
12
12
|
|
13
|
+
test "it's using configured custom exception" do
|
14
|
+
assert_equal Knock.not_found_exception_class, Knock::MyCustomException
|
15
|
+
end
|
16
|
+
|
13
17
|
test "responds with 404 if user does not exist" do
|
14
18
|
post :create, auth: { email: 'wrong@example.net', password: '' }
|
15
19
|
assert_response :not_found
|
@@ -24,5 +28,12 @@ module Knock
|
|
24
28
|
post :create, auth: { email: user.email, password: 'secret' }
|
25
29
|
assert_response :created
|
26
30
|
end
|
31
|
+
|
32
|
+
test "response contains token" do
|
33
|
+
post :create, auth: { email: user.email, password: 'secret' }
|
34
|
+
|
35
|
+
content = JSON.parse(response.body)
|
36
|
+
assert_equal true, content.has_key?("jwt")
|
37
|
+
end
|
27
38
|
end
|
28
39
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Admin < ActiveRecord::Base
|
2
|
+
has_secure_password
|
3
|
+
|
4
|
+
def self.from_token_request request
|
5
|
+
email = request.params["auth"] && request.params["auth"]["email"]
|
6
|
+
self.find_by email: email
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.from_token_payload payload
|
10
|
+
self.find payload["sub"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_token_payload
|
14
|
+
{sub: id}
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Knock.setup do |config|
|
2
|
+
config.token_signature_algorithm = 'HS256'
|
3
|
+
config.token_secret_signature_key = -> { Rails.application.secrets.secret_key_base }
|
4
|
+
config.token_public_key = nil
|
5
|
+
config.token_audience = nil
|
6
|
+
|
7
|
+
config.current_user_from_handle = -> handle { User.find_by(Knock.handle_attr => handle) || raise(Knock::MyCustomException) }
|
8
|
+
config.current_user_from_token = -> claims { User.find_by(id: claims['sub']) || raise(Knock::MyCustomException) }
|
9
|
+
config.not_found_exception_class_name = 'Knock::MyCustomException'
|
10
|
+
end
|
data/test/dummy/config/routes.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
+
post 'admin_token' => 'admin_token#create'
|
3
|
+
post 'vendor_token' => 'vendor_token#create'
|
4
|
+
|
2
5
|
resources :protected_resources
|
3
6
|
resource :current_user
|
7
|
+
|
8
|
+
resources :admin_protected
|
9
|
+
resources :composite_name_entity_protected
|
10
|
+
resources :vendor_protected
|
11
|
+
|
4
12
|
mount Knock::Engine => "/knock"
|
5
13
|
end
|