api_guard_grape 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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