Skip to content

Commit 35afbdd

Browse files
Merge pull request #19 from graphql-devise/support-skip-only-mount-options
Support `skip` and `only` when mounting routes
2 parents 292209c + ec97af1 commit 35afbdd

10 files changed

Lines changed: 372 additions & 117 deletions

File tree

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@ First, you need to mount the gem in the routes file like this
4141
Rails.application.routes.draw do
4242
mount_graphql_devise_for 'User', at: 'api/v1', authenticable_type: Types::CustomUserType, operations: {
4343
login: Mutations::Login
44-
}
44+
}, skip: [:sign_up]
45+
end
4546
```
4647
If you used DTA's installer you will have to remove the `mount_devise_token_auth_for`
4748
line.
4849

4950
Here are the option for the mount method:
5051

5152
1. `at`: Route where the GraphQL schema will be mounted on the Rails server. In the
52-
example your API will have this two routes `POST /api/v1//graphql_auth` `GET /api/v1//graphql_auth`.
53+
example your API will have this two routes `POST /api/v1/graphql_auth` and `GET /api/v1/graphql_auth`.
5354
If no this option is not specified, the schema will be mounted at `/graphql_auth`.
5455
1. `operations`: Specifying this one is optional. Here you can override default
5556
behavior by specifying your own mutations and queries for every GraphQL operation.
@@ -62,6 +63,23 @@ our default classes and yielding your customized code after calling `super`, exa
6263
and an `authenticable` type to every query. Gem will try to use `Types::<model>Type` by
6364
default, so in our example you could define `Types::UserType` and every query and mutation
6465
will use it. But, you can override this type with this option like in the example.
66+
1. `skip`: An array of the operations that should not be available in the authentication schema. All these operations are
67+
symbols and should belong to the list of available operations in the gem.
68+
1. `only`: An array of the operations that should be available in the authentication schema. The `skip` and `only` options are
69+
mutually exclusive, an error will be raised if you pass both to the mount method.
70+
71+
#### Available Operations
72+
The following is a list of the symbols you can provide to the `operations`, `skip` and `only` options of the mount method:
73+
```ruby
74+
:login
75+
:logout
76+
:sign_up
77+
:update_password
78+
:send_reset_password
79+
:confirm_account
80+
:check_password_token
81+
```
82+
6583

6684
### Configuring Model
6785
Just like with Devise and DTA, you need to include a module in your authenticable model,
@@ -145,8 +163,7 @@ templates.
145163
We will continue to improve the gem and add better docs.
146164

147165
1. Add install generator.
148-
1. Support more options on the mount method.
149-
1. Better support for multiple mounted models (it already works by mounting in different routes).
166+
1. Add mount option that will create a separate schema for the mounted resource.
150167
1. Make sure this gem can correctly work alongside DTA and the original Devise gem.
151168
1. Improve DOCS.
152169
1. Add support for unlockable and other Devise modules.

lib/graphql_devise/rails/routes.rb

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
11
module ActionDispatch::Routing
22
class Mapper
33
def mount_graphql_devise_for(resource, opts = {})
4-
custom_operations = opts[:operations] || {}
4+
custom_operations = opts[:operations] || {}
5+
skipped_operations = opts.fetch(:skip, [])
6+
only_operations = opts.fetch(:only, [])
7+
8+
if [skipped_operations, only_operations].all?(&:any?)
9+
raise GraphqlDevise::Error, "Can't specify both `skip` and `only` options when mounting the route."
10+
end
11+
12+
default_mutations = {
13+
login: GraphqlDevise::Mutations::Login,
14+
logout: GraphqlDevise::Mutations::Logout,
15+
sign_up: GraphqlDevise::Mutations::SignUp,
16+
update_password: GraphqlDevise::Mutations::UpdatePassword,
17+
send_reset_password: GraphqlDevise::Mutations::SendPasswordReset
18+
}.freeze
19+
default_queries = {
20+
confirm_account: GraphqlDevise::Resolvers::ConfirmAccount,
21+
check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken
22+
}
23+
supported_operations = default_mutations.keys + default_queries.keys
24+
25+
unless skipped_operations.all? { |skipped| supported_operations.include?(skipped) }
26+
raise GraphqlDevise::Error, 'Trying to skip a non supported operation. Check for typos.'
27+
end
28+
unless only_operations.all? { |only| supported_operations.include?(only) }
29+
raise GraphqlDevise::Error, 'One of the `only` operations is not supported. Check for typos.'
30+
end
531

632
path = opts.fetch(:at, '/graphql_auth')
733
mapping_name = resource.underscore.tr('/', '_').to_sym
@@ -16,15 +42,12 @@ def mount_graphql_devise_for(resource, opts = {})
1642
"Types::#{resource}Type".safe_constantize ||
1743
GraphqlDevise::Types::AuthenticableType
1844

19-
default_mutations = {
20-
login: GraphqlDevise::Mutations::Login,
21-
logout: GraphqlDevise::Mutations::Logout,
22-
sign_up: GraphqlDevise::Mutations::SignUp,
23-
update_password: GraphqlDevise::Mutations::UpdatePassword,
24-
send_reset_password: GraphqlDevise::Mutations::SendPasswordReset
25-
}.freeze
26-
27-
default_mutations.each do |action, mutation|
45+
used_mutations = if only_operations.present?
46+
default_mutations.slice(*only_operations)
47+
else
48+
default_mutations.except(*skipped_operations)
49+
end
50+
used_mutations.each do |action, mutation|
2851
used_mutation = if custom_operations[action].present?
2952
custom_operations[action]
3053
else
@@ -39,12 +62,12 @@ def mount_graphql_devise_for(resource, opts = {})
3962
GraphqlDevise::Types::MutationType.field("#{mapping_name}_#{action}", mutation: used_mutation)
4063
end
4164

