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.
- 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
|