knocknock 0.0.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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +206 -0
  4. data/Rakefile +37 -0
  5. data/app/controllers/knocknock/auth_tokens_controller.rb +54 -0
  6. data/app/model/knocknock/auth_token.rb +69 -0
  7. data/config/routes.rb +2 -0
  8. data/lib/generators/knocknock/install_generator.rb +20 -0
  9. data/lib/generators/knocknock/token_controller_generator.rb +25 -0
  10. data/lib/generators/templates/access_token.rb.erb +17 -0
  11. data/lib/generators/templates/create_access_token.rb +9 -0
  12. data/lib/generators/templates/knocknock.rb +50 -0
  13. data/lib/generators/templates/resource_tokens_controller.rb.erb +2 -0
  14. data/lib/knocknock.rb +25 -0
  15. data/lib/knocknock/authenticatable.rb +47 -0
  16. data/lib/knocknock/engine.rb +6 -0
  17. data/lib/knocknock/version.rb +3 -0
  18. data/lib/tasks/knocknock_tasks.rake +4 -0
  19. data/test/dummy/README.rdoc +28 -0
  20. data/test/dummy/Rakefile +6 -0
  21. data/test/dummy/app/controllers/admin_protected_controller.rb +7 -0
  22. data/test/dummy/app/controllers/admin_tokens_controller.rb +2 -0
  23. data/test/dummy/app/controllers/application_controller.rb +3 -0
  24. data/test/dummy/app/controllers/user_protected_controller.rb +7 -0
  25. data/test/dummy/app/controllers/user_tokens_controller.rb +2 -0
  26. data/test/dummy/app/helpers/application_helper.rb +2 -0
  27. data/test/dummy/app/models/access_token.rb +3 -0
  28. data/test/dummy/app/models/admin.rb +5 -0
  29. data/test/dummy/app/models/user.rb +5 -0
  30. data/test/dummy/bin/bundle +3 -0
  31. data/test/dummy/bin/rails +4 -0
  32. data/test/dummy/bin/rake +4 -0
  33. data/test/dummy/bin/setup +29 -0
  34. data/test/dummy/config.ru +4 -0
  35. data/test/dummy/config/application.rb +23 -0
  36. data/test/dummy/config/boot.rb +5 -0
  37. data/test/dummy/config/database.yml +25 -0
  38. data/test/dummy/config/environment.rb +5 -0
  39. data/test/dummy/config/environments/development.rb +56 -0
  40. data/test/dummy/config/environments/production.rb +82 -0
  41. data/test/dummy/config/environments/test.rb +44 -0
  42. data/test/dummy/config/initializers/assets.rb +11 -0
  43. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  44. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  45. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  46. data/test/dummy/config/initializers/inflections.rb +16 -0
  47. data/test/dummy/config/initializers/mime_types.rb +4 -0
  48. data/test/dummy/config/initializers/session_store.rb +3 -0
  49. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  50. data/test/dummy/config/locales/en.yml +23 -0
  51. data/test/dummy/config/routes.rb +6 -0
  52. data/test/dummy/config/secrets.yml +22 -0
  53. data/test/dummy/db/development.sqlite3 +0 -0
  54. data/test/dummy/db/migrate/20150713101607_create_users.rb +10 -0
  55. data/test/dummy/db/migrate/20150922015152_create_admins.rb +10 -0
  56. data/test/dummy/db/migrate/20160218200351_create_access_tokens.rb +11 -0
  57. data/test/dummy/db/schema.rb +40 -0
  58. data/test/dummy/db/test.sqlite3 +0 -0
  59. data/test/dummy/log/development.log +52 -0
  60. data/test/dummy/log/test.log +9320 -0
  61. data/test/dummy/public/404.html +67 -0
  62. data/test/dummy/public/422.html +67 -0
  63. data/test/dummy/public/500.html +66 -0
  64. data/test/dummy/public/favicon.ico +0 -0
  65. data/test/dummy/test/controllers/admin_protected_controller_test.rb +49 -0
  66. data/test/dummy/test/controllers/admin_tokens_controller_test.rb +22 -0
  67. data/test/dummy/test/controllers/user_protected_controller_test.rb +49 -0
  68. data/test/dummy/test/controllers/user_tokens_controller_test.rb +23 -0
  69. data/test/dummy/test/fixtures/access_tokens.yml +11 -0
  70. data/test/dummy/test/models/access_token_test.rb +7 -0
  71. data/test/dummy/test/models/admin_test.rb +4 -0
  72. data/test/dummy/test/models/user_test.rb +4 -0
  73. data/test/fixtures/admins.yml +5 -0
  74. data/test/fixtures/users.yml +9 -0
  75. data/test/generators/install_generator_test.rb +15 -0
  76. data/test/generators/token_controller_generator_test.rb +19 -0
  77. data/test/knocknock_test.rb +9 -0
  78. data/test/model/knocknock/auth_token_test.rb +50 -0
  79. data/test/support/generators_test_helper.rb +9 -0
  80. data/test/test_helper.rb +38 -0
  81. data/test/tmp/app/controllers/admin_tokens_controller.rb +2 -0
  82. data/test/tmp/app/controllers/user_tokens_controller.rb +2 -0
  83. data/test/tmp/config/routes.rb +8 -0
  84. metadata +253 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d201ff692191ecb40b82db654dd5ec345cde6e76
