graphql_devise 0.11.1 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rspec +1 -0
- data/.travis.yml +11 -3
- data/CHANGELOG.md +43 -0
- data/README.md +191 -32
- data/app/controllers/graphql_devise/application_controller.rb +4 -1
- data/app/controllers/graphql_devise/concerns/set_user_by_token.rb +23 -0
- data/app/controllers/graphql_devise/graphql_controller.rb +2 -0
- data/app/helpers/graphql_devise/mailer_helper.rb +2 -2
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +14 -7
- data/graphql_devise.gemspec +6 -4
- data/lib/generators/graphql_devise/install_generator.rb +63 -30
- data/lib/graphql_devise.rb +39 -6
- data/lib/graphql_devise/default_operations/mutations.rb +6 -6
- data/lib/graphql_devise/default_operations/resolvers.rb +2 -2
- data/lib/graphql_devise/errors/authentication_error.rb +7 -0
- data/lib/graphql_devise/{detailed_user_error.rb → errors/detailed_user_error.rb} +1 -1
- data/lib/graphql_devise/errors/error_codes.rb +6 -0
- data/lib/graphql_devise/errors/execution_error.rb +4 -0
- data/lib/graphql_devise/{user_error.rb → errors/user_error.rb} +1 -1
- data/lib/graphql_devise/mount_method/operation_preparer.rb +2 -2
- data/lib/graphql_devise/mount_method/operation_preparers/default_operation_preparer.rb +6 -2
- data/lib/graphql_devise/mount_method/operation_preparers/gql_name_setter.rb +1 -1
- data/lib/graphql_devise/mount_method/operation_preparers/mutation_field_setter.rb +3 -2
- data/lib/graphql_devise/mount_method/operation_preparers/resolver_type_setter.rb +1 -1
- data/lib/graphql_devise/mount_method/operation_preparers/resource_name_setter.rb +2 -2
- data/lib/graphql_devise/mutations/resend_confirmation.rb +1 -4
- data/lib/graphql_devise/mutations/send_password_reset.rb +3 -1
- data/lib/graphql_devise/mutations/sign_up.rb +1 -5
- data/lib/graphql_devise/rails/routes.rb +5 -67
- data/lib/graphql_devise/resource_loader.rb +87 -0
- data/lib/graphql_devise/schema_plugin.rb +87 -0
- data/lib/graphql_devise/version.rb +1 -1
- data/spec/dummy/app/controllers/api/v1/graphql_controller.rb +11 -2
- data/spec/dummy/app/controllers/application_controller.rb +1 -0
- data/spec/dummy/app/graphql/dummy_schema.rb +9 -0
- data/spec/dummy/app/graphql/interpreter_schema.rb +9 -0
- data/spec/dummy/app/graphql/types/mutation_type.rb +1 -1
- data/spec/dummy/app/graphql/types/query_type.rb +10 -0
- data/spec/dummy/config/environments/test.rb +1 -1
- data/spec/dummy/config/routes.rb +1 -0
- data/spec/generators/graphql_devise/install_generator_spec.rb +62 -30
- data/spec/rails_helper.rb +4 -1
- data/spec/requests/graphql_controller_spec.rb +80 -0
- data/spec/requests/mutations/resend_confirmation_spec.rb +2 -14
- data/spec/requests/mutations/send_password_reset_spec.rb +8 -3
- data/spec/requests/user_controller_spec.rb +180 -24
- data/spec/services/mount_method/operation_preparer_spec.rb +8 -3
- data/spec/services/mount_method/operation_preparers/custom_operation_preparer_spec.rb +1 -1
- data/spec/services/mount_method/operation_preparers/default_operation_preparer_spec.rb +15 -8
- data/spec/services/mount_method/operation_preparers/mutation_field_setter_spec.rb +18 -4
- data/spec/services/mount_method/operation_preparers/resource_name_setter_spec.rb +1 -1
- data/spec/services/resource_loader_spec.rb +82 -0
- data/spec/services/schema_plugin_spec.rb +26 -0
- data/spec/spec_helper.rb +1 -1
- metadata +107 -89
- data/lib/graphql_devise/error_codes.rb +0 -5
- data/spec/support/generators/file_helpers.rb +0 -12
@@ -1,5 +1,28 @@
|
|
1
1
|
module GraphqlDevise
|
2
2
|
module Concerns
|
3
3
|
SetUserByToken = DeviseTokenAuth::Concerns::SetUserByToken
|
4
|
+
|
5
|
+
SetUserByToken.module_eval do
|
6
|
+
attr_accessor :client_id, :token, :resource
|
7
|
+
|
8
|
+
alias_method :set_resource_by_token, :set_user_by_token
|
9
|
+
|
10
|
+
def graphql_context
|
11
|
+
{
|
12
|
+
current_resource: @resource,
|
13
|
+
controller: self
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_redirect_headers(access_token, client, redirect_header_options = {})
|
18
|
+
{
|
19
|
+
DeviseTokenAuth.headers_names[:"access-token"] => access_token,
|
20
|
+
DeviseTokenAuth.headers_names[:client] => client,
|
21
|
+
:config => params[:config],
|
22
|
+
:client_id => client,
|
23
|
+
:token => access_token
|
24
|
+
}.merge(redirect_header_options)
|
25
|
+
end
|
26
|
+
end
|
4
27
|
end
|
5
28
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module GraphqlDevise
|
2
2
|
module MailerHelper
|
3
3
|
def confirmation_query(resource_name:, token:, redirect_url:)
|
4
|
-
name = "#{resource_name.camelize(:lower)}ConfirmAccount"
|
4
|
+
name = "#{resource_name.underscore.tr('/', '_').camelize(:lower)}ConfirmAccount"
|
5
5
|
raw = <<-GRAPHQL
|
6
6
|
query($token:String!,$redirectUrl:String!){
|
7
7
|
#{name}(confirmationToken:$token,redirectUrl:$redirectUrl){
|
@@ -17,7 +17,7 @@ module GraphqlDevise
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def password_reset_query(token:, redirect_url:, resource_name:)
|
20
|
-
name = "#{resource_name.camelize(:lower)}CheckPasswordToken"
|
20
|
+
name = "#{resource_name.underscore.tr('/', '_').camelize(:lower)}CheckPasswordToken"
|
21
21
|
raw = <<-GRAPHQL
|
22
22
|
query($token:String!,$redirectUrl:String!){
|
23
23
|
#{name}(resetPasswordToken:$token,redirectUrl:$redirectUrl){
|
data/config/locales/en.yml
CHANGED
@@ -14,6 +14,7 @@ en:
|
|
14
14
|
password_not_required: "This account does not require a password. Sign in using your '%{provider}' account instead."
|
15
15
|
reset_token_not_found: "No user found for the specified reset token."
|
16
16
|
reset_token_expired: "Reset password token is no longer valid."
|
17
|
+
send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
|
17
18
|
sessions:
|
18
19
|
bad_credentials: "Invalid login credentials. Please try again."
|
19
20
|
not_confirmed: "A confirmation email was sent to your account at '%{email}'. You must follow the instructions in the email before your account can be activated"
|
data/config/routes.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
GraphqlDevise::Engine.routes.draw do
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
# Required as Devise forces routes to reload on eager_load
|
3
|
+
unless GraphqlDevise.schema_loaded?
|
4
|
+
if GraphqlDevise::Types::QueryType.fields.blank?
|
5
|
+
GraphqlDevise::Types::QueryType.field(:dummy, resolver: GraphqlDevise::Resolvers::Dummy)
|
6
|
+
end
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
if GraphqlDevise::Types::MutationType.fields.present?
|
9
|
+
GraphqlDevise::Schema.mutation(GraphqlDevise::Types::MutationType)
|
10
|
+
end
|
11
|
+
|
12
|
+
GraphqlDevise::Schema.query(GraphqlDevise::Types::QueryType)
|
9
13
|
|
10
|
-
|
14
|
+
GraphqlDevise.load_schema
|
15
|
+
|
16
|
+
Devise.mailer.helper(GraphqlDevise::MailerHelper)
|
17
|
+
end
|
11
18
|
end
|
data/graphql_devise.gemspec
CHANGED
@@ -21,13 +21,15 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.bindir = 'exe'
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ['lib']
|
24
|
-
spec.test_files = Dir
|
24
|
+
spec.test_files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").select { |f| f.match(%r{^spec/}) }
|
26
|
+
end
|
25
27
|
|
26
28
|
spec.required_ruby_version = '>= 2.2.0'
|
27
29
|
|
28
|
-
spec.add_dependency 'devise_token_auth', '>= 0.1.43'
|
29
|
-
spec.add_dependency 'graphql', '>= 1.8'
|
30
|
-
spec.add_dependency 'rails', '>= 4.2'
|
30
|
+
spec.add_dependency 'devise_token_auth', '>= 0.1.43', '< 2.0'
|
31
|
+
spec.add_dependency 'graphql', '>= 1.8', '< 1.11.0'
|
32
|
+
spec.add_dependency 'rails', '>= 4.2', '< 6.2'
|
31
33
|
|
32
34
|
spec.add_development_dependency 'appraisal'
|
33
35
|
spec.add_development_dependency 'coveralls'
|
@@ -5,62 +5,95 @@ module GraphqlDevise
|
|
5
5
|
argument :user_class, type: :string, default: 'User'
|
6
6
|
argument :mount_path, type: :string, default: 'auth'
|
7
7
|
|
8
|
+
class_option :mount, type: :string, default: 'separate_route'
|
9
|
+
|
8
10
|
def execute_devise_installer
|
9
11
|
generate 'devise:install'
|
10
12
|
end
|
11
13
|
|
12
14
|
def execute_dta_installer
|
15
|
+
# Necessary in case of a re-run of the generator, for DTA to detect concerns already included
|
16
|
+
if File.exist?(File.expand_path("app/models/#{user_class.underscore}.rb", destination_root))
|
17
|
+
gsub_file(
|
18
|
+
"app/models/#{user_class.underscore}.rb",
|
19
|
+
'GraphqlDevise::Concerns::Model',
|
20
|
+
'DeviseTokenAuth::Concerns::User'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
gsub_file(
|
24
|
+
'app/controllers/application_controller.rb',
|
25
|
+
'GraphqlDevise::Concerns::SetUserByToken',
|
26
|
+
'DeviseTokenAuth::Concerns::SetUserByToken'
|
27
|
+
)
|
28
|
+
|
13
29
|
generate 'devise_token_auth:install', "#{user_class} #{mount_path}"
|
14
30
|
end
|
15
31
|
|
16
32
|
def mount_resource_route
|
17
33
|
routes_file = 'config/routes.rb'
|
18
|
-
routes_path = File.join(destination_root, routes_file)
|
19
|
-
gem_helper = 'mount_graphql_devise_for'
|
20
|
-
gem_route = "#{gem_helper} '#{user_class}', at: '#{mount_path}'"
|
21
34
|
dta_route = "mount_devise_token_auth_for '#{user_class}', at: '#{mount_path}'"
|
22
|
-
file_start = 'Rails.application.routes.draw do'
|
23
35
|
|
24
|
-
if
|
25
|
-
|
36
|
+
if options['mount'] != 'separate_route'
|
37
|
+
gsub_file(routes_file, /^\s+#{Regexp.escape(dta_route + "\n")}/i, '')
|
38
|
+
else
|
39
|
+
gem_route = "mount_graphql_devise_for '#{user_class}', at: '#{mount_path}'"
|
40
|
+
|
41
|
+
if file_contains_str?(routes_file, gem_route)
|
42
|
+
gsub_file(routes_file, /^\s+#{Regexp.escape(dta_route + "\n")}/i, '')
|
26
43
|
|
27
|
-
if current_route.present?
|
28
44
|
say_status('skipped', "Routes already exist for #{user_class} at #{mount_path}")
|
29
45
|
else
|
30
|
-
|
31
|
-
|
32
|
-
if current_dta_route.present?
|
33
|
-
replace_line(routes_path, dta_route, gem_route)
|
34
|
-
else
|
35
|
-
insert_text_after_line(routes_path, file_start, gem_route)
|
36
|
-
end
|
46
|
+
gsub_file(routes_file, /#{Regexp.escape(dta_route)}/i, gem_route)
|
37
47
|
end
|
38
|
-
else
|
39
|
-
say_status('skipped', "#{routes_file} not found. Add \"#{gem_route}\" to your routes file.")
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
43
|
-
|
51
|
+
def replace_model_concern
|
52
|
+
gsub_file(
|
53
|
+
"app/models/#{user_class.underscore}.rb",
|
54
|
+
/^\s+include DeviseTokenAuth::Concerns::User/,
|
55
|
+
' include GraphqlDevise::Concerns::Model'
|
56
|
+
)
|
57
|
+
end
|
44
58
|
|
45
|
-
def
|
46
|
-
gsub_file(
|
47
|
-
|
48
|
-
|
59
|
+
def replace_controller_concern
|
60
|
+
gsub_file(
|
61
|
+
'app/controllers/application_controller.rb',
|
62
|
+
/^\s+include DeviseTokenAuth::Concerns::SetUserByToken/,
|
63
|
+
' include GraphqlDevise::Concerns::SetUserByToken'
|
64
|
+
)
|
49
65
|
end
|
50
66
|
|
51
|
-
def
|
52
|
-
gsub_file(
|
67
|
+
def set_change_headers_on_each_request_false
|
68
|
+
gsub_file(
|
69
|
+
'config/initializers/devise_token_auth.rb',
|
70
|
+
'# config.change_headers_on_each_request = true',
|
71
|
+
'config.change_headers_on_each_request = false'
|
72
|
+
)
|
53
73
|
end
|
54
74
|
|
55
|
-
def
|
56
|
-
|
75
|
+
def mount_in_schema
|
76
|
+
return if options['mount'] == 'separate_route'
|
57
77
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
78
|
+
inject_into_file "app/graphql/#{options['mount'].underscore}.rb", after: "< GraphQL::Schema\n" do
|
79
|
+
<<-RUBY
|
80
|
+
use GraphqlDevise::SchemaPlugin.new(
|
81
|
+
query: Types::QueryType,
|
82
|
+
mutation: Types::MutationType,
|
83
|
+
resource_loaders: [
|
84
|
+
GraphqlDevise::ResourceLoader.new('#{user_class}'),
|
85
|
+
]
|
86
|
+
)
|
87
|
+
RUBY
|
62
88
|
end
|
63
|
-
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def file_contains_str?(filename, regex_str)
|
94
|
+
path = File.join(destination_root, filename)
|
95
|
+
|
96
|
+
File.read(path) =~ /(#{Regexp.escape(regex_str)})/i
|
64
97
|
end
|
65
98
|
end
|
66
99
|
end
|
data/lib/graphql_devise.rb
CHANGED
@@ -6,8 +6,44 @@ module GraphqlDevise
|
|
6
6
|
class Error < StandardError; end
|
7
7
|
|
8
8
|
class InvalidMountOptionsError < GraphqlDevise::Error; end
|
9
|
+
|
10
|
+
@schema_loaded = false
|
11
|
+
@mounted_resources = []
|
12
|
+
|
13
|
+
def self.schema_loaded?
|
14
|
+
@schema_loaded
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.load_schema
|
18
|
+
@schema_loaded = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.resource_mounted?(mapping_name)
|
22
|
+
@mounted_resources.include?(mapping_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.mount_resource(mapping_name)
|
26
|
+
@mounted_resources << mapping_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add_mapping(mapping_name, resource)
|
30
|
+
return if Devise.mappings.key?(mapping_name)
|
31
|
+
|
32
|
+
Devise.add_mapping(
|
33
|
+
mapping_name.to_s.pluralize.to_sym,
|
34
|
+
module: :devise, class_name: resource
|
35
|
+
)
|
36
|
+
end
|
9
37
|
end
|
10
38
|
|
39
|
+
require 'graphql_devise/engine'
|
40
|
+
require 'graphql_devise/version'
|
41
|
+
require 'graphql_devise/errors/error_codes'
|
42
|
+
require 'graphql_devise/errors/execution_error'
|
43
|
+
require 'graphql_devise/errors/user_error'
|
44
|
+
require 'graphql_devise/errors/authentication_error'
|
45
|
+
require 'graphql_devise/errors/detailed_user_error'
|
46
|
+
|
11
47
|
require 'graphql_devise/concerns/controller_methods'
|
12
48
|
require 'graphql_devise/schema'
|
13
49
|
require 'graphql_devise/types/authenticatable_type'
|
@@ -18,13 +54,10 @@ require 'graphql_devise/default_operations/mutations'
|
|
18
54
|
require 'graphql_devise/default_operations/resolvers'
|
19
55
|
require 'graphql_devise/resolvers/dummy'
|
20
56
|
|
21
|
-
require 'graphql_devise/engine'
|
22
|
-
require 'graphql_devise/version'
|
23
|
-
require 'graphql_devise/error_codes'
|
24
|
-
require 'graphql_devise/user_error'
|
25
|
-
require 'graphql_devise/detailed_user_error'
|
26
|
-
|
27
57
|
require 'graphql_devise/mount_method/option_sanitizer'
|
28
58
|
require 'graphql_devise/mount_method/options_validator'
|
29
59
|
require 'graphql_devise/mount_method/operation_preparer'
|
30
60
|
require 'graphql_devise/mount_method/operation_sanitizer'
|
61
|
+
|
62
|
+
require 'graphql_devise/resource_loader'
|
63
|
+
require 'graphql_devise/schema_plugin'
|
@@ -9,12 +9,12 @@ require 'graphql_devise/mutations/update_password'
|
|
9
9
|
module GraphqlDevise
|
10
10
|
module DefaultOperations
|
11
11
|
MUTATIONS = {
|
12
|
-
login: GraphqlDevise::Mutations::Login,
|
13
|
-
logout: GraphqlDevise::Mutations::Logout,
|
14
|
-
sign_up: GraphqlDevise::Mutations::SignUp,
|
15
|
-
update_password: GraphqlDevise::Mutations::UpdatePassword,
|
16
|
-
send_password_reset: GraphqlDevise::Mutations::SendPasswordReset,
|
17
|
-
resend_confirmation: GraphqlDevise::Mutations::ResendConfirmation
|
12
|
+
login: { klass: GraphqlDevise::Mutations::Login, authenticatable: true },
|
13
|
+
logout: { klass: GraphqlDevise::Mutations::Logout, authenticatable: true },
|
14
|
+
sign_up: { klass: GraphqlDevise::Mutations::SignUp, authenticatable: true },
|
15
|
+
update_password: { klass: GraphqlDevise::Mutations::UpdatePassword, authenticatable: true },
|
16
|
+
send_password_reset: { klass: GraphqlDevise::Mutations::SendPasswordReset, authenticatable: false },
|
17
|
+
resend_confirmation: { klass: GraphqlDevise::Mutations::ResendConfirmation, authenticatable: false }
|
18
18
|
}.freeze
|
19
19
|
end
|
20
20
|
end
|
@@ -5,8 +5,8 @@ require 'graphql_devise/resolvers/confirm_account'
|
|
5
5
|
module GraphqlDevise
|
6
6
|
module DefaultOperations
|
7
7
|
QUERIES = {
|
8
|
-
confirm_account: GraphqlDevise::Resolvers::ConfirmAccount,
|
9
|
-
check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken
|
8
|
+
confirm_account: { klass: GraphqlDevise::Resolvers::ConfirmAccount },
|
9
|
+
check_password_token: { klass: GraphqlDevise::Resolvers::CheckPasswordToken }
|
10
10
|
}.freeze
|
11
11
|
end
|
12
12
|
end
|
@@ -8,10 +8,10 @@ require_relative 'operation_preparers/custom_operation_preparer'
|
|
8
8
|
module GraphqlDevise
|
9
9
|
module MountMethod
|
10
10
|
class OperationPreparer
|
11
|
-
def initialize(
|
11
|
+
def initialize(mapping_name:, selected_operations:, preparer:, custom:, additional_operations:)
|
12
12
|
@selected_operations = selected_operations
|
13
13
|
@preparer = preparer
|
14
|
-
@mapping_name =
|
14
|
+
@mapping_name = mapping_name
|
15
15
|
@custom = custom
|
16
16
|
@additional_operations = additional_operations
|
17
17
|
end
|
@@ -10,14 +10,18 @@ module GraphqlDevise
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def call
|
13
|
-
@selected_operations.except(*@custom_keys).each_with_object({}) do |(action,
|
13
|
+
@selected_operations.except(*@custom_keys).each_with_object({}) do |(action, operation_info), result|
|
14
14
|
mapped_action = "#{@mapping_name}_#{action}"
|
15
|
+
operation = operation_info[:klass]
|
16
|
+
options = operation_info.except(:klass)
|
15
17
|
|
16
18
|
result[mapped_action.to_sym] = [
|
17
19
|
OperationPreparers::GqlNameSetter.new(mapped_action),
|
18
20
|
@preparer,
|
19
21
|
OperationPreparers::ResourceNameSetter.new(@mapping_name)
|
20
|
-
].reduce(child_class(operation))
|
22
|
+
].reduce(child_class(operation)) do |prepared_operation, preparer|
|
23
|
+
preparer.call(prepared_operation, **options)
|
24
|
+
end
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
@@ -6,9 +6,10 @@ module GraphqlDevise
|
|
6
6
|
@authenticatable_type = authenticatable_type
|
7
7
|
end
|
8
8
|
|
9
|
-
def call(mutation)
|
10
|
-
mutation
|
9
|
+
def call(mutation, authenticatable: true)
|
10
|
+
return mutation unless authenticatable
|
11
11
|
|
12
|
+
mutation.field(:authenticatable, @authenticatable_type, null: false)
|
12
13
|
mutation
|
13
14
|
end
|
14
15
|
end
|