api_guard 0.2.0 → 0.5.0

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 -14
  3. data/Rakefile +2 -5
  4. data/app/controllers/api_guard/application_controller.rb +4 -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: d220d5bf204e5a090dc71fc977101de4f3d431f4
4
- data.tar.gz: bac634e2197010b0de248d247382d38679de8f16
2
+ SHA256:
3
+ metadata.gz: 16c29cefc6fd22302a55e87e7ec5f52d6fc4936640ee190cad692646cbe37cc7
4
+ data.tar.gz: daa21cfa0052dc034fc5e5c18c380a9ee3eac72e3d1490db895798dc2f31c9f7
5
5
  SHA512:
6
- metadata.gz: 5a45df067c41509e759f0cd0ca200c6948192cf0f7a69fe7872ba6636c0cfd48e37ce0fa023579a4a2512fa36f6377b196cc4a5f854d3feb5392271bc46e6089
7
- data.tar.gz: 81ad551d87c7fd35a3ab80c3b8f20f9f6b7e51056a865a436767703c93f4767361349a8cabe5c2c9a6820cdcab9dc4a3d0b73dda47186c76984d80284ef91134
6
+ metadata.gz: 6daaca97ec4dd5d3c4f9938642edc77b2fc25a8854985d6b305e4ce1e83b20dd52b2056fed4e0e9d8b6e14f9f0049391afe42c40f455983d9386556fc1bca37c
7
+ data.tar.gz: a3feb57d806c7581a6679833a2cb0f41328e8b9a9077acd5504898173527b7ae30778d846efbbac4106d52abea65c35300e6714f2aa0cc2cb3016e1dd2b1eb2f
data/README.md CHANGED
@@ -1,7 +1,7 @@
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
6
  [![Test Coverage](https://api.codeclimate.com/v1/badges/ced3e74a26a66ed915cb/test_coverage)](https://codeclimate.com/github/Gokul595/api_guard/test_coverage)
7
7
 
@@ -35,7 +35,12 @@ for cryptographic signing.
35
35
  * [Overriding defaults](#overriding-defaults)
36
36
  * [Controllers](#controllers)
37
37
  * [Routes](#routes)
38
+ * [Adding custom data in JWT token payload](#adding-custom-data-in-jwt-token-payload)
39
+ * [Override finding resource](#override-finding-resource)
40
+ * [Customizing / translating response messages using I18n](#customizing--translating-response-messages-using-i18n)
38
41
  * [Testing](#testing)
42
+ * [Wiki](https://github.com/Gokul595/api_guard/wiki)
43
+ * [Using API Guard with Devise](https://github.com/Gokul595/api_guard/wiki/Using-API-Guard-with-Devise)
39
44
  * [Contributing](#contributing)
40
45
  * [License](#license)
41
46
 
@@ -63,20 +68,22 @@ Below steps are provided assuming the model in `User`.
63
68
 
64
69
  ### Creating User model
65
70
 
66
- Create a model for User with below command
71
+ Create a model for User with below command.
67
72
 
68
73
  ```bash
69
74
  $ rails generate model user name:string email:string:uniq password_digest:string
70
75
  ```
71
76
 
72
- Then, run migration to create the `users` table
77
+ Then, run migration to create the `users` table.
73
78
 
74
79
  ```bash
75
80
  $ rails db:migrate
76
81
  ```
77
82
 
78
83
  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
84
+ in `User` model for password authentication.
85
+
86
+ > 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
87
 
81
88
  ```ruby
82
89
  class User < ApplicationRecord
@@ -86,7 +93,7 @@ end
86
93
 
87
94
  Then, add `bcrypt` gem in your Gemfile which is used by
88
95
  [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password)
89
- for encrypting password and authentication
96
+ for encrypting password and authentication.
90
97
 
91
98
  ```ruby
92
99
  gem 'bcrypt', '~> 3.1.7'
@@ -108,6 +115,8 @@ api_guard_routes for: 'users'
108
115
 
109
116
  This will generate default routes such as sign up, sign in, sign out, token refresh, password change for User.
110
117
 
118
+ > 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.
119
+
111
120
  ### Registration
112
121
 
113
122
  This will create an user and responds with access token, refresh token and access token expiry in the response header.
@@ -353,19 +362,19 @@ configurations.
353
362
  ApiGuard.setup do |config|
354
363
  # Validity of the JWT access token
355
364
  # Default: 1 day
356
- config.token_validity = 1.day
365
+ # config.token_validity = 1.day
357
366
 
358
367
  # Secret key for signing (encoding & decoding) the JWT access token
359
368
  # Default: 'secret_key_base' from Rails secrets
360
- config.token_signing_secret = Rails.application.secrets.secret_key_base
369
+ # config.token_signing_secret = 'my_signing_secret'
361
370
 
362
371
  # Invalidate old tokens on changing the password
363
372
  # Default: false
364
- config.invalidate_old_tokens_on_password_change = false
373
+ # config.invalidate_old_tokens_on_password_change = false
365
374
 
366
375
  # Blacklist JWT access token after refreshing
367
376
  # Default: false
368
- config.blacklist_token_after_refreshing = false
377
+ # config.blacklist_token_after_refreshing = false
369
378
  end
370
379
  ```
371
380
 
@@ -389,6 +398,10 @@ Override this by configuring `token_signing_secret`
389
398
  config.token_signing_secret = 'my_signing_secret'
390
399
  ```
391
400
 
401
+ >**Note:** Avoid committing this token signing secret in your version control (GIT) and always keep this secure. As,
402
+ >exposing this allow anyone to generate JWT access token and give full access to APIs. Better way is storing this value
403
+ >in environment variable or in encrypted secrets (Rails 5.2+)
404
+
392
405
  ### Invalidate tokens on password change
393
406
 
394
407
  By default, API Guard will not invalidate old JWT access tokens on changing password. If you need, you can enable it by
@@ -539,28 +552,96 @@ api_guard_routes for: 'users', only: [:authentication]
539
552
 
540
553
  >**Available controllers:** registration, authentication, tokens, passwords
541
554
 
542
- **Customizing the routes**
555
+ **Customizing the route path:**
543
556
 
544
- To customize the default routes generated by API Guard you can use `api_guard_scope` in the routes.
557
+ You can customize the path of the default routes of the API Guard using the `api_guard_scope` as below,
545
558
 
546
559
  ```ruby
547
560
  api_guard_routes for: 'users', except: [:registration]
548
561
 
549
562
  api_guard_scope 'users' do
550
- post 'account/create' => 'users/registration#create'
551
- delete 'account/delete' => 'users/registration#destroy'
563
+ post 'account/create' => 'api_guard/registration#create'
564
+ delete 'account/delete' => 'api_guard/registration#destroy'
552
565
  end
553
566
  ```
554
567
 
555
568
  Above configuration will replace default registration routes `users/sign_up` & `users/delete` with `account/create` &
556
569
  `account/delete`
557
570
 
571
+ ### Adding custom data in JWT token payload
572
+
573
+ You can add custom data in the JWT token payload in the format of Hash and use the data after decoding the token on
574
+ every request.
575
+
576
+ To add custom data, you need to create an instance method `jwt_token_payload` in the resource model as below which
577
+ should return a Hash,
578
+
579
+ ```ruby
580
+ class User < ApplicationRecord
581
+ def jwt_token_payload
582
+ { custom_key: 'value' }
583
+ end
584
+ end
585
+ ```
586
+
587
+ API Guard will add the hash returned by this method to the JWT token payload in addition to the default payload values.
588
+ This data (including default payload values) will be available in the instance variable `@decoded_token` on each request
589
+ if the token has been successfully decoded. You can access the values as below,
590
+
591
+ ```ruby
592
+ @decoded_token[:custom_key]
593
+ ```
594
+
595
+ ### Override finding resource
596
+
597
+ By default, API Guard will try to find the resource by it's `id`. If you wish to override this default behavior, you can
598
+ do it by creating a method `find_resource_from_token` in the specific controller or in `ApplicationController` as you
599
+ need.
600
+
601
+ **Adding custom logic in addition to the default logic:**
602
+ ```ruby
603
+ def find_resource_from_token(resource_class)
604
+ user = super # This will call the actual method defined in API Guard
605
+ user if user&.active?
606
+ end
607
+ ```
608
+
609
+ **Using custom query to find the user from the token:**
610
+ ```ruby
611
+ def find_resource_from_token(resource_class)
612
+ resource_id = @decoded_token[:"#{@resource_name}_id"]
613
+ resource_class.find_by(id: resource_id, status: 'active') if resource_id
614
+ end
615
+ ```
616
+
617
+ This method has an argument `resource_class` which is the class (model) of the current resource (`User`).
618
+ This method should return a resource object to successfully authenticate the request or `nil` to respond with 401.
619
+
620
+ You can also use the [custom data](#adding-custom-data-in-jwt-token-payload) added in the JWT token payload using
621
+ `@decoded_token` instance variable and customize the logic as you need.
622
+
623
+ ### Customizing / translating response messages using I18n
624
+
625
+ API Guard uses [I18n](https://guides.rubyonrails.org/i18n.html) for success and error messages. You can create your own
626
+ locale file and customize the messages for any language.
627
+
628
+ ```yaml
629
+ en:
630
+ api_guard:
631
+ authentication:
632
+ signed_in: 'Signed in successfully'
633
+ signed_out: 'Signed out successfully'
634
+ ```
635
+
636
+ You can find the complete list of available keys in this file:
637
+ https://github.com/Gokul595/api_guard/blob/master/config/locales/en.yml
638
+
558
639
  ## Testing
559
640
 
560
641
  API Guard comes with helper for creating JWT access token and refresh token for the resource which you can use it for
561
642
  testing the controllers of your application.
562
643
 
563
- For using it include the helper in you test framework
644
+ For using it, just include the helper in your test framework.
564
645
 
565
646
  **RSpec**
566
647
 
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,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApiGuard
2
4
  class ApplicationController < ActionController::Base
5
+ skip_before_action :verify_authenticity_token, raise: false
6
+
3
7
  def authenticate_resource
4
8
  public_send("authenticate_and_set_#{resource_name}")
5
9
  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,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