4
+ data.tar.gz: 4dba1fabb76874fbe9926917cefeaa3d5be340fe
5
+ SHA512:
6
+ metadata.gz: c8b0068fecc55b4e5e79d6078f6716b43854e106e1be00a5f6bb491514173c001cabdd11bb02d08e89d5eafc40653362de756603e51f52046d9295722c4c0fba
7
+ data.tar.gz: 0c9c7b061476df87e3b28ccc907cf083f12a372048beda55e7676d66ade0fdfabbc728c7fca005d5c3ec343bb40827e2d4dba399163f2b603c34143d80ddf77c
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Arnaud MESUREUR
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,206 @@
1
+ # knocknock
2
+ [![Gem Version](https://badge.fury.io/rb/knock.svg)](http://badge.fury.io/rb/knock)
3
+ [![Build Status](https://travis-ci.org/nsarno/knock.svg)](https://travis-ci.org/nsarno/knock)
4
+ [![Test Coverage](https://codeclimate.com/github/nsarno/knock/badges/coverage.svg)](https://codeclimate.com/github/nsarno/knock/coverage)
5
+ [![Code Climate](https://codeclimate.com/github/nsarno/knock/badges/gpa.svg)](https://codeclimate.com/github/nsarno/knock)
6
+ [![Dependency Status](https://gemnasium.com/nsarno/knock.svg)](https://gemnasium.com/nsarno/knock)
7
+
8
+ Seamless JWT authentication for Rails API
9
+
10
+ ## Description
11
+
12
+ Knock is an authentication solution for Rails API-only application based on JSON Web Tokens.
13
+
14
+ ### What are JSON Web Tokens?
15
+
16
+ [![JWT](http://jwt.io/assets/badge.svg)](http://jwt.io/)
17
+
18
+ ### Why should I use this?
19
+
20
+ - It's lightweight.
21
+ - It's tailored for Rails API-only application.
22
+ - It's [stateless](https://en.wikipedia.org/wiki/Representational_state_transfer#Stateless).
23
+ - It works out of the box with [Auth0](https://auth0.com/docs/server-apis/rails).
24
+
25
+ ### Is this gem going to be maintained?
26
+
27
+ Yes. And we will keep improving it.
28
+
29
+ ## Getting Started
30
+
31
+ ### Requirements
32
+
33
+ Knock makes one assumption about your user model:
34
+
35
+ It must have an `authenticate` method, similar to the one added by [has_secure_password](http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password).
36
+
37
+ ```ruby
38
+ class User < ActiveRecord::Base
39
+ has_secure_password
40
+ end
41
+ ```
42
+
43
+ Using `has_secure_password` is recommended, but you don't have to as long as your user model implements an `authenticate` instance method with the same behavior.
44
+
45
+ Knock (>= 2.0) can handle multiple user models (eg: User, Admin, etc).
46
+ Although, for the sake of simplicity, all examples in this README will use the `User` model.
47
+
48
+ ### Installation
49
+
50
+ Add this line to your application's Gemfile:
51
+
52
+ ```ruby
53
+ gem 'knock'
54
+ ```
55
+
56
+ And then execute:
57
+
58
+ $ bundle install
59
+
60
+ Finally, run the install generator:
61
+
62
+ $ rails generate knock:install
63
+
64
+ It will create the following initializer `config/initializers/knock.rb`.
65
+ This file contains all the informations about the existing configuration options.
66
+
67
+ If you don't use an external authentication solution like Auth0, you also need to
68
+ provide a way for users to authenticate:
69
+
70
+ $ rails generate knock:token_controller user
71
+
72
+ This will generate the controller `user_token_controller.rb` and add the required
73
+ route to your `config/routes.rb` file.
74
+
75
+ ### Usage
76
+
77
+ Include the `Knock::Authenticatable` module in your `ApplicationController`
78
+
79
+ ```ruby
80
+ class ApplicationController < ActionController::API
81
+ include Knock::Authenticatable
82
+ end
83
+ ```
84
+
85
+ You can now restrict access to your controllers like this:
86
+
87
+ ```ruby
88
+ class SecuredController < ApplicationController
89
+ before_action :authenticate_user
90
+
91
+ def index
92
+ # etc...
93
+ end
94
+
95
+ # etc...
96
+ end
97
+ ```
98
+
99
+ If your user model is something other than User, replace "user" with "yourmodel". The same logic applies everywhere.
100
+
101
+ You can access the current authenticated user in your controller with this method:
102
+
103
+ ```ruby
104
+ current_user
105
+ ```
106
+
107
+ If no valid token is passed with the request, Knock will respond with:
108
+
109
+ ```
110
+ head :unauthorized
111
+ ```
112
+
113
+ If you just want to read the `current_user`, without actually authenticating, you can also do that:
114
+
115
+ ```ruby
116
+ class CurrentUsersController < ApplicationController
117
+ def show
118
+ if current_user
119
+ head :ok
120
+ else
121
+ head :not_found
122
+ end
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### Authenticating from a web or mobile application:
128
+
129
+ Example request to get a token from your API:
130
+ ```
131
+ POST /user_auth_token
132
+ {"auth": {"email": "foo@bar.com", "password": "secret"}}
133
+ ```
134
+
135
+ Example response from the API:
136
+ ```
137
+ 201 Created
138
+ {"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"}
139
+ ```
140
+
141
+ To make an authenticated request to your API, you need to pass the token in the request header:
142
+ ```
143
+ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
144
+ GET /my_resources
145
+ ```
146
+
147
+ **NB:** HTTPS should always be enabled when sending a password or token in your request.
148
+
149
+ ### Authenticated tests
150
+
151
+ To authenticate within your tests:
152
+
153
+ 1. Create a valid token
154
+ 2. Pass it in your request
155
+
156
+ e.g.
157
+
158
+ ```ruby
159
+ class SecuredControllerTest < ActionController::TestCase
160
+ def authenticate
161
+ token = Knock::AuthToken.new(payload: { sub: users(:one).id }).token
162
+ request.env['HTTP_AUTHORIZATION'] = "Bearer #{token}"
163
+ end
164
+
165
+ setup do
166
+ authenticate
167
+ end
168
+
169
+ it 'responds successfully' do
170
+ get :index
171
+ assert_response :success
172
+ end
173
+ end
174
+ ```
175
+
176
+ ### Algorithms
177
+
178
+ The JWT spec supports different kind of cryptographic signing algorithms.
179
+ You can set `token_signature_algorithm` to use the one you want in the
180
+ initializer or do nothing and use the default one (HS256).
181
+
182
+ You can specify any of the algorithms supported by the
183
+ [jwt](https://github.com/jwt/ruby-jwt) gem.
184
+
185
+ If the algorithm you use requires a public key, you also need to set
186
+ `token_public_key` in the initializer.
187
+
188
+ ## CORS
189
+
190
+ To enable cross-origin resource sharing, check out the [rack-cors](https://github.com/cyu/rack-cors) gem.
191
+
192
+ ## Related links
193
+
194
+ - [10 things you should know about tokens](https://auth0.com/blog/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies/)
195
+
196
+ ## Contributing
197
+
198
+ 1. Fork it ( https://github.com/nsarno/knock/fork )
199
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
200
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
201
+ 4. Push to the branch (`git push origin my-new-feature`)
202
+ 5. Create a new Pull Request
203
+
204
+ ## License
205
+
206
+ MIT
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Knock'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,54 @@
1
+ module Knocknock
2
+ class AuthTokensController < ActionController::API
3
+ before_action :authenticate, only: :create
4
+ before_action :authenticate_resource, only: :destroy
5
+
6
+ def create
7
+ render json: auth_token, status: :created
8
+ end
9
+
10
+ def destroy
11
+ @access_token.destroy
12
+ head :ok
13
+ end
14
+
15
+ private
16
+ def authenticate
17
+ unless resource.present? && resource.authenticate(auth_params[:password])
18
+ head :not_found
19
+ end
20
+ end
21
+
22
+ def authenticate_resource
23
+ token = request.headers['Authorization'].split(' ').last
24
+ @access_token = Knock::AuthToken.new(token: token).access_token
25
+ rescue
26
+ head :unauthorized
27
+ end
28
+
29
+ def auth_token
30
+ AuthToken.new payload: { sub: resource.access_tokens.create.token }
31
+ end
32
+
33
+ def auth_params
34
+ params.require(:auth).permit!
35
+ end
36
+
37
+ def resource
38
+ @resource ||=
39
+ if resource_class.respond_to? :find_for_token_creation
40
+ resource_class.find_for_token_creation auth_params
41
+ else
42
+ resource_class.find_by email: auth_params[:email]
43
+ end
44
+ end
45
+
46
+ def resource_class
47
+ resource_name.constantize
48
+ end
49
+
50
+ def resource_name
51
+ self.class.name.split('TokensController').first
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,69 @@
1
+ require 'jwt'
2
+
3
+ module Knocknock
4
+ class AuthToken
5
+ attr_reader :token
6
+ attr_reader :payload
7
+
8
+ def initialize payload: {}, token: nil
9
+ if token.present?
10
+ @payload, _ = JWT.decode token, decode_key, true, options
11
+ @token = token
12
+ else
13
+ @payload = payload
14
+ @token = JWT.encode claims.merge(payload),
15
+ secret_key,
16
+ Knocknock.token_signature_algorithm
17
+ end
18
+ end
19
+
20
+ def access_token
21
+ AccessToken.find_by(token: @payload['sub'])
22
+ end
23
+
24
+ def resource resource_class
25
+ AccessToken.find_by(token: @payload['sub'],
26
+ authenticatee_type: resource_class.to_s).authenticatee
27
+ end
28
+
29
+ def to_json options = {}
30
+ { jwt: @token }.to_json
31
+ end
32
+
33
+ private
34
+ def secret_key
35
+ Knocknock.token_secret_signature_key.call
36
+ end
37
+
38
+ def decode_key
39
+ Knocknock.token_public_key || secret_key
40
+ end
41
+
42
+ def options
43
+ verify_claims.merge({
44
+ algorithm: Knocknock.token_signature_algorithm
45
+ })
46
+ end
47
+
48
+ def claims
49
+ {
50
+ exp: Knocknock.token_lifetime.from_now.to_i,
51
+ aud: token_audience
52
+ }
53
+ end
54
+
55
+ def verify_claims
56
+ {
57
+ aud: token_audience, verify_aud: verify_audience?
58
+ }
59
+ end
60
+
61
+ def token_audience
62
+ verify_audience? && Knocknock.token_audience.call
63
+ end
64
+
65
+ def verify_audience?
66
+ Knocknock.token_audience.present?
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,2 @@
1
+ Knocknock::Engine.routes.draw do
2
+ end
@@ -0,0 +1,20 @@
1
+ module Knocknock
2
+ class InstallGenerator < Rails::Generators::Base
3
+ source_root File.expand_path("../../templates", __FILE__)
4
+
5
+ desc "Creates a Knocknock initializer and an AccessToken model"
6
+
7
+ def copy_initializer
8
+ template 'knocknock.rb', 'config/initializers/knocknock.rb'
9
+ end
10
+
11
+ def create_access_token_migration
12
+ template 'create_access_token.rb',
13
+ "db/migrate/#{Time.now.strftime("%Y%m%d%H%M%S")}_create_access_token.rb"
14
+ end
15
+
16
+ def copy_access_token_model
17
+ template 'access_token.rb.erb', 'app/models/access_token.rb'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module Knocknock
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 Knocknock tokens controller for the given resource
8
+ and add the corresponding routes.
9
+ DESC
10
+
11
+ def copy_controller_file
12
+ template 'resource_tokens_controller.rb.erb', "app/controllers/#{name.underscore}_tokens_controller.rb"
13
+ end
14
+
15
+ def add_route
16
+ route "resource :#{name.underscore}_tokens, only: [:create, :destroy]"
17
+ end
18
+
19
+ private
20
+
21
+ def resource_name
22
+ name
23
+ end
24
+ end
25
+ end