graphql_devise 0.14.3 → 0.17.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +7 -0
  3. data/CHANGELOG.md +58 -0
  4. data/README.md +186 -82
  5. data/app/controllers/graphql_devise/concerns/additional_controller_methods.rb +72 -0
  6. data/app/controllers/graphql_devise/concerns/set_user_by_token.rb +5 -27
  7. data/app/helpers/graphql_devise/mailer_helper.rb +2 -2
  8. data/app/models/graphql_devise/concerns/additional_model_methods.rb +21 -0
  9. data/app/models/graphql_devise/concerns/model.rb +6 -9
  10. data/app/views/graphql_devise/mailer/confirmation_instructions.html.erb +7 -1
  11. data/lib/generators/graphql_devise/install_generator.rb +1 -1
  12. data/lib/graphql_devise.rb +20 -6
  13. data/lib/graphql_devise/concerns/controller_methods.rb +3 -3
  14. data/lib/graphql_devise/default_operations/mutations.rb +14 -8
  15. data/lib/graphql_devise/default_operations/resolvers.rb +2 -2
  16. data/lib/graphql_devise/model/with_email_updater.rb +34 -8
  17. data/lib/graphql_devise/mount_method/operation_preparer.rb +6 -6
  18. data/lib/graphql_devise/mount_method/operation_preparers/custom_operation_preparer.rb +6 -4
  19. data/lib/graphql_devise/mount_method/operation_preparers/default_operation_preparer.rb +7 -5
  20. data/lib/graphql_devise/mount_method/operation_preparers/{resource_name_setter.rb → resource_klass_setter.rb} +4 -4
  21. data/lib/graphql_devise/mount_method/operation_sanitizer.rb +13 -1
  22. data/lib/graphql_devise/mutations/confirm_registration_with_token.rb +30 -0
  23. data/lib/graphql_devise/mutations/login.rb +2 -0
  24. data/lib/graphql_devise/mutations/register.rb +60 -0
  25. data/lib/graphql_devise/mutations/resend_confirmation_with_token.rb +44 -0
  26. data/lib/graphql_devise/mutations/sign_up.rb +1 -1
  27. data/lib/graphql_devise/resolvers/confirm_account.rb +1 -1
  28. data/lib/graphql_devise/resource_loader.rb +26 -11
  29. data/lib/graphql_devise/schema_plugin.rb +20 -8
  30. data/lib/graphql_devise/version.rb +1 -1
  31. data/spec/dummy/app/controllers/api/v1/graphql_controller.rb +11 -0
  32. data/spec/dummy/app/graphql/dummy_schema.rb +4 -3
  33. data/spec/dummy/app/graphql/mutations/register.rb +14 -0
  34. data/spec/dummy/app/graphql/types/query_type.rb +5 -0
  35. data/spec/dummy/config/routes.rb +7 -5
  36. data/spec/dummy/db/migrate/20210516211417_add_vip_to_users.rb +5 -0
  37. data/spec/dummy/db/schema.rb +4 -3
  38. data/spec/generators/graphql_devise/install_generator_spec.rb +1 -1
  39. data/spec/graphql/user_queries_spec.rb +3 -1
  40. data/spec/graphql_devise/model/with_email_updater_spec.rb +97 -68
  41. data/spec/requests/graphql_controller_spec.rb +1 -1
  42. data/spec/requests/mutations/confirm_registration_with_token_spec.rb +117 -0
  43. data/spec/requests/mutations/register_spec.rb +166 -0
  44. data/spec/requests/mutations/resend_confirmation_with_token_spec.rb +137 -0
  45. data/spec/requests/user_controller_spec.rb +86 -25
  46. data/spec/services/mount_method/operation_preparer_spec.rb +5 -5
  47. data/spec/services/mount_method/operation_preparers/custom_operation_preparer_spec.rb +5 -5
  48. data/spec/services/mount_method/operation_preparers/default_operation_preparer_spec.rb +5 -5
  49. data/spec/services/mount_method/operation_preparers/{resource_name_setter_spec.rb → resource_klass_setter_spec.rb} +6 -6
  50. data/spec/services/mount_method/operation_sanitizer_spec.rb +3 -3
  51. data/spec/services/resource_loader_spec.rb +5 -5
  52. data/spec/support/contexts/graphql_request.rb +2 -2
  53. metadata +21 -6