42-
default_queries = {
43-
confirm_account: GraphqlDevise::Resolvers::ConfirmAccount,
44-
check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken
45-
}
46-
47-
default_queries.each do |action, query|
65+
used_queries = if only_operations.present?
66+
default_queries.slice(*only_operations)
67+
else
68+
default_queries.except(*skipped_operations)
69+
end
70+
used_queries.each do |action, query|
4871
used_query = if custom_operations[action].present?
4972
custom_operations[action]
5073
else

spec/dummy/app/models/guest.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Guest < ApplicationRecord
2+
devise :database_authenticatable,
3+
:registerable,
4+
:recoverable,
5+
:validatable,
6+
:confirmable
7+
8+
include GraphqlDevise::Concerns::Model
9+
end

spec/dummy/config/routes.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@
77
mount_graphql_devise_for(
88
'Admin',
99
authenticable_type: Types::CustomAdminType,
10+
skip: [:sign_up, :check_password_token],
1011
at: '/api/v1/admin/graphql_auth'
1112
)
1213

14+
mount_graphql_devise_for(
15+
'Guest',
16+
only: [:login, :logout],
17+
at: '/api/v1/guest/graphql_auth'
18+
)
19+
1320
post '/api/v1/graphql', to: 'api/v1/graphql#graphql'
1421
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class CreateGuests < ActiveRecord::Migration[6.0]
2+
def change
3+
create_table :guests do |t|
4+
## Required
5+
t.string :provider, null: false, default: 'email'
6+
t.string :uid, null: false, default: ''
7+
8+
## Database authenticatable
9+
t.string :encrypted_password, null: false, default: ''
10+
11+
## Recoverable
12+
t.string :reset_password_token
13+
t.datetime :reset_password_sent_at
14+
t.boolean :allow_password_change, default: false
15+
16+
## Confirmable
17+
t.string :confirmation_token
18+
t.datetime :confirmed_at
19+
t.datetime :confirmation_sent_at
20+
t.string :unconfirmed_email # Only if using reconfirmable
21+
22+
## User Info
23+
t.string :email
24+
25+
## Tokens
26+
t.text :tokens
27+
28+
t.timestamps
29+
end
30+
31+
add_index :guests, :email, unique: true
32+
add_index :guests, [:uid, :provider], unique: true
33+
add_index :guests, :reset_password_token, unique: true
34+
add_index :guests, :confirmation_token, unique: true
35+
end
36+
end

spec/dummy/db/schema.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2019_09_16_012505) do
13+
ActiveRecord::Schema.define(version: 2019_10_13_213045) do
1414

1515
create_table "admins", force: :cascade do |t|
1616
t.string "provider", default: "email", null: false
@@ -33,6 +33,27 @@
3333
t.index ["uid", "provider"], name: "index_admins_on_uid_and_provider", unique: true
3434
end
3535

36+
create_table "guests", force: :cascade do |t|
37+
t.string "provider", default: "email", null: false
38+
t.string "uid", default: "", null: false
39+
t.string "encrypted_password", default: "", null: false
40+
t.string "reset_password_token"
41+
t.datetime "reset_password_sent_at"
42+
t.boolean "allow_password_change", default: false
43+
t.string "confirmation_token"
44+
t.datetime "confirmed_at"
45+
t.datetime "confirmation_sent_at"
46+
t.string "unconfirmed_email"
47+
t.string "email"
48+
t.text "tokens"
49+
t.datetime "created_at", precision: 6, null: false
50+
t.datetime "updated_at", precision: 6, null: false
51+
t.index ["confirmation_token"], name: "index_guests_on_confirmation_token", unique: true
52+
t.index ["email"], name: "index_guests_on_email", unique: true
53+
t.index ["reset_password_token"], name: "index_guests_on_reset_password_token", unique: true
54+
t.index ["uid", "provider"], name: "index_guests_on_uid_and_provider", unique: true
55+
end
56+
3657
create_table "users", force: :cascade do |t|
3758
t.string "provider", default: "email", null: false
3859
t.string "uid", default: "", null: false

spec/factories/guests.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FactoryBot.define do
2+
factory :guest do
3+
email { Faker::Internet.unique.email }
4+
password { Faker::Internet.password }
5+
6+
trait :confirmed do
7+
confirmed_at { Time.now }
8+
end
9+
end
10+
end

spec/requests/mutations/login_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,28 @@
114114
)
115115
end
116116
end
117+
118+
context 'when using the guest model' do
119+
let(:guest) { create(:guest, :confirmed, password: password) }
120+
let(:query) do
121+
<<-GRAPHQL
122+
mutation {
123+
guestLogin(
124+
email: "#{guest.email}",
125+
password: "#{password}"
126+
) {
127+
authenticable { email }
128+
}
129+
}
130+
GRAPHQL
131+
end
132+
133+
before { post_request }
134+
135+
it 'works alongside the user mount point' do
136+
expect(json_response[:data][:guestLogin]).to include(
137+
authenticable: { email: guest.email }
138+
)
139+
end
140+
end
117141
end

0 commit comments

Comments
 (0)