api_guard_grape 0.5.1

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +690 -0
  4. data/Rakefile +21 -0
  5. data/app/controllers/api_guard/application_controller.rb +11 -0
  6. data/app/controllers/api_guard/authentication_controller.rb +31 -0
  7. data/app/controllers/api_guard/passwords_controller.rb +29 -0
  8. data/app/controllers/api_guard/registration_controller.rb +30 -0
  9. data/app/controllers/api_guard/tokens_controller.rb +32 -0
  10. data/config/locales/en.yml +22 -0
  11. data/config/routes.rb +6 -0
  12. data/lib/api_guard.rb +40 -0
  13. data/lib/api_guard/app_secret_key.rb +22 -0
  14. data/lib/api_guard/engine.rb +17 -0
  15. data/lib/api_guard/jwt_auth/authentication.rb +98 -0
  16. data/lib/api_guard/jwt_auth/blacklist_token.rb +35 -0
  17. data/lib/api_guard/jwt_auth/json_web_token.rb +72 -0
  18. data/lib/api_guard/jwt_auth/refresh_jwt_token.rb +46 -0
  19. data/lib/api_guard/models/concerns.rb +27 -0
  20. data/lib/api_guard/modules.rb +26 -0
  21. data/lib/api_guard/resource_mapper.rb +43 -0
  22. data/lib/api_guard/response_formatters/renderer.rb +22 -0
  23. data/lib/api_guard/route_mapper.rb +85 -0
  24. data/lib/api_guard/test/controller_helper.rb +13 -0
  25. data/lib/api_guard/version.rb +5 -0
  26. data/lib/generators/api_guard/controllers/USAGE +11 -0
  27. data/lib/generators/api_guard/controllers/controllers_generator.rb +25 -0
  28. data/lib/generators/api_guard/controllers/templates/authentication_controller.rb +27 -0
  29. data/lib/generators/api_guard/controllers/templates/passwords_controller.rb +25 -0
  30. data/lib/generators/api_guard/controllers/templates/registration_controller.rb +26 -0
  31. data/lib/generators/api_guard/controllers/templates/tokens_controller.rb +28 -0
  32. data/lib/generators/api_guard/initializer/USAGE +8 -0
  33. data/lib/generators/api_guard/initializer/initializer_generator.rb +13 -0
  34. data/lib/generators/api_guard/initializer/templates/initializer.rb +19 -0
  35. metadata +202 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ module JwtAuth
