knocknock 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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