api_guard 0.2.2 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +99 -17
  3. data/Rakefile +2 -5
  4. data/app/controllers/api_guard/application_controller.rb +2 -0
  5. data/app/controllers/api_guard/authentication_controller.rb +6 -4
  6. data/app/controllers/api_guard/passwords_controller.rb +4 -2
  7. data/app/controllers/api_guard/registration_controller.rb +4 -2
  8. data/app/controllers/api_guard/tokens_controller.rb +5 -3
  9. data/config/locales/en.yml +22 -0
  10. data/config/routes.rb +2 -0
  11. data/lib/api_guard.rb +9 -6
  12. data/lib/api_guard/app_secret_key.rb +2 -0
  13. data/lib/api_guard/engine.rb +3 -1
  14. data/lib/api_guard/jwt_auth/authentication.rb +49 -17
  15. data/lib/api_guard/jwt_auth/blacklist_token.rb +7 -3
  16. data/lib/api_guard/jwt_auth/json_web_token.rb +11 -5
  17. data/lib/api_guard/jwt_auth/refresh_jwt_token.rb +4 -0
  18. data/lib/api_guard/models/concerns.rb +8 -6
  19. data/lib/api_guard/modules.rb +13 -11
  20. data/lib/api_guard/resource_mapper.rb +3 -1
  21. data/lib/api_guard/response_formatters/renderer.rb +5 -2
  22. data/lib/api_guard/route_mapper.rb +58 -54
  23. data/lib/api_guard/test/controller_helper.rb +2 -0
  24. data/lib/api_guard/version.rb +3 -1
  25. data/lib/generators/api_guard/controllers/controllers_generator.rb +9 -7
  26. data/lib/generators/api_guard/controllers/templates/authentication_controller.rb +4 -4
  27. data/lib/generators/api_guard/controllers/templates/passwords_controller.rb +3 -3
  28. data/lib/generators/api_guard/controllers/templates/registration_controller.rb +3 -3
  29. data/lib/generators/api_guard/controllers/templates/tokens_controller.rb +7 -4
  30. data/lib/generators/api_guard/initializer/initializer_generator.rb +3 -1
  31. data/lib/generators/api_guard/initializer/templates/initializer.rb +6 -4
  32. metadata +53 -69
  33. data/app/models/api_guard/application_record.rb +0 -5
  34. data/app/views/layouts/api_guard/application.html.erb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6b7ce3ac5306ae3a652084d29da83f37ec9b41c5
4
- data.tar.gz: 7fb6b919ed4044e459a3840e442505326bfda971
2
+ SHA256:
3
+ metadata.gz: bc003426a48f2bf1a83d3b1653438efa43522f465a4cf98b4ac0b030f691cbfd
4
+ data.tar.gz: 771a016a40a938684ea61accde401e7eed3dc8c201688f51bb0444cd241901d7
5
5
  SHA512:
6
- metadata.gz: e02605d2b473c9931a1c494203562ae9e87ef90d5b3adfa4e043aba6b96e3da7aa3fc306189139a4d546b7b88eb4ecbdb876783dab28b9e6879b37dcca2f1c97
7
- data.tar.gz: 293b709e2ae69d3b07912c319d5fd37c8ae3156797cf09b19aa55ab8bc317e90822747a3b3d0a856c368d0e16d8a89cdb7b6ca208d047e9dc5142db81db385d8
6
+ metadata.gz: 16a0a967961773fcfdab3744fcfd228198484eb550b7b6abeb19e4daa1d2a42d7ecf2730709c4ab7f518bdb0fe1781973255b24da60c67a88b71198cb76913d1
7
+ data.tar.gz: e609d92695acc9d424739450b87c6a7cd4725d200e3b3bdf745e465e2c090fe9af7bb15820bb02d9964cc0af420f7faa34fede752484b41dcfe9aaf7bc53114c
data/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # API Guard
2
2
 
