api_guard_grape 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +690 -0
- data/Rakefile +21 -0
- data/app/controllers/api_guard/application_controller.rb +11 -0
- data/app/controllers/api_guard/authentication_controller.rb +31 -0
- data/app/controllers/api_guard/passwords_controller.rb +29 -0
- data/app/controllers/api_guard/registration_controller.rb +30 -0
- data/app/controllers/api_guard/tokens_controller.rb +32 -0
- data/config/locales/en.yml +22 -0
- data/config/routes.rb +6 -0
- data/lib/api_guard.rb +40 -0
- data/lib/api_guard/app_secret_key.rb +22 -0
- data/lib/api_guard/engine.rb +17 -0
- data/lib/api_guard/jwt_auth/authentication.rb +98 -0
- data/lib/api_guard/jwt_auth/blacklist_token.rb +35 -0
- data/lib/api_guard/jwt_auth/json_web_token.rb +72 -0
- data/lib/api_guard/jwt_auth/refresh_jwt_token.rb +46 -0
- data/lib/api_guard/models/concerns.rb +27 -0
- data/lib/api_guard/modules.rb +26 -0
- data/lib/api_guard/resource_mapper.rb +43 -0
- data/lib/api_guard/response_formatters/renderer.rb +22 -0
- data/lib/api_guard/route_mapper.rb +85 -0
- data/lib/api_guard/test/controller_helper.rb +13 -0
- data/lib/api_guard/version.rb +5 -0
- data/lib/generators/api_guard/controllers/USAGE +11 -0
- data/lib/generators/api_guard/controllers/controllers_generator.rb +25 -0
- data/lib/generators/api_guard/controllers/templates/authentication_controller.rb +27 -0
- data/lib/generators/api_guard/controllers/templates/passwords_controller.rb +25 -0
- data/lib/generators/api_guard/controllers/templates/registration_controller.rb +26 -0
- data/lib/generators/api_guard/controllers/templates/tokens_controller.rb +28 -0
- data/lib/generators/api_guard/initializer/USAGE +8 -0
- data/lib/generators/api_guard/initializer/initializer_generator.rb +13 -0
- data/lib/generators/api_guard/initializer/templates/initializer.rb +19 -0
- 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,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,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
|