api_guard 0.2.1 → 0.5.1

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 +95 -15
  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 +11 -6
  12. data/lib/api_guard/app_secret_key.rb +22 -0
  13. data/lib/api_guard/engine.rb +4 -5
  14. data/lib/api_guard/jwt_auth/authentication.rb +34 -12
  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 +54 -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: 4bbc1b32deafff68b420cf4b56f343d67a90caa7
4
- data.tar.gz: 5b7f2aa8e25c5fd1fdbf5c4d21d99fb3461ecf33
2
+ SHA256:
3
+ metadata.gz: db273f8953347d07ed9d5acb5923480e463461517e2e1396d37a9db704ffc218
4
+ data.tar.gz: 92b5aeffc41c1d97d3d1ade1389b4acd902ff66166ecb3895fd858b0f8f51a01
5
5
  SHA512:
6
- metadata.gz: cc4e72c586bae02e4f5bb9671e070625f58c88152924ff1448cd2b7d0ce805aa6f47db48ee8359b6411321e1333ddf3ae121ce2ba85a5a1d20471101cf7d16a2
7
- data.tar.gz: 8284feb1676f32b989735c9097f5f1c2e329f6e150ab2ebed2fba4b622a7c1eb7b9ba7186f01f049738a8d5a4920c857e23de9a96b0eef09c8b3be715b29b3ea
6
+ metadata.gz: d99aa923a6b41bdc4b1c5524cc9159342fee8e1c31931d0387c21651d7b338ccef6be7773cdd48d8773f1b50000e228310bf7a18d9c3cefaeb52f63f2b82c824
7
+ data.tar.gz: 3651814062e401e8c8be16d9c103a23de042232140bccb10a79977dc3ffdb7370b658ddfeed26cc3e995705e3d7e60c90d97f5b65469025eed717260e6097a31
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/badge.svg?branch=master)](https://github.com/Gokul595/api_guard/actions?query=workflow%3Abuild)
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.
@@ -353,19 +361,19 @@ configurations.
353
361
  ApiGuard.setup do |config|
354
362
  # Validity of the JWT access token
355
363
  # Default: 1 day
356
- config.token_validity = 1.day
364
+ # config.token_validity = 1.day
357
365
 
358
366
  # Secret key for signing (encoding & decoding) the JWT access token
359
367
  # Default: 'secret_key_base' from Rails secrets
360
- config.token_signing_secret = Rails.application.secrets.secret_key_base
368
+ # config.token_signing_secret = 'my_signing_secret'
361
369
 
362
370
  # Invalidate old tokens on changing the password
363
371
  # Default: false
364
- config.invalidate_old_tokens_on_password_change = false
372
+ # config.invalidate_old_tokens_on_password_change = false
365
373
 
366
374
  # Blacklist JWT access token after refreshing
367
375
  # Default: false
368
- config.blacklist_token_after_refreshing = false
376
+ # config.blacklist_token_after_refreshing = false
369
377
  end
370
378
  ```
371
379
 
@@ -389,6 +397,10 @@ Override this by configuring `token_signing_secret`
389
397
  config.token_signing_secret = 'my_signing_secret'
390
398
  ```
391
399
 
400
+ >**Note:** Avoid committing this token signing secret in your version control (GIT) and always keep this secure. As,
401
+ >exposing this allow anyone to generate JWT access token and give full access to APIs. Better way is storing this value
402
+ >in environment variable or in encrypted secrets (Rails 5.2+)
403
+
392
404
  ### Invalidate tokens on password change
393
405
 
394
406
  By default, API Guard will not invalidate old JWT access tokens on changing password. If you need, you can enable it by
@@ -539,28 +551,96 @@ api_guard_routes for: 'users', only: [:authentication]
539
551
 
540
552
  >**Available controllers:** registration, authentication, tokens, passwords
541
553
 
542
- **Customizing the routes**
554
+ **Customizing the route path:**
543
555
 
544
- To customize the default routes generated by API Guard you can use `api_guard_scope` in the routes.
556
+ You can customize the path of the default routes of the API Guard using the `api_guard_scope` as below,
545
557
 
546
558
  ```ruby
547
559
  api_guard_routes for: 'users', except: [:registration]
548
560
 
549
561
  api_guard_scope 'users' do
550
- post 'account/create' => 'users/registration#create'
551
- delete 'account/delete' => 'users/registration#destroy'
562
+ post 'account/create' => 'api_guard/registration#create'
563
+ delete 'account/delete' => 'api_guard/registration#destroy'
552
564
  end
553
565
  ```
554
566
 
555
567
  Above configuration will replace default registration routes `users/sign_up` & `users/delete` with `account/create` &
556
568
  `account/delete`
557
569
 
570
+ ### Adding custom data in JWT token payload
571
+
572
+ You can add custom data in the JWT token payload in the format of Hash and use the data after decoding the token on
573
+ every request.
574
+
575
+ To add custom data, you need to create an instance method `jwt_token_payload` in the resource model as below which
576
+ should return a Hash,
577
+
578
+ ```ruby
579
+ class User < ApplicationRecord
580
+ def jwt_token_payload
581
+ { custom_key: 'value' }
582
+ end
583
+ end
584
+ ```
585
+
586
+ API Guard will add the hash returned by this method to the JWT token payload in addition to the default payload values.
587
+ This data (including default payload values) will be available in the instance variable `@decoded_token` on each request
588
+ if the token has been successfully decoded. You can access the values as below,
589
+
590
+ ```ruby
591
+ @decoded_token[:custom_key]
592
+ ```
593
+
594
+ ### Override finding resource
595
+
596
+ By default, API Guard will try to find the resource by it's `id`. If you wish to override this default behavior, you can
597
+ do it by creating a method `find_resource_from_token` in the specific controller or in `ApplicationController` as you
598
+ need.
599
+
600
+ **Adding custom logic in addition to the default logic:**
601
+ ```ruby
602
+ def find_resource_from_token(resource_class)
603
+ user = super # This will call the actual method defined in API Guard
604
+ user if user&.active?
605
+ end
606
+ ```
607
+
608
+ **Using custom query to find the user from the token:**
609
+ ```ruby
610
+ def find_resource_from_token(resource_class)
611
+ resource_id = @decoded_token[:"#{@resource_name}_id"]
612
+ resource_class.find_by(id: resource_id, status: 'active') if resource_id
613
+ end
614
+ ```
615
+
616
+ This method has an argument `resource_class` which is the class (model) of the current resource (`User`).
617
+ This method should return a resource object to successfully authenticate the request or `nil` to respond with 401.
618
+
619
+ You can also use the [custom data](#adding-custom-data-in-jwt-token-payload) added in the JWT token payload using
620
+ `@decoded_token` instance variable and customize the logic as you need.
621
+
622
+ ### Customizing / translating response messages using I18n
623
+
624
+ API Guard uses [I18n](https://guides.rubyonrails.org/i18n.html) for success and error messages. You can create your own
625
+ locale file and customize the messages for any language.
626
+
627
+ ```yaml
628
+ en:
629
+ api_guard:
630
+ authentication:
631
+ signed_in: 'Signed in successfully'
632
+ signed_out: 'Signed out successfully'
633
+ ```
634
+
635
+ You can find the complete list of available keys in this file:
636
+ https://github.com/Gokul595/api_guard/blob/master/config/locales/en.yml
637
+
558
638
  ## Testing
559
639
 
560
640
  API Guard comes with helper for creating JWT access token and refresh token for the resource which you can use it for
561
641
  testing the controllers of your application.
562
642
 
563
- For using it include the helper in you test framework
643
+ For using it, just include the helper in your test framework.
564
644
 
565
645
  **RSpec**
566
646
 
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,8 +1,12 @@
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
8
+ autoload :AppSecretKey, 'api_guard/app_secret_key'
9
+
6
10
  module Test
7
11
  autoload :ControllerHelper, 'api_guard/test/controller_helper'
8
12
  end
@@ -22,14 +26,15 @@ module ApiGuard
22
26
  mattr_accessor :api_guard_associations
23
27
  self.api_guard_associations = {}
24
28
 
25
- mattr_reader :mapped_resource
26
- @@mapped_resource = {}
29
+ mattr_reader :mapped_resource do
30
+ {}
31
+ end
27
32
 
28
33
  def self.setup
29
34
  yield self
30
35
  end
31
36
 
32
37
  def self.map_resource(routes_for, class_name)
33
- @@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)
34
39
  end
35
40
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ class AppSecretKey
5
+ def initialize(application)
6
+ @application = application
7
+ end
8
+
9
+ def detect
10
+ secret_key_base(:credentials) || secret_key_base(:secrets) ||
11
+ secret_key_base(:config) || secret_key_base
12
+ end
13
+
14
+ private
15
+
16
+ def secret_key_base(source = nil)
17
+ return @application.secret_key_base unless source
18
+
19
+ @application.send(source).secret_key_base.presence if @application.respond_to?(source)
20
+ end
21
+ end
22
+ end
@@ -1,18 +1,17 @@
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
11
13
  initializer 'ApiGuard.token_signing_secret' do |app|
12
- unless ApiGuard.token_signing_secret
13
- signing_secret = app.respond_to?(:credentials) ? app.credentials.secret_key_base : app.secrets.secret_key_base
14
- ApiGuard.token_signing_secret = signing_secret
15
- end
14
+ ApiGuard.token_signing_secret ||= ApiGuard::AppSecretKey.new(app).detect
16
15
  end
17
16
  end
18
17
  end