5
+ # Common module for refresh token functionality
6
+ module RefreshJwtToken
7
+ def refresh_token_association(resource)
8
+ resource.class.refresh_token_association
9
+ end
10
+
11
+ def refresh_token_enabled?(resource)
12
+ refresh_token_association(resource).present?
13
+ end
14
+
15
+ def refresh_tokens_for(resource)
16
+ refresh_token_association = refresh_token_association(resource)
17
+ resource.send(refresh_token_association)
18
+ end
19
+
20
+ def find_refresh_token_of(resource, refresh_token)
21
+ refresh_tokens_for(resource).find_by_token(refresh_token)
22
+ end
23
+
24
+ # Generate and return unique refresh token for the resource
25
+ def uniq_refresh_token(resource)
26
+ loop do
27
+ random_token = SecureRandom.urlsafe_base64
28
+ return random_token unless refresh_tokens_for(resource).exists?(token: random_token)
29
+ end
30
+ end
31
+
32
+ # Create a new refresh_token for the current resource
33
+ def new_refresh_token(resource)
34
+ return unless refresh_token_enabled?(resource)
35
+
36
+ refresh_tokens_for(resource).create(token: uniq_refresh_token(resource)).token
37
+ end
38
+
39
+ def destroy_all_refresh_tokens(resource)
40
+ return unless refresh_token_enabled?(resource)
41
+
42
+ refresh_tokens_for(resource).destroy_all
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ module Models
5
+ module Concerns
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def api_guard_associations(refresh_token: nil, blacklisted_token: nil)
10
+ return if ApiGuard.api_guard_associations[name]
11
+
12
+ ApiGuard.api_guard_associations[name] = {}
13
+ ApiGuard.api_guard_associations[name][:refresh_token] = refresh_token
14
+ ApiGuard.api_guard_associations[name][:blacklisted_token] = blacklisted_token
15
+ end
16
+
17
+ def refresh_token_association
18
+ ApiGuard.api_guard_associations.dig(name, :refresh_token)
19
+ end
20
+
21
+ def blacklisted_token_association
22
+ ApiGuard.api_guard_associations.dig(name, :blacklisted_token)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api_guard/resource_mapper'
4
+ require 'api_guard/jwt_auth/json_web_token'
5
+ require 'api_guard/jwt_auth/authentication'
6
+ require 'api_guard/jwt_auth/refresh_jwt_token'
7
+ require 'api_guard/jwt_auth/blacklist_token'
8
+ require 'api_guard/response_formatters/renderer'
9
+ require 'api_guard/models/concerns'
10
+
11
+ module ApiGuard
12
+ module Modules
13
+ ActiveSupport.on_load(:action_controller) do
14
+ include ApiGuard::Resource
15
+ include ApiGuard::JwtAuth::JsonWebToken
16
+ include ApiGuard::JwtAuth::Authentication
17
+ include ApiGuard::JwtAuth::RefreshJwtToken
18
+ include ApiGuard::JwtAuth::BlacklistToken
19
+ include ApiGuard::ResponseFormatters::Renderer
20
+ end
21
+
22
+ ActiveSupport.on_load(:active_record) do
23
+ include ApiGuard::Models::Concerns
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ class ResourceMapper
5
+ attr_reader :resource_name, :resource_class, :resource_instance_name
6
+
7
+ def initialize(routes_for, class_name)
8
+ @resource_name = routes_for.singularize
9
+ @resource_class = class_name.constantize
10
+ @resource_instance_name = "@api_guard_#{routes_for}"
11
+ end
12
+ end
13
+
14
+ module Resource
15
+ def resource
16
+ instance_variable_get(mapped_resource_instance)
17
+ end
18
+
19
+ def resource=(new_resource)
20
+ instance_variable_set(mapped_resource_instance, new_resource)
21
+ end
22
+
23
+ def current_resource_mapping
24
+ request.env['api_guard.mapping']
25
+ end
26
+
27
+ def resource_name
28
+ current_resource_mapping.resource_name
29
+ end
30
+
31
+ def resource_class
32
+ current_resource_mapping.resource_class
33
+ end
34
+
35
+ def mapped_resource_instance
36
+ current_resource_mapping.resource_instance_name
37
+ end
38
+
39
+ def init_resource(params)
40
+ self.resource = resource_class.new(params)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ module ResponseFormatters
5
+ module Renderer
6
+ def render_success(data: nil, message: nil)
7
+ resp_data = { status: I18n.t('api_guard.response.success') }
8
+ resp_data[:message] = message if message
9
+ resp_data[:data] = data if data
10
+
11
+ render json: resp_data, status: 200
12
+ end
13
+
14
+ def render_error(status, options = {})
15
+ data = { status: I18n.t('api_guard.response.error') }
16
+ data[:error] = options[:object] ? options[:object].errors.full_messages[0] : options[:message]
17
+
18
+ render json: data, status: status
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Referenced from devise gem:
4
+ # https://github.com/plataformatec/devise/blob/master/lib/devise/rails/routes.rb
5
+ #
6
+ # Customizable API routes
7
+ module ActionDispatch
8
+ module Routing
9
+ class Mapper
10
+ def api_guard_routes(options = {})
11
+ routes_for = options.delete(:for).to_s || 'users'
12
+
13
+ controllers = default_controllers(options[:only], options[:except])
14
+ controller_options = options.delete(:controller)
15
+
16
+ options[:as] = options[:as] || routes_for.singularize
17
+ options[:path] = options[:path] || routes_for
18
+
19
+ api_guard_scope(routes_for) do |mapped_resource|
20
+ scope options do
21
+ generate_routes(mapped_resource, controller_options, controllers)
22
+ end
23
+ end
24
+ end
25
+
26
+ def api_guard_scope(routes_for)
27
+ mapped_resource = ApiGuard.mapped_resource[routes_for.to_sym].presence ||
28
+ ApiGuard.map_resource(routes_for, routes_for.classify)
29
+
30
+ constraint = lambda do |request|
31
+ request.env['api_guard.mapping'] = mapped_resource
32
+ true
33
+ end
34
+
35
+ constraints(constraint) do
36
+ yield(mapped_resource)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def default_controllers(only, except)
43
+ return only if only
44
+
45
+ controllers = %i[registration authentication tokens passwords]
46
+ except ? (controllers - except) : controllers
47
+ end
48
+
49
+ def generate_routes(mapped_resource, options, controllers)
50
+ options ||= {}
51
+ controllers -= %i[tokens] unless mapped_resource.resource_class.refresh_token_association
52
+
53
+ controllers.each do |controller|
54
+ send("#{controller}_routes", options[controller])
55
+ end
56
+ end
57
+
58
+ def authentication_routes(controller_name = nil)
59
+ controller_name ||= 'api_guard/authentication'
60
+
61
+ post 'sign_in' => "#{controller_name}#create"
62
+ delete 'sign_out' => "#{controller_name}#destroy"
63
+ end
64
+
65
+ def registration_routes(controller_name = nil)
66
+ controller_name ||= 'api_guard/registration'
67
+
68
+ post 'sign_up' => "#{controller_name}#create"
69
+ delete 'delete' => "#{controller_name}#destroy"
70
+ end
71
+
72
+ def passwords_routes(controller_name = nil)
73
+ controller_name ||= 'api_guard/passwords'
74
+
75
+ patch 'passwords' => "#{controller_name}#update"
76
+ end
77
+
78
+ def tokens_routes(controller_name = nil)
79
+ controller_name ||= 'api_guard/tokens'
80
+
81
+ post 'tokens' => "#{controller_name}#create"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'api_guard/jwt_auth/json_web_token'
4
+ require 'api_guard/jwt_auth/refresh_jwt_token'
5
+
6
+ module ApiGuard
7
+ module Test
8
+ module ControllerHelper
9
+ include ApiGuard::JwtAuth::JsonWebToken
10
+ include ApiGuard::JwtAuth::RefreshJwtToken
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ VERSION = '0.5.1'
5
+ end
@@ -0,0 +1,11 @@
1
+ Description:
2
+ Generates all API Guard controllers in app/controllers/
3
+
4
+ Example:
5
+ rails generate api_guard:controllers users
6
+
7
+ This will create:
8
+ app/controllers/users/registration_controller.rb
9
+ app/controllers/users/authentication_controller.rb
10
+ app/controllers/users/tokens_controller.rb
11
+ app/controllers/users/passwords_controller.rb
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ class ControllersGenerator < Rails::Generators::Base
5
+ CONTROLLERS = %i[registration authentication tokens passwords].freeze
6
+
7
+ desc 'Generates API Guard controllers in app/controllers/'
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ argument :scope, required: true, desc: 'The scope to create controllers in, e.g. users, admins'
11
+
12
+ class_option :controllers, aliases: '-c', type: :array,
13
+ desc: "Specify the controllers to generate (#{CONTROLLERS.join(', ')})"
14
+
15
+ def create_controllers
16
+ @controller_scope = scope.camelize
17
+ controllers = options[:controllers] || CONTROLLERS
18
+
19
+ controllers.each do |controller_name|
20
+ template "#{controller_name}_controller.rb",
21
+ "app/controllers/#{scope}/#{controller_name}_controller.rb"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ module <%= @controller_scope %>
2
+ class AuthenticationController < ApiGuard::AuthenticationController
3
+ # before_action :find_resource, only: [:create]
4
+ # before_action :authenticate_resource, only: [:destroy]
5
+
6
+ # def create
7
+ # if resource.authenticate(params[:password])
8
+ # create_token_and_set_header(resource, resource_name)
9
+ # render_success(message: I18n.t('api_guard.authentication.signed_in'))
10
+ # else
11
+ # render_error(422, message: I18n.t('api_guard.authentication.invalid_login_credentials'))
12
+ # end
13
+ # end
14
+
15
+ # def destroy
16
+ # blacklist_token
17
+ # render_success(message: I18n.t('api_guard.authentication.signed_out'))
18
+ # end
19
+
20
+ # private
21
+
22
+ # def find_resource
23
+ # self.resource = resource_class.find_by(email: params[:email].downcase.strip) if params[:email].present?
24
+ # render_error(422, message: I18n.t('api_guard.authentication.invalid_login_credentials')) unless resource
25
+ # end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module <%= @controller_scope %>
2
+ class PasswordsController < ApiGuard::PasswordsController
3
+ # before_action :authenticate_resource, only: [:update]
4
+
5
+ # def update
6
+ # invalidate_old_jwt_tokens(current_resource)
7
+ #
8
+ # if current_resource.update_attributes(password_params)
9
+ # blacklist_token unless ApiGuard.invalidate_old_tokens_on_password_change
10
+ # destroy_all_refresh_tokens(current_resource)
11
+ #
12
+ # create_token_and_set_header(current_resource, resource_name)
13
+ # render_success(message: I18n.t('api_guard.password.changed'))
14
+ # else
15
+ # render_error(422, object: current_resource)
16
+ # end
17
+ # end
18
+
19
+ # private
20
+
21
+ # def password_params
22
+ # params.permit(:password, :password_confirmation)
23
+ # end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module <%= @controller_scope %>
2
+ class RegistrationController < ApiGuard::RegistrationController
3
+ # before_action :authenticate_resource, only: [:destroy]
4
+
5
+ # def create
6
+ # init_resource(sign_up_params)
7
+ # if resource.save
8
+ # create_token_and_set_header(resource, resource_name)
9
+ # render_success(message: I18n.t('api_guard.registration.signed_up'))
10
+ # else
11
+ # render_error(422, object: resource)
12
+ # end
13
+ # end
14
+
15
+ # def destroy
16
+ # current_resource.destroy
17
+ # render_success(message: I18n.t('api_guard.registration.account_deleted'))
18
+ # end
19
+
20
+ # private
21
+
22
+ # def sign_up_params
23
+ # params.permit(:email, :password, :password_confirmation)
24
+ # end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module <%= @controller_scope %>
2
+ class TokensController < ApiGuard::TokensController
3
+ # before_action :authenticate_resource, only: [:create]
4
+ # before_action :find_refresh_token, only: [:create]
5
+
6
+ # def create
7
+ # create_token_and_set_header(current_resource, resource_name)
8
+ #
9
+ # @refresh_token.destroy
10
+ # blacklist_token if ApiGuard.blacklist_token_after_refreshing
11
+ #
12
+ # render_success(message: I18n.t('api_guard.access_token.refreshed'))
13
+ # end
14
+
15
+ # private
16
+
17
+ # def find_refresh_token
18
+ # refresh_token_from_header = request.headers['Refresh-Token']
19
+ #
20
+ # if refresh_token_from_header
21
+ # @refresh_token = find_refresh_token_of(current_resource, refresh_token_from_header)
22
+ # return render_error(401, message: I18n.t('api_guard.refresh_token.invalid')) unless @refresh_token
23
+ # else
24
+ # render_error(401, message: I18n.t('api_guard.refresh_token.missing'))
25
+ # end
26
+ # end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Creates initializer for configuring API Guard
3
+
4
+ Example:
5
+ rails generate api_guard:initializer
6
+
7
+ This will create:
8
+ config/initializers/api_guard.rb
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ class InitializerGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('templates', __dir__)
6
+
7
+ desc 'Creates initializer for configuring API Guard'
8
+
9
+ def create_initializer
10
+ copy_file 'initializer.rb', 'config/initializers/api_guard.rb'
11
+ end
12
+ end
13
+ end