@@ -4,23 +4,25 @@ module GraphqlDevise
4
4
  module MountMethod
5
5
  module OperationPreparers
6
6
  class DefaultOperationPreparer
7
- def initialize(selected_operations:, custom_keys:, mapping_name:, preparer:)
7
+ def initialize(selected_operations:, custom_keys:, model:, preparer:)
8
8
  @selected_operations = selected_operations
9
9
  @custom_keys = custom_keys
10
- @mapping_name = mapping_name
10
+ @model = model
11
11
  @preparer = preparer
12
12
  end
13
13
 
14
14
  def call
15
+ mapping_name = GraphqlDevise.to_mapping_name(@model)
16
+
15
17
  @selected_operations.except(*@custom_keys).each_with_object({}) do |(action, operation_info), result|
16
- mapped_action = "#{@mapping_name}_#{action}"
18
+ mapped_action = "#{mapping_name}_#{action}"
17
19
  operation = operation_info[:klass]
18
- options = operation_info.except(:klass)
20
+ options = operation_info.except(:klass, :deprecation_reason)
19
21
 
20
22
  result[mapped_action.to_sym] = [
21
23
  OperationPreparers::GqlNameSetter.new(mapped_action),
22
24
  @preparer,
23
- OperationPreparers::ResourceNameSetter.new(@mapping_name)
25
+ OperationPreparers::ResourceKlassSetter.new(@model)
24
26
  ].reduce(child_class(operation)) do |prepared_operation, preparer|
25
27
  preparer.call(prepared_operation, **options)
26
28
  end
@@ -3,13 +3,13 @@
3
3
  module GraphqlDevise
4
4
  module MountMethod
5
5
  module OperationPreparers
6
- class ResourceNameSetter
7
- def initialize(name)
8
- @name = name
6
+ class ResourceKlassSetter
7
+ def initialize(klass)
8
+ @klass = klass
9
9
  end
10
10
 
11
11
  def call(operation, **)
12
- operation.instance_variable_set(:@resource_name, @name)
12
+ operation.instance_variable_set(:@resource_klass, @klass)
13
13
 
14
14
  operation
15
15
  end
@@ -18,13 +18,25 @@ module GraphqlDevise
18
18
  end
19
19
 
20
20
  def call
21
- if @only.present?
21
+ operations = if @only.present?
22
22
  @default.slice(*@only)
23
23
  elsif @skipped.present?
24
24
  @default.except(*@skipped)
25
25
  else
26
26
  @default
27
27
  end
28
+
29
+ operations.each do |operation, values|
30
+ next if values[:deprecation_reason].blank?
31
+
32
+ ActiveSupport::Deprecation.warn(<<-DEPRECATION.strip_heredoc, caller)
33
+ `#{operation}` is deprecated and will be removed in a future version of this gem.
34
+ #{values[:deprecation_reason]}
35
+
36
+ You can supress this message by skipping `#{operation}` on your ResourceLoader or the
37
+ mount_graphql_devise_for method on your routes file.
38
+ DEPRECATION
39
+ end
28
40
  end
29
41
  end
30
42
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlDevise
4
+ module Mutations
5
+ class ConfirmRegistrationWithToken < Base
6
+ argument :confirmation_token, String, required: true
7
+
8
+ field :credentials,
9
+ GraphqlDevise::Types::CredentialType,
10
+ null: true,
11
+ description: 'Authentication credentials. Null unless user is signed in after confirmation.'
12
+
13
+ def resolve(confirmation_token:)
14
+ resource = resource_class.confirm_by_token(confirmation_token)
15
+
16
+ if resource.errors.empty?
17
+ yield resource if block_given?
18
+
19
+ response_payload = { authenticatable: resource }
20
+
21
+ response_payload[:credentials] = set_auth_headers(resource) if resource.active_for_authentication?
22
+
23
+ response_payload
24
+ else
25
+ raise_user_error(I18n.t('graphql_devise.confirmations.invalid_token'))
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -24,6 +24,8 @@ module GraphqlDevise
24
24
 
25
25
  yield resource if block_given?
26
26
 
27
+ context[:current_resource] = resource if context[:current_resource].nil?
28
+
27
29
  { authenticatable: resource, credentials: new_headers }
28
30
  elsif resource && !active_for_authentication?(resource)
29
31
  if locked?(resource)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlDevise
