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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +206 -0
- data/Rakefile +37 -0
- data/app/controllers/knocknock/auth_tokens_controller.rb +54 -0
- data/app/model/knocknock/auth_token.rb +69 -0
- data/config/routes.rb +2 -0
- data/lib/generators/knocknock/install_generator.rb +20 -0
- data/lib/generators/knocknock/token_controller_generator.rb +25 -0
- data/lib/generators/templates/access_token.rb.erb +17 -0
- data/lib/generators/templates/create_access_token.rb +9 -0
- data/lib/generators/templates/knocknock.rb +50 -0
- data/lib/generators/templates/resource_tokens_controller.rb.erb +2 -0
- data/lib/knocknock.rb +25 -0
- data/lib/knocknock/authenticatable.rb +47 -0
- data/lib/knocknock/engine.rb +6 -0
- data/lib/knocknock/version.rb +3 -0
- data/lib/tasks/knocknock_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/controllers/admin_protected_controller.rb +7 -0
- data/test/dummy/app/controllers/admin_tokens_controller.rb +2 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/user_protected_controller.rb +7 -0
- data/test/dummy/app/controllers/user_tokens_controller.rb +2 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/access_token.rb +3 -0
- data/test/dummy/app/models/admin.rb +5 -0
- data/test/dummy/app/models/user.rb +5 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +56 -0
- data/test/dummy/config/environments/production.rb +82 -0
- data/test/dummy/config/environments/test.rb +44 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +6 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20150713101607_create_users.rb +10 -0
- data/test/dummy/db/migrate/20150922015152_create_admins.rb +10 -0
- data/test/dummy/db/migrate/20160218200351_create_access_tokens.rb +11 -0
- data/test/dummy/db/schema.rb +40 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +52 -0
- data/test/dummy/log/test.log +9320 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/controllers/admin_protected_controller_test.rb +49 -0
- data/test/dummy/test/controllers/admin_tokens_controller_test.rb +22 -0
- data/test/dummy/test/controllers/user_protected_controller_test.rb +49 -0
- data/test/dummy/test/controllers/user_tokens_controller_test.rb +23 -0
- data/test/dummy/test/fixtures/access_tokens.yml +11 -0
- data/test/dummy/test/models/access_token_test.rb +7 -0
- data/test/dummy/test/models/admin_test.rb +4 -0
- data/test/dummy/test/models/user_test.rb +4 -0
- data/test/fixtures/admins.yml +5 -0
- data/test/fixtures/users.yml +9 -0
- data/test/generators/install_generator_test.rb +15 -0
- data/test/generators/token_controller_generator_test.rb +19 -0
- data/test/knocknock_test.rb +9 -0
- data/test/model/knocknock/auth_token_test.rb +50 -0
- data/test/support/generators_test_helper.rb +9 -0
- data/test/test_helper.rb +38 -0
- data/test/tmp/app/controllers/admin_tokens_controller.rb +2 -0
- data/test/tmp/app/controllers/user_tokens_controller.rb +2 -0
- data/test/tmp/config/routes.rb +8 -0
- metadata +253 -0
checksums.yaml
ADDED
@@ -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
|
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# knocknock
|
2
|
+
[](http://badge.fury.io/rb/knock)
|
3
|
+
[](https://travis-ci.org/nsarno/knock)
|
4
|
+
[](https://codeclimate.com/github/nsarno/knock/coverage)
|
5
|
+
[](https://codeclimate.com/github/nsarno/knock)
|
6
|
+
[](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
|
+
[](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
|
data/Rakefile
ADDED
@@ -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
|
data/config/routes.rb
ADDED
@@ -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
|