3
3
  [![Version](https://img.shields.io/gem/v/api_guard.svg?color=green)](https://rubygems.org/gems/api_guard)
4
- [![Build Status](https://travis-ci.org/Gokul595/api_guard.svg?branch=master)](https://travis-ci.org/Gokul595/api_guard)
4
+ [![Build Status](https://github.com/Gokul595/api_guard/workflows/build-master/badge.svg?branch=master)](https://github.com/Gokul595/api_guard/actions?query=workflow%3Abuild-master)
5
5
  [![Maintainability](https://api.codeclimate.com/v1/badges/ced3e74a26a66ed915cb/maintainability)](https://codeclimate.com/github/Gokul595/api_guard/maintainability)
6
- [![Test Coverage](https://api.codeclimate.com/v1/badges/ced3e74a26a66ed915cb/test_coverage)](https://codeclimate.com/github/Gokul595/api_guard/test_coverage)
7
6
 
8
7
 
9
8
  [JSON Web Token (JWT)](https://jwt.io/) based authentication solution with token refreshing & blacklisting for APIs
@@ -35,7 +34,12 @@ for cryptographic signing.
35
34
  * [Overriding defaults](#overriding-defaults)
36
35
  * [Controllers](#controllers)
37
36
  * [Routes](#routes)
37
+ * [Adding custom data in JWT token payload](#adding-custom-data-in-jwt-token-payload)
38
+ * [Override finding resource](#override-finding-resource)
39
+ * [Customizing / translating response messages using I18n](#customizing--translating-response-messages-using-i18n)
38
40
  * [Testing](#testing)
41
+ * [Wiki](https://github.com/Gokul595/api_guard/wiki)
42
+ * [Using API Guard with Devise](https://github.com/Gokul595/api_guard/wiki/Using-API-Guard-with-Devise)
39
43
  * [Contributing](#contributing)
40
44
  * [License](#license)
41
45
 
@@ -63,20 +67,22 @@ Below steps are provided assuming the model in `User`.
63
67
 
64
68
  ### Creating User model
65
69
 
66
- Create a model for User with below command
70
+ Create a model for User with below command.
67
71
 
68
72
  ```bash
69
73
  $ rails generate model user name:string email:string:uniq password_digest:string
70
74
  ```
71
75
 
72
- Then, run migration to create the `users` table
76
+ Then, run migration to create the `users` table.
73
77
 
74
78
  ```bash
75
79
  $ rails db:migrate
76
80
  ```
77
81
 
78
82
  Add [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password)
79
- in `User` model for password authentication
83
+ in `User` model for password authentication.
84
+
85
+ > Refer [this Wiki](https://github.com/Gokul595/api_guard/wiki/Using-API-Guard-with-Devise#authentication) for configuring API Guard authentication to work with Devise instead of using `has_secure_password`.
80
86
 
81
87
  ```ruby
82
88
  class User < ApplicationRecord
@@ -86,7 +92,7 @@ end
86
92
 
87
93
  Then, add `bcrypt` gem in your Gemfile which is used by
88
94
  [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password)
89
- for encrypting password and authentication
95
+ for encrypting password and authentication.
90
96
 
91
97
  ```ruby
92
98
  gem 'bcrypt', '~> 3.1.7'
@@ -108,6 +114,8 @@ api_guard_routes for: 'users'
108
114
 
109
115
  This will generate default routes such as sign up, sign in, sign out, token refresh, password change for User.
110
116
 
117
+ > Refer [this Wiki](https://github.com/Gokul595/api_guard/wiki/Using-API-Guard-with-Devise#routes) for configuring API Guard routes to work with Devise.
118
+
111
119
  ### Registration
112
120
 
113
121
  This will create an user and responds with access token, refresh token and access token expiry in the response header.
@@ -194,6 +202,8 @@ To authenticate the API request just add this before_action in the controller:
194
202
  before_action :authenticate_and_set_user
195
203
  ```
196
204
 
205
+ >**Note:** It is possible to authenticate with more than one resource, e.g. `authenticate_and_set_user_or_admin` will permit tokens issued for users or admins.
206
+
197
207
  Send the access token got in sign in API in the Authorization header in the API request as below.
198
208
  Also, make sure you add "Bearer" before the access token in the header value.
199
209
 
@@ -353,19 +363,19 @@ configurations.
353
363
  ApiGuard.setup do |config|
354
364
  # Validity of the JWT access token
355
365
  # Default: 1 day
356
- config.token_validity = 1.day
366
+ # config.token_validity = 1.day
357
367
 
358
368
  # Secret key for signing (encoding & decoding) the JWT access token
359
369
  # Default: 'secret_key_base' from Rails secrets
360
- config.token_signing_secret = Rails.application.secrets.secret_key_base
370
+ # config.token_signing_secret = 'my_signing_secret'
361
371
 
362
372
  # Invalidate old tokens on changing the password
363
373
  # Default: false
364
- config.invalidate_old_tokens_on_password_change = false
374
+ # config.invalidate_old_tokens_on_password_change = false
365
375
 
366
376
  # Blacklist JWT access token after refreshing
367
377
  # Default: false
368
- config.blacklist_token_after_refreshing = false
378
+ # config.blacklist_token_after_refreshing = false
369
379
  end
370
380
  ```
371
381
 
@@ -389,6 +399,10 @@ Override this by configuring `token_signing_secret`
389
399
  config.token_signing_secret = 'my_signing_secret'
390
400
  ```
391
401
 
402
+ >**Note:** Avoid committing this token signing secret in your version control (GIT) and always keep this secure. As,
403
+ >exposing this allow anyone to generate JWT access token and give full access to APIs. Better way is storing this value
404
+ >in environment variable or in encrypted secrets (Rails 5.2+)
405
+
392
406
  ### Invalidate tokens on password change
393
407
 
394
408
  By default, API Guard will not invalidate old JWT access tokens on changing password. If you need, you can enable it by
@@ -439,11 +453,11 @@ api_guard_associations refresh_token: 'refresh_tokens', blacklisted_token: 'blac
439
453
 
440
454
  ### Token blacklisting
441
455
 
442
- To include token blacklisting in your application you need to create a table to store the refresh tokens. This will be
456
+ To include token blacklisting in your application you need to create a table to store the blacklisted tokens. This will be
443
457
  used to blacklist a JWT access token from future use. The access token will be blacklisted on successful sign out of the
444
458
  resource.
445
459
 
446
- Use below command to create a model `RefeshToken` with columns to store the token and the user reference
460
+ Use below command to create a model `BlacklistedToken` with columns to store the token and the user reference
447
461
 
448
462
  ```bash
449
463
  $ rails generate model blacklisted_token token:string user:references expire_at:datetime
@@ -539,28 +553,96 @@ api_guard_routes for: 'users', only: [:authentication]
539
553
 
540
554
  >**Available controllers:** registration, authentication, tokens, passwords
541
555
 
542
- **Customizing the routes**
556
+ **Customizing the route path:**
543
557
 
544
- To customize the default routes generated by API Guard you can use `api_guard_scope` in the routes.
558
+ You can customize the path of the default routes of the API Guard using the `api_guard_scope` as below,
545
559
 
546
560
  ```ruby
547
561
  api_guard_routes for: 'users', except: [:registration]
548
562
 
549
563
  api_guard_scope 'users' do
550
- post 'account/create' => 'users/registration#create'
551
- delete 'account/delete' => 'users/registration#destroy'
564
+ post 'account/create' => 'api_guard/registration#create'
565
+ delete 'account/delete' => 'api_guard/registration#destroy'
552
566
  end
553
567
  ```
554
568
 
555
569
  Above configuration will replace default registration routes `users/sign_up` & `users/delete` with `account/create` &
556
570
  `account/delete`
557
571
 
572
+ ### Adding custom data in JWT token payload
573
+
574
+ You can add custom data in the JWT token payload in the format of Hash and use the data after decoding the token on
575
+ every request.
576
+
577
+ To add custom data, you need to create an instance method `jwt_token_payload` in the resource model as below which
578
+ should return a Hash,
579
+
580
+ ```ruby
581
+ class User < ApplicationRecord
582
+ def jwt_token_payload
583
+ { custom_key: 'value' }
584
+ end
585
+ end
586
+ ```
587
+
588
+ API Guard will add the hash returned by this method to the JWT token payload in addition to the default payload values.
589
+ This data (including default payload values) will be available in the instance variable `@decoded_token` on each request
590
+ if the token has been successfully decoded. You can access the values as below,
591
+
592
+ ```ruby
593
+ @decoded_token[:custom_key]
594
+ ```
595
+
596
+ ### Override finding resource
597
+
598
+ By default, API Guard will try to find the resource by it's `id`. If you wish to override this default behavior, you can
599
+ do it by creating a method `find_resource_from_token` in the specific controller or in `ApplicationController` as you
600
+ need.
601
+
602
+ **Adding custom logic in addition to the default logic:**
603
+ ```ruby
604
+ def find_resource_from_token(resource_class)
605
+ user = super # This will call the actual method defined in API Guard
606
+ user if user&.active?
607
+ end
608
+ ```
609
+
610
+ **Using custom query to find the user from the token:**
611
+ ```ruby
612
+ def find_resource_from_token(resource_class)
613
+ resource_id = @decoded_token[:"#{@resource_name}_id"]
614
+ resource_class.find_by(id: resource_id, status: 'active') if resource_id
615
+ end
616
+ ```
617
+
618
+ This method has an argument `resource_class` which is the class (model) of the current resource (`User`).
619
+ This method should return a resource object to successfully authenticate the request or `nil` to respond with 401.
620
+
621
+ You can also use the [custom data](#adding-custom-data-in-jwt-token-payload) added in the JWT token payload using
622
+ `@decoded_token` instance variable and customize the logic as you need.
623
+
624
+ ### Customizing / translating response messages using I18n
625
+
626
+ API Guard uses [I18n](https://guides.rubyonrails.org/i18n.html) for success and error messages. You can create your own
627
+ locale file and customize the messages for any language.
628
+
629
+ ```yaml
630
+ en:
631
+ api_guard:
632
+ authentication:
633
+ signed_in: 'Signed in successfully'
634
+ signed_out: 'Signed out successfully'
635
+ ```
636
+
637
+ You can find the complete list of available keys in this file:
638
+ https://github.com/Gokul595/api_guard/blob/master/config/locales/en.yml
639
+
558
640
  ## Testing
559
641
 
560
642
  API Guard comes with helper for creating JWT access token and refresh token for the resource which you can use it for
561
643
  testing the controllers of your application.
562
644
 
563
- For using it include the helper in you test framework
645
+ For using it, just include the helper in your test framework.
564
646
 
565
647
  **RSpec**
566
648
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -14,11 +16,6 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
16
  rdoc.rdoc_files.include('lib/**/*.rb')
15
17
  end
16
18
 
17
-
18
-
19
19
  load 'rails/tasks/statistics.rake'
20
20
 
21
-
22
-
23
21
  require 'bundler/gem_tasks'
24
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApiGuard
2
4
  class ApplicationController < ActionController::Base
3
5
  skip_before_action :verify_authenticity_token, raise: false
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_dependency 'api_guard/application_controller'
2
4
 
3
5
  module ApiGuard
@@ -8,22 +10,22 @@ module ApiGuard
8
10
  def create
9
11
  if resource.authenticate(params[:password])
10
12
  create_token_and_set_header(resource, resource_name)
11
- render_success(message: 'Signed in successfully')
13
+ render_success(message: I18n.t('api_guard.authentication.signed_in'))
12
14
  else
13
- render_error(422, message: 'Invalid login credentials')
15
+ render_error(422, message: I18n.t('api_guard.authentication.invalid_login_credentials'))
14
16
  end
15
17
  end
16
18
 
17
19
  def destroy
18
20
  blacklist_token
19
- render_success(message: 'Signed out successfully')
21
+ render_success(message: I18n.t('api_guard.authentication.signed_out'))
20
22
  end
21
23
 
22
24
  private
23
25
 
24
26
  def find_resource
25
27
  self.resource = resource_class.find_by(email: params[:email].downcase.strip) if params[:email].present?
26
- render_error(422, message: 'Invalid login credentials') unless resource
28
+ render_error(422, message: I18n.t('api_guard.authentication.invalid_login_credentials')) unless resource
27
29
  end
28
30
  end
29
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_dependency 'api_guard/application_controller'
2
4
 
3
5
  module ApiGuard
@@ -7,12 +9,12 @@ module ApiGuard
7
9
  def update
8
10
  invalidate_old_jwt_tokens(current_resource)
9
11
 
10
- if current_resource.update_attributes(password_params)
12
+ if current_resource.update(password_params)
11
13
  blacklist_token unless ApiGuard.invalidate_old_tokens_on_password_change
12
14
  destroy_all_refresh_tokens(current_resource)
13
15
 
14
16
  create_token_and_set_header(current_resource, resource_name)
15
- render_success(message: 'Password changed successfully')
17
+ render_success(message: I18n.t('api_guard.password.changed'))
16
18
  else
17
19
  render_error(422, object: current_resource)
18
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_dependency 'api_guard/application_controller'
2
4
 
3
5
  module ApiGuard
@@ -8,7 +10,7 @@ module ApiGuard
8
10
  init_resource(sign_up_params)
9
11
  if resource.save
10
12
  create_token_and_set_header(resource, resource_name)
11
- render_success(message: 'Signed up successfully')
13
+ render_success(message: I18n.t('api_guard.registration.signed_up'))
12
14
  else
13
15
  render_error(422, object: resource)
14
16
  end
@@ -16,7 +18,7 @@ module ApiGuard
16
18
 
17
19
  def destroy
18
20
  current_resource.destroy
19
- render_success(message: "Account deleted successfully")
21
+ render_success(message: I18n.t('api_guard.registration.account_deleted'))
20
22
  end
21
23
 
22
24
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_dependency 'api_guard/application_controller'
2
4
 
3
5
  module ApiGuard
@@ -11,7 +13,7 @@ module ApiGuard
11
13
  @refresh_token.destroy
12
14
  blacklist_token if ApiGuard.blacklist_token_after_refreshing
13
15
 
14
- render_success(message: 'Token refreshed successfully')
16
+ render_success(message: I18n.t('api_guard.access_token.refreshed'))
15
17
  end
16
18
 
17
19
  private
@@ -21,9 +23,9 @@ module ApiGuard
21
23
 
22
24
  if refresh_token_from_header
23
25
  @refresh_token = find_refresh_token_of(current_resource, refresh_token_from_header)
24
- return render_error(401, message: 'Invalid refresh token') unless @refresh_token
26
+ return render_error(401, message: I18n.t('api_guard.refresh_token.invalid')) unless @refresh_token
25
27
  else
26
- render_error(401, message: 'Refresh token is missing in the request')
28
+ render_error(401, message: I18n.t('api_guard.refresh_token.missing'))
27
29
  end
28
30
  end
29
31
  end
@@ -0,0 +1,22 @@
1
+ en:
2
+ api_guard:
3
+ authentication:
4
+ signed_in: 'Signed in successfully'
5
+ signed_out: 'Signed out successfully'
6
+ invalid_login_credentials: 'Invalid login credentials'
7
+ password:
8
+ changed: 'Password changed successfully'
9
+ registration:
10
+ signed_up: 'Signed up successfully'
11
+ account_deleted: 'Account deleted successfully'
12
+ access_token:
13
+ refreshed: 'Token refreshed successfully'
14
+ missing: 'Access token is missing in the request'
15
+ invalid: 'Invalid access token'
16
+ expired: 'Access token expired'
17
+ refresh_token:
18
+ invalid: 'Invalid refresh token'
19
+ missing: 'Refresh token is missing in the request'
20
+ response:
21
+ success: 'success'
22
+ error: 'error'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ApiGuard::Engine.routes.draw do
2
4
  # Can't see anything here?
3
5
  # Goto 'lib/api_guard/route_mapper.rb'
@@ -1,6 +1,8 @@
1
- require "api_guard/engine"
2
- require "api_guard/route_mapper"
3
- require "api_guard/modules"
1
+ # frozen_string_literal: true
2
+
3
+ require 'api_guard/engine'
4
+ require 'api_guard/route_mapper'
5
+ require 'api_guard/modules'
4
6
 
5
7
  module ApiGuard
6
8
  autoload :AppSecretKey, 'api_guard/app_secret_key'
@@ -24,14 +26,15 @@ module ApiGuard
24
26
  mattr_accessor :api_guard_associations
25
27
  self.api_guard_associations = {}
26
28
 
27
- mattr_reader :mapped_resource
28
- @@mapped_resource = {}
29
+ mattr_reader :mapped_resource do
30
+ {}
31
+ end
29
32
 
30
33
  def self.setup
31
34
  yield self
32
35
  end
33
36
 
34
37
  def self.map_resource(routes_for, class_name)
35
- @@mapped_resource[routes_for.to_sym] = ApiGuard::ResourceMapper.new(routes_for, class_name)
38
+ mapped_resource[routes_for.to_sym] = ApiGuard::ResourceMapper.new(routes_for, class_name)
36
39
  end
37
40
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApiGuard
2
4
  class AppSecretKey
3
5
  def initialize(application)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApiGuard
2
4
  class Engine < ::Rails::Engine
3
5
  isolate_namespace ApiGuard
4
6
 
5
7
  config.generators do |g|
6
8
  g.test_framework :rspec
7
- g.fixture_replacement :factory_girl, :dir => 'spec/factories'
9
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
8
10
  end
9
11
 
10
12
  # Use 'secret_key_base' from Rails secrets if 'token_signing_secret' is not configured