4
+ module Mutations
5
+ class Register < Base
6
+ argument :email, String, required: true
7
+ argument :password, String, required: true
8
+ argument :password_confirmation, String, required: true
9
+ argument :confirm_url, String, required: false
10
+
11
+ field :credentials,
12
+ GraphqlDevise::Types::CredentialType,
13
+ null: true,
14
+ description: 'Authentication credentials. Null if after signUp resource is not active for authentication (e.g. Email confirmation required).'
15
+
16
+ def resolve(confirm_url: nil, **attrs)
17
+ resource = build_resource(attrs.merge(provider: provider))
18
+ raise_user_error(I18n.t('graphql_devise.resource_build_failed')) if resource.blank?
19
+
20
+ redirect_url = confirm_url || DeviseTokenAuth.default_confirm_success_url
21
+ if confirmable_enabled? && redirect_url.blank?
22
+ raise_user_error(I18n.t('graphql_devise.registrations.missing_confirm_redirect_url'))
23
+ end
24
+
25
+ check_redirect_url_whitelist!(redirect_url)
26
+
27
+ resource.skip_confirmation_notification! if resource.respond_to?(:skip_confirmation_notification!)
28
+
29
+ if resource.save
30
+ yield resource if block_given?
31
+
32
+ unless resource.confirmed?
33
+ resource.send_confirmation_instructions(
34
+ redirect_url: redirect_url,
35
+ template_path: ['graphql_devise/mailer']
36
+ )
37
+ end
38
+
39
+ response_payload = { authenticatable: resource }
40
+
41
+ response_payload[:credentials] = set_auth_headers(resource) if resource.active_for_authentication?
42
+
43
+ response_payload
44
+ else
45
+ resource.try(:clean_up_passwords)
46
+ raise_user_error_list(
47
+ I18n.t('graphql_devise.registration_failed'),
48
+ errors: resource.errors.full_messages
49
+ )
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def build_resource(attrs)
56
+ resource_class.new(attrs)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlDevise
4
+ module Mutations
5
+ class ResendConfirmationWithToken < Base
6
+ argument :email, String, required: true
7
+ argument :confirm_url, String, required: true
8
+
9
+ field :message, String, null: false
10
+
11
+ def resolve(email:, confirm_url:)
12
+ check_redirect_url_whitelist!(confirm_url)
13
+
14
+ resource = find_confirmable_resource(email)
15
+
16
+ if resource
17
+ yield resource if block_given?
18
+
19
+ if resource.confirmed? && !resource.pending_reconfirmation?
20
+ raise_user_error(I18n.t('graphql_devise.confirmations.already_confirmed'))
21
+ end
22
+
23
+ resource.send_confirmation_instructions(
24
+ redirect_url: confirm_url,
25
+ template_path: ['graphql_devise/mailer']
26
+ )
27
+
28
+ { message: I18n.t('graphql_devise.confirmations.send_instructions', email: email) }
29
+ else
30
+ raise_user_error(I18n.t('graphql_devise.confirmations.user_not_found', email: email))
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def find_confirmable_resource(email)
37
+ email_insensitive = get_case_insensitive_field(:email, email)
38
+ resource = find_resource(:unconfirmed_email, email_insensitive) if resource_class.reconfirmable
39
+ resource ||= find_resource(:email, email_insensitive)
40
+ resource
41
+ end
42
+ end
43
+ end
44
+ end
@@ -31,7 +31,7 @@ module GraphqlDevise
31
31
 
32
32
  unless resource.confirmed?
33
33
  resource.send_confirmation_instructions(
34
- redirect_url: confirm_success_url,
34
+ redirect_url: redirect_url,
35
35
  template_path: ['graphql_devise/mailer'],
36
36
  schema_url: controller.full_url_without_params
37
37
  )
@@ -32,7 +32,7 @@ module GraphqlDevise
32
32
  end
33
33
 
34
34
  controller.redirect_to(redirect_to_link)
35
- { authenticatable: resource }
35
+ resource
36
36
  else
37
37
  raise_user_error(I18n.t('graphql_devise.confirmations.invalid_token'))
38
38
  end
@@ -10,12 +10,27 @@ module GraphqlDevise
10
10
  end
11
11
 
12
12
  def call(query, mutation)
13
- mapping_name = @resource.to_s.underscore.tr('/', '_').to_sym
14
-
15
13
  # clean_options responds to all keys defined in GraphqlDevise::MountMethod::SUPPORTED_OPTIONS
