groovestack-auth 0.1.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/.rspec +1 -0
- data/.rubocop.yml +51 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +194 -0
- data/config/initializers/core_config.rb +41 -0
- data/config/initializers/devise.rb +314 -0
- data/config/initializers/devise_token_auth.rb +72 -0
- data/config/initializers/graphql_devise.rb +58 -0
- data/config/initializers/omniauth.rb +69 -0
- data/config/routes.rb +11 -0
- data/config.ru +12 -0
- data/db/migrate/20231103172517_create_users.rb +54 -0
- data/db/migrate/20231103174037_create_identities.rb +19 -0
- data/docs/apple-oauth-local-dev.adoc +67 -0
- data/groovestack-auth.gemspec +41 -0
- data/lib/fabricators/user_fabricator.rb +17 -0
- data/lib/graphql/identity/filter.rb +13 -0
- data/lib/graphql/identity/mutations.rb +27 -0
- data/lib/graphql/identity/queries.rb +25 -0
- data/lib/graphql/identity/type.rb +22 -0
- data/lib/graphql/user/filter.rb +15 -0
- data/lib/graphql/user/mutations.rb +63 -0
- data/lib/graphql/user/queries.rb +40 -0
- data/lib/graphql/user/type.rb +30 -0
- data/lib/groovestack/auth/action_cable.rb +29 -0
- data/lib/groovestack/auth/authenticated_api_controller.rb +13 -0
- data/lib/groovestack/auth/omniauth_callbacks_controller.rb +111 -0
- data/lib/groovestack/auth/provider.rb +59 -0
- data/lib/groovestack/auth/providers/apple.rb +26 -0
- data/lib/groovestack/auth/providers/email.rb +15 -0
- data/lib/groovestack/auth/providers/google.rb +17 -0
- data/lib/groovestack/auth/providers/omni_auth.rb +47 -0
- data/lib/groovestack/auth/railtie.rb +64 -0
- data/lib/groovestack/auth/schema_plugin.rb +19 -0
- data/lib/groovestack/auth/version.rb +7 -0
- data/lib/groovestack/auth.rb +108 -0
- data/lib/identity.rb +31 -0
- data/lib/user.rb +53 -0
- data/lib/users/roles.rb +42 -0
- data/readme.adoc +52 -0
- metadata +144 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module ActionCable
|
6
|
+
module Connection
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
identified_by :current_resource
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect
|
14
|
+
self.current_resource = find_verified_resource
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def find_verified_resource
|
20
|
+
if (verified_user = env['warden'].user)
|
21
|
+
verified_user
|
22
|
+
else
|
23
|
+
reject_unauthorized_connection
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'devise_token_auth/concerns/resource_finder'
|
4
|
+
require 'devise_token_auth/concerns/set_user_by_token'
|
5
|
+
|
6
|
+
module Groovestack
|
7
|
+
module Auth
|
8
|
+
class AuthenticatedApiController < ApplicationController
|
9
|
+
include GraphqlDevise::SetUserByToken
|
10
|
+
include Devise::Controllers::Helpers
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'devise_helper'
|
4
|
+
require 'devise_controller'
|
5
|
+
require 'devise_token_auth/concerns/resource_finder'
|
6
|
+
require 'devise_token_auth/concerns/set_user_by_token'
|
7
|
+
require 'devise_token_auth/application_controller'
|
8
|
+
require 'devise_token_auth/omniauth_callbacks_controller'
|
9
|
+
|
10
|
+
module Groovestack
|
11
|
+
module Auth
|
12
|
+
class OmniauthCallbacksController < DeviseTokenAuth::OmniauthCallbacksController
|
13
|
+
def verified_request?
|
14
|
+
# required b/c apple uses POST to callback and resets the origin header
|
15
|
+
# source: https://github.com/rails/rails/blob/6b93fff8af32ef5e91f4ec3cfffb081d0553faf0/actionpack/lib/action_controller/metal/request_forgery_protection.rb#L442C11-L442C27
|
16
|
+
|
17
|
+
apple_success_callback_request? || super
|
18
|
+
end
|
19
|
+
|
20
|
+
def apple_success_callback_request?
|
21
|
+
params[:action] == 'verified' && params[:provider] == 'apple' && request.headers['Origin'] == 'https://appleid.apple.com'
|
22
|
+
end
|
23
|
+
|
24
|
+
def auth_hash
|
25
|
+
@auth_hash ||= request.env['omniauth.auth'] # goes through plain old omniauth so need to override
|
26
|
+
end
|
27
|
+
|
28
|
+
def resource_class(_mapping = nil)
|
29
|
+
# TODO: this is required b/c their code relies on the resource_class
|
30
|
+
# method which expects access to the 'dta.omniauth.params' which we
|
31
|
+
# don't currently have access through b/c we are going directly
|
32
|
+
# through omniauth
|
33
|
+
|
34
|
+
return @resource_class if defined?(@resource_class)
|
35
|
+
|
36
|
+
raise 'No resource_class found' if @resource.blank?
|
37
|
+
|
38
|
+
@resource_class = @resource.class
|
39
|
+
|
40
|
+
@resource_class
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_auth_params
|
44
|
+
@auth_params = {
|
45
|
+
auth_token: @token.token,
|
46
|
+
client_id: @token.client,
|
47
|
+
# uid: @resource.uid,
|
48
|
+
expiry: @token.expiry,
|
49
|
+
config: @config,
|
50
|
+
id: @resource.id
|
51
|
+
}
|
52
|
+
@auth_params.merge!(oauth_registration: true) if @oauth_registration
|
53
|
+
@auth_params
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_resource_from_auth_hash # rubocop:disable Naming/AccessorMethodName
|
57
|
+
# invitation_token = request.env.dig('omniauth.params', 'invitation_token')
|
58
|
+
language = request.env.dig('omniauth.params', 'language')
|
59
|
+
|
60
|
+
c_user = begin
|
61
|
+
current_user
|
62
|
+
rescue StandardError
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
identity_params = {
|
67
|
+
auth: auth_hash,
|
68
|
+
current_user: c_user,
|
69
|
+
user_attrs: {
|
70
|
+
defaults: {
|
71
|
+
roles: Core::Config::App.generate_config[:has_admins] ? [] : [Users::Roles::Role::ADMIN]
|
72
|
+
},
|
73
|
+
priority: {
|
74
|
+
language: language
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
@resource = Identity.find_or_create_from_omniauth!(**identity_params).user
|
80
|
+
|
81
|
+
@resource
|
82
|
+
end
|
83
|
+
|
84
|
+
def verified # rubocop:disable Metrics/AbcSize
|
85
|
+
omniauth_success do
|
86
|
+
set_token_in_cookie(@resource, @token)
|
87
|
+
|
88
|
+
redirect_url = if redirect_options.present? || request.env['omniauth.origin'].split('?').size > 1
|
89
|
+
DeviseTokenAuth::Url.generate(request.env['omniauth.origin'], redirect_options)
|
90
|
+
else
|
91
|
+
request.env['omniauth.origin'].split('?').first # get rid of occasional trailing ?
|
92
|
+
end
|
93
|
+
|
94
|
+
redirect_to redirect_url
|
95
|
+
|
96
|
+
return
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def omniauth_failure
|
101
|
+
@error = params[:message]
|
102
|
+
# render_data_or_redirect('authFailure', omniauth_failure_error: @error)
|
103
|
+
|
104
|
+
::Groovestack::Base.notify_error('Groovestack::Auth::OmniauthCallbacksController.omniauth_failure', @error)
|
105
|
+
|
106
|
+
data = { omniauth_failure_error: @error }.merge(redirect_options)
|
107
|
+
redirect_to DeviseTokenAuth::Url.generate(session['omniauth.origin'], data)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
def self.available_providers(ancestor: nil)
|
6
|
+
# ensure all providers are loaded
|
7
|
+
::Groovestack::Auth::Providers.eager_load!
|
8
|
+
|
9
|
+
root = ancestor || ::Groovestack::Auth::Provider
|
10
|
+
|
11
|
+
root.descendants.select(&:available?)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.enabled_providers(ancestor: nil)
|
15
|
+
available_providers(ancestor: ancestor).select(&:enabled?)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configured_providers(ancestor: nil)
|
19
|
+
enabled_providers(ancestor: ancestor).select(&:configured?)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.enabled_providers_sans_configuration(ancestor: nil)
|
23
|
+
enabled_providers(ancestor: ancestor) - configured_providers(ancestor: ancestor)
|
24
|
+
end
|
25
|
+
|
26
|
+
class Provider
|
27
|
+
def self.provider
|
28
|
+
const_defined?(:PROVIDER) ? self::PROVIDER : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.k
|
32
|
+
const_defined?(:K) ? self::K : provider
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.available?
|
36
|
+
provider.present?
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.enabled?
|
40
|
+
available? && ::Groovestack::Auth.disabled_providers.exclude?(provider)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.configured?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.as_json(keys = nil)
|
48
|
+
verbose = {
|
49
|
+
provider: provider,
|
50
|
+
k: k || provider
|
51
|
+
}
|
52
|
+
|
53
|
+
return verbose if keys.nil?
|
54
|
+
|
55
|
+
verbose.slice(*keys)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module Providers
|
6
|
+
class Apple < OmniAuth
|
7
|
+
PROVIDER = :apple
|
8
|
+
REQUIRED_CREDENTIALS = %i[APPLE_CLIENT_ID APPLE_TEAM_ID APPLE_KEY_ID APPLE_PEM_CONTENT].freeze
|
9
|
+
|
10
|
+
def self.generate_omniauth_args
|
11
|
+
[
|
12
|
+
provider,
|
13
|
+
Rails.application.credentials.APPLE_CLIENT_ID,
|
14
|
+
'',
|
15
|
+
{
|
16
|
+
team_id: Rails.application.credentials.APPLE_TEAM_ID,
|
17
|
+
key_id: Rails.application.credentials.APPLE_KEY_ID,
|
18
|
+
pem: Rails.application.credentials.APPLE_PEM_CONTENT,
|
19
|
+
origin_param: 'return_to'
|
20
|
+
}
|
21
|
+
]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module Providers
|
6
|
+
class Google < OmniAuth
|
7
|
+
PROVIDER = :google_oauth2
|
8
|
+
K = :google
|
9
|
+
REQUIRED_CREDENTIALS = %i[GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET].freeze
|
10
|
+
|
11
|
+
def self.generate_omniauth_args
|
12
|
+
super << { name: k, origin_param: 'return_to' }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module Providers
|
6
|
+
class OmniAuth < Groovestack::Auth::Provider
|
7
|
+
BASE_PATH = '/users/auth'
|
8
|
+
|
9
|
+
def self.required_credentials
|
10
|
+
const_defined?(:REQUIRED_CREDENTIALS) ? self::REQUIRED_CREDENTIALS : []
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.required_credentials_present?
|
14
|
+
required_credentials.all? { |credential| Rails.application.credentials.send(credential).present? }
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.configured?
|
18
|
+
required_credentials_present?
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.generate_omniauth_args
|
22
|
+
# different providers require different arguments
|
23
|
+
[
|
24
|
+
provider,
|
25
|
+
*required_credentials.map { |c| Rails.application.credentials.send(c) }
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.path
|
30
|
+
"#{BASE_PATH}/#{k}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.as_json(keys = nil)
|
34
|
+
verbose = super.merge({
|
35
|
+
required_credentials: required_credentials,
|
36
|
+
path: path,
|
37
|
+
generate_omniauth_args: generate_omniauth_args
|
38
|
+
})
|
39
|
+
|
40
|
+
return verbose if keys.nil?
|
41
|
+
|
42
|
+
verbose.slice(*keys)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'provider'
|
4
|
+
|
5
|
+
if defined?(Rails)
|
6
|
+
module Groovestack
|
7
|
+
module Auth
|
8
|
+
class Railtie < ::Rails::Engine
|
9
|
+
include ::Groovestack::Base::CoreRailtie
|
10
|
+
|
11
|
+
def dx_validations
|
12
|
+
[
|
13
|
+
{
|
14
|
+
eval: proc { raise unless defined?(::Groovestack::Base) },
|
15
|
+
message: "Error: 'core-base' gem is required, add it your your gemfile"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
eval: proc { raise unless defined?(::Core::Config) },
|
19
|
+
message: "Error: 'core-config' gem is required, add it your your gemfile"
|
20
|
+
},
|
21
|
+
{
|
22
|
+
eval: proc {
|
23
|
+
raise if ::Groovestack::Auth.enabled_providers_sans_configuration.present?
|
24
|
+
},
|
25
|
+
message: missing_configuration_msg
|
26
|
+
}
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
def providers_missing_configuration
|
31
|
+
::Groovestack::Auth.enabled_providers_sans_configuration.map do |h|
|
32
|
+
"#{h.provider.to_s.titleize} - #{h.required_credentials.join(', ')}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def missing_configuration_msg
|
37
|
+
msg = "\n\tWarning: enabled providers are missing required credentials:\n\t\t"
|
38
|
+
msg += providers_missing_configuration.join("\n\t\t")
|
39
|
+
"#{msg}\n\tAdd them to your credentials file or disable the providers by adding them to Groovestack::Auth.disabled_providers." # rubocop:disable Layout/LineLength
|
40
|
+
end
|
41
|
+
|
42
|
+
def module_description
|
43
|
+
avail = ::Groovestack::Auth.available_providers.map(&:k).map(&:to_s).map(&:titleize)
|
44
|
+
descr = "\n\tAvailable auth providers: #{avail.join(', ')}"
|
45
|
+
enabled = ::Groovestack::Auth.enabled_providers.map(&:k).map(&:to_s).map(&:titleize)
|
46
|
+
descr += "\n\tEnabled auth providers: #{enabled.join(', ')}"
|
47
|
+
descr
|
48
|
+
end
|
49
|
+
|
50
|
+
initializer :append_migrations do |app|
|
51
|
+
append_migrations app
|
52
|
+
end
|
53
|
+
|
54
|
+
initializer :append_initializers do |app|
|
55
|
+
append_initializers app
|
56
|
+
end
|
57
|
+
|
58
|
+
config.after_initialize do
|
59
|
+
after_init
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module SchemaPlugin
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
use GraphqlDevise::SchemaPlugin.new(
|
10
|
+
query: Types::QueryType,
|
11
|
+
mutation: Types::MutationType,
|
12
|
+
resource_loaders: [
|
13
|
+
GraphqlDevise::ResourceLoader.new(User)
|
14
|
+
]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'groovestack/base'
|
4
|
+
|
5
|
+
require 'graphql_devise' if defined?(GraphqlDevise)
|
6
|
+
require 'omniauth-google-oauth2'
|
7
|
+
require 'omniauth-apple'
|
8
|
+
|
9
|
+
require 'groovestack/auth/version'
|
10
|
+
require 'groovestack/auth/railtie' if defined?(Rails::Railtie)
|
11
|
+
|
12
|
+
if defined?(GraphqlDevise)
|
13
|
+
# add devise and devise_token_auth app/ dirs to load path
|
14
|
+
Dir[File.join(Gem::Specification.find_by_name('devise').gem_dir, 'app', '*')].each do |sub_dir|
|
15
|
+
$LOAD_PATH.push(sub_dir)
|
16
|
+
end
|
17
|
+
Dir[File.join(Gem::Specification.find_by_name('devise_token_auth').gem_dir, 'app', '*')].each do |sub_dir|
|
18
|
+
$LOAD_PATH.push(sub_dir)
|
19
|
+
end
|
20
|
+
|
21
|
+
unless defined?(ApplicationController)
|
22
|
+
class ApplicationController < ActionController::Base
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
unless defined?(DeviseTokenAuth::Concerns)
|
27
|
+
module DeviseTokenAuth
|
28
|
+
module Concerns
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'users/roles'
|
35
|
+
require 'user'
|
36
|
+
require 'identity'
|
37
|
+
|
38
|
+
module GraphQL
|
39
|
+
module Identity
|
40
|
+
autoload :Type, 'graphql/identity/type'
|
41
|
+
autoload :Filter, 'graphql/identity/filter'
|
42
|
+
autoload :Queries, 'graphql/identity/queries'
|
43
|
+
autoload :Mutations, 'graphql/identity/mutations'
|
44
|
+
end
|
45
|
+
|
46
|
+
module User
|
47
|
+
autoload :Filter, 'graphql/user/filter'
|
48
|
+
autoload :Type, 'graphql/user/type'
|
49
|
+
autoload :Queries, 'graphql/user/queries'
|
50
|
+
autoload :Mutations, 'graphql/user/mutations'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Groovestack
|
55
|
+
module Auth
|
56
|
+
autoload :Provider, 'groovestack/auth/provider'
|
57
|
+
|
58
|
+
if defined?(GraphqlDevise)
|
59
|
+
autoload :AuthenticatedApiController, 'groovestack/auth/authenticated_api_controller'
|
60
|
+
autoload :OmniauthCallbacksController, 'groovestack/auth/omniauth_callbacks_controller'
|
61
|
+
autoload :ActionCable, 'groovestack/auth/action_cable'
|
62
|
+
autoload :SchemaPlugin, 'groovestack/auth/schema_plugin'
|
63
|
+
end
|
64
|
+
|
65
|
+
module Providers
|
66
|
+
extend ActiveSupport::Autoload
|
67
|
+
|
68
|
+
eager_autoload do
|
69
|
+
autoload :Email, 'groovestack/auth/providers/email'
|
70
|
+
autoload :OmniAuth, 'groovestack/auth/providers/omni_auth'
|
71
|
+
autoload :Apple, 'groovestack/auth/providers/apple'
|
72
|
+
autoload :Google, 'groovestack/auth/providers/google'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
extend Dry::Configurable
|
77
|
+
|
78
|
+
setting :disabled_providers, default: [], reader: true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
require 'fabricators/user_fabricator' if defined?(Fabrication) && defined?(Faker)
|
83
|
+
|
84
|
+
# TODO: remove aliases after all core modules have been updated
|
85
|
+
# to reference Groovestack::Auth
|
86
|
+
module Core
|
87
|
+
module Auth
|
88
|
+
Provider = ::Groovestack::Auth::Provider
|
89
|
+
|
90
|
+
if defined?(GraphqlDevise)
|
91
|
+
AuthenticatedApiController = ::Groovestack::Auth::AuthenticatedApiController
|
92
|
+
OmniauthCallbacksController = ::Groovestack::Auth::OmniauthCallbacksController
|
93
|
+
ActionCable = ::Groovestack::Auth::ActionCable
|
94
|
+
SchemaPlugin = ::Groovestack::Auth::SchemaPlugin
|
95
|
+
end
|
96
|
+
|
97
|
+
module Providers
|
98
|
+
Email = ::Groovestack::Auth::Providers::Email
|
99
|
+
OmniAuth = ::Groovestack::Auth::Providers::OmniAuth
|
100
|
+
Apple = ::Groovestack::Auth::Providers::Apple
|
101
|
+
Google = ::Groovestack::Auth::Providers::Google
|
102
|
+
end
|
103
|
+
|
104
|
+
extend Dry::Configurable
|
105
|
+
|
106
|
+
setting :disabled_providers, default: [], reader: true
|
107
|
+
end
|
108
|
+
end
|
data/lib/identity.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Identity < ActiveRecord::Base
|
4
|
+
belongs_to :user
|
5
|
+
|
6
|
+
def self.find_or_create_from_omniauth!(auth:, current_user: nil, user_attrs: {}) # rubocop:disable Metrics/AbcSize
|
7
|
+
where(provider: auth.provider, uid: auth.uid).first_or_create! do |identity|
|
8
|
+
# TODO
|
9
|
+
# possible cases
|
10
|
+
# 1. user exists in the system (i.e. we found an email for them)
|
11
|
+
# 2. user does not exist in the system (i.e. oauth email doesn't match anything in the system
|
12
|
+
|
13
|
+
user = current_user || User.find_by(email: auth.info.email)
|
14
|
+
user_attrs_to_assign = user_attrs[:priority] || {}
|
15
|
+
|
16
|
+
if user.nil?
|
17
|
+
user_attrs_to_assign = user_attrs_to_assign.merge(user_attrs[:defaults] || {})
|
18
|
+
user = User.new
|
19
|
+
end
|
20
|
+
|
21
|
+
attrs = auth['info'].to_hash.slice(*user.attribute_names)
|
22
|
+
user.assign_attributes(attrs.merge(user_attrs_to_assign))
|
23
|
+
|
24
|
+
# user.skip_confirmation!
|
25
|
+
user.save!
|
26
|
+
|
27
|
+
identity.omniauth_data = auth
|
28
|
+
identity.user = user
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/user.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jsonb_accessor'
|
4
|
+
|
5
|
+
class User < ActiveRecord::Base
|
6
|
+
include Users::Roles
|
7
|
+
|
8
|
+
if defined?(Devise)
|
9
|
+
extend ::Devise::Models
|
10
|
+
|
11
|
+
# Include default devise modules. Others available are:
|
12
|
+
# :confirmable, :lockable, :timeoutable
|
13
|
+
|
14
|
+
devise :database_authenticatable, :registerable,
|
15
|
+
:recoverable, :rememberable, :trackable, :validatable
|
16
|
+
end
|
17
|
+
|
18
|
+
has_many :identities, dependent: :destroy
|
19
|
+
|
20
|
+
scope :fuzzysearch, ->(q) { where('name::text ilike ?', "%#{q}%".gsub(/\s/, '%').squeeze('%')) }
|
21
|
+
scope :emailsearch, ->(qemail) { where('email::text ilike ?', "#{qemail}%") }
|
22
|
+
|
23
|
+
# GraphqlDevise overrides (due to omniauthable relying on new Identity model)
|
24
|
+
# NOTE: DeviseTokenAuth User.provider & User.uid columns are removed
|
25
|
+
# TODO: can we remove this overrides?
|
26
|
+
|
27
|
+
def provider
|
28
|
+
# will probably need to pick from identities eventually
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def uid
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def email_provider?
|
37
|
+
encrypted_password.present?
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_auth_headers(token, client = 'default') # rubocop:disable Metrics/AbcSize
|
41
|
+
# client may use expiry to prevent validation request if expired
|
42
|
+
# must be cast as string or headers will break
|
43
|
+
expiry = tokens[client]['expiry'] || tokens[client][:expiry]
|
44
|
+
headers = {
|
45
|
+
DeviseTokenAuth.headers_names[:'access-token'] => token,
|
46
|
+
DeviseTokenAuth.headers_names[:'token-type'] => 'Bearer',
|
47
|
+
DeviseTokenAuth.headers_names[:client] => client,
|
48
|
+
DeviseTokenAuth.headers_names[:expiry] => expiry.to_s,
|
49
|
+
DeviseTokenAuth.headers_names[:id] => id
|
50
|
+
}
|
51
|
+
headers.merge(build_bearer_token(headers))
|
52
|
+
end
|
53
|
+
end
|
data/lib/users/roles.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Users
|
4
|
+
module Roles
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
scope :with_roles, ->(roles) { jsonb_contains(:roles, roles) }
|
9
|
+
scope :admins, -> { with_roles(:admin) }
|
10
|
+
|
11
|
+
module Role
|
12
|
+
ADMIN = 'admin'
|
13
|
+
end
|
14
|
+
|
15
|
+
ROLES = [
|
16
|
+
Role::ADMIN
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
def add_roles(roles)
|
20
|
+
self.roles |= roles
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_role(role)
|
24
|
+
add_roles([role])
|
25
|
+
end
|
26
|
+
|
27
|
+
def admin!
|
28
|
+
add_role(User::Role::ADMIN)
|
29
|
+
save!
|
30
|
+
end
|
31
|
+
|
32
|
+
def admin?
|
33
|
+
roles.include? User::Role::ADMIN
|
34
|
+
end
|
35
|
+
|
36
|
+
# TODO: rname role?
|
37
|
+
def has_role?(role) # rubocop:disable Naming/PredicateName
|
38
|
+
roles.include? role.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|