16
14
  clean_options = GraphqlDevise::MountMethod::OptionSanitizer.new(@options).call!
17
15
 
18
- return clean_options if GraphqlDevise.resource_mounted?(mapping_name) && @routing
16
+ model = if @resource.is_a?(String)
17
+ ActiveSupport::Deprecation.warn(<<-DEPRECATION.strip_heredoc, caller)
18
+ Providing a String as the model you want to mount is deprecated and will be removed in a future version of
19
+ this gem. Please use the actual model constant instead.
20
+
21
+ EXAMPLE
22
+
23
+ GraphqlDevise::ResourceLoader.new(User) # instead of GraphqlDevise::ResourceLoader.new('User')
24
+
25
+ mount_graphql_devise_for User # instead of mount_graphql_devise_for 'User'
26
+ DEPRECATION
27
+ @resource.constantize
28
+ else
29
+ @resource
30
+ end
31
+
32
+ # Necesary when mounting a resource via route file as Devise forces the reloading of routes
33
+ return clean_options if GraphqlDevise.resource_mounted?(model) && @routing
19
34
 
20
35
  validate_options!(clean_options)
21
36
 
@@ -23,7 +38,7 @@ module GraphqlDevise
23
38
  "Types::#{@resource}Type".safe_constantize ||
24
39
  GraphqlDevise::Types::AuthenticatableType
25
40
 
26
- prepared_mutations = prepare_mutations(mapping_name, clean_options, authenticatable_type)
41
+ prepared_mutations = prepare_mutations(model, clean_options, authenticatable_type)
27
42
 
28
43
  if prepared_mutations.any? && mutation.blank?
29
44
  raise GraphqlDevise::Error, 'You need to provide a mutation type unless all mutations are skipped'
@@ -33,7 +48,7 @@ module GraphqlDevise
33
48
  mutation.field(action, mutation: prepared_mutation, authenticate: false)
34
49
  end
35
50
 
36
- prepared_resolvers = prepare_resolvers(mapping_name, clean_options, authenticatable_type)
51
+ prepared_resolvers = prepare_resolvers(model, clean_options, authenticatable_type)
37
52
 
38
53
  if prepared_resolvers.any? && query.blank?
39
54
  raise GraphqlDevise::Error, 'You need to provide a query type unless all queries are skipped'
@@ -43,17 +58,17 @@ module GraphqlDevise
43
58
  query.field(action, resolver: resolver, authenticate: false)
44
59
  end
45
60
 
46
- GraphqlDevise.add_mapping(mapping_name, @resource)
47
- GraphqlDevise.mount_resource(mapping_name) if @routing
61
+ GraphqlDevise.add_mapping(GraphqlDevise.to_mapping_name(@resource).to_sym, @resource)
62
+ GraphqlDevise.mount_resource(model) if @routing
48
63
 
49
64
  clean_options
50
65
  end
51
66
 
52
67
  private
53
68
 
54
- def prepare_resolvers(mapping_name, clean_options, authenticatable_type)
69
+ def prepare_resolvers(model, clean_options, authenticatable_type)
55
70
  GraphqlDevise::MountMethod::OperationPreparer.new(
56
- mapping_name: mapping_name,
71
+ model: model,
57
72
  custom: clean_options.operations,
58
73
  additional_operations: clean_options.additional_queries,
59
74
  preparer: GraphqlDevise::MountMethod::OperationPreparers::ResolverTypeSetter.new(authenticatable_type),
@@ -63,9 +78,9 @@ module GraphqlDevise
63
78
  ).call
64
79
  end
65
80
 
66
- def prepare_mutations(mapping_name, clean_options, authenticatable_type)
81
+ def prepare_mutations(model, clean_options, authenticatable_type)
67
82
  GraphqlDevise::MountMethod::OperationPreparer.new(
68
- mapping_name: mapping_name,
83
+ model: model,
69
84
  custom: clean_options.operations,
70
85
  additional_operations: clean_options.additional_mutations,
71
86
  preparer: GraphqlDevise::MountMethod::OperationPreparers::MutationFieldSetter.new(authenticatable_type),
@@ -16,7 +16,6 @@ module GraphqlDevise
16
16
 
17
17
  # Must happen on initialize so operations are loaded before the types are added to the schema on GQL < 1.10
18
18
  load_fields
19
- reconfigure_warden!
20
19
  end
21
20
 
22
21
  def use(schema_definition)
@@ -31,9 +30,23 @@ module GraphqlDevise
31
30
  auth_required = authenticate_option(field, trace_data)
32
31
  context = context_from_data(trace_data)
33
32
 
33
+ if context.key?(:resource_name)
34
+ ActiveSupport::Deprecation.warn(<<-DEPRECATION.strip_heredoc, caller)
35
+ Providing `resource_name` as part of the GQL context, or doing so by using the `graphql_context(resource_name)`
36
+ method on your controller is deprecated and will be removed in a future version of this gem.
37
+ Please use `gql_devise_context` in you controller instead.
38
+
39
+ EXAMPLE
40
+ include GraphqlDevise::Concerns::SetUserByToken
41
+
42
+ DummySchema.execute(params[:query], context: gql_devise_context(User))
43
+ DummySchema.execute(params[:query], context: gql_devise_context(User, Admin))
44
+ DEPRECATION
45
+ end
46
+
34
47
  if auth_required && !(public_introspection && introspection_field?(field))
35
48
  context = set_current_resource(context)
36
- raise_on_missing_resource(context, field)
49
+ raise_on_missing_resource(context, field, auth_required)
37
50
  end
38
51
 
39
52
  yield
@@ -62,8 +75,12 @@ module GraphqlDevise
62
75
  context
63
76
  end
64
77
 
65
- def raise_on_missing_resource(context, field)
78
+ def raise_on_missing_resource(context, field, auth_required)
66
79
  @unauthenticated_proc.call(field.name) if context[:current_resource].blank?
80
+
81
+ if auth_required.respond_to?(:call) && !auth_required.call(context[:current_resource])
82
+ @unauthenticated_proc.call(field.name)
83
+ end
67
84
  end
68
85
 
69
86
  def context_from_data(trace_data)
@@ -102,11 +119,6 @@ module GraphqlDevise
102
119
  auth_required.nil? ? @authenticate_default : auth_required
103
120
  end
104
121
 
105
- def reconfigure_warden!
106
- Devise.class_variable_set(:@@warden_configured, nil)
107
- Devise.configure_warden!
108
- end
109
-
110
122
  def load_fields
111
123
  @resource_loaders.each do |resource_loader|
112
124
  raise Error, 'Invalid resource loader instance' unless resource_loader.instance_of?(GraphqlDevise::ResourceLoader)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphqlDevise
4
- VERSION = '0.14.3'.freeze
4
+ VERSION = '0.17.1'.freeze
5
5
  end
@@ -19,6 +19,17 @@ module Api
19
19
  render json: DummySchema.execute(params[:query], context: graphql_context([:user, :fail]))
20
20
  end
21
21
 
22
+ def controller_auth
23
+ result = DummySchema.execute(
24
+ params[:query],
25
+ operation_name: params[:operationName],
26
+ variables: ensure_hash(params[:variables]),
27
+ context: gql_devise_context(SchemaUser, User)
28
+ )
29
+
30
+ render json: result unless performed?
31
+ end
32
+
22
33
  private
23
34
 
24
35
  def execute_params(item)
@@ -7,17 +7,18 @@ class DummySchema < GraphQL::Schema
7
7
  public_introspection: true,
8
8
  resource_loaders: [
9
9
  GraphqlDevise::ResourceLoader.new(
10
- 'User',
10
+ User,
11
11
  only: [
12
12
  :login,
13
13
  :confirm_account,
14
14
  :send_password_reset,
15
15
  :resend_confirmation,
16
+ :resend_confirmation_with_token,
16
17
  :check_password_token
17
18
  ]
18
19
  ),
19
- GraphqlDevise::ResourceLoader.new('Guest', only: [:logout]),
20
- GraphqlDevise::ResourceLoader.new('SchemaUser')
20
+ GraphqlDevise::ResourceLoader.new(Guest, only: [:logout]),
21
+ GraphqlDevise::ResourceLoader.new(SchemaUser)
21
22
  ]
22
23
  )
23
24
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutations
4
+ class Register < GraphqlDevise::Mutations::Register
5
+ argument :name, String, required: false
6
+
7
+ field :user, Types::UserType, null: true
8
+
9
+ def resolve(email:, **attrs)
10
+ original_payload = super
11
+ original_payload.merge(user: original_payload[:authenticatable])
12
+ end
13
+ end
14
+ end