groovestack-auth 0.1.5 → 0.1.6
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 +4 -4
- data/Gemfile +8 -1
- data/Gemfile.lock +120 -20
- data/{lib/groovestack/auth/action_cable.rb → app/channels/groovestack/auth/action_cable/connection.rb} +2 -2
- data/app/controllers/concerns/groovestack/auth/graphql/controllers/auth_helpers.rb +69 -0
- data/app/controllers/concerns/groovestack/auth/graphql/controllers/authed_execute.rb +16 -0
- data/app/controllers/groovestack/auth/authenticated_api_controller.rb +10 -0
- data/app/controllers/groovestack/auth/omniauth_callbacks_controller.rb +138 -0
- data/app/controllers/groovestack/auth/passwordless/magic_links_controller.rb +58 -0
- data/app/controllers/groovestack/auth/passwordless/sessions_controller.rb +75 -0
- data/app/graphql/graphql/identity_extensions.rb +11 -0
- data/app/graphql/graphql/user_extensions.rb +14 -0
- data/app/models/concerns/groovestack/auth/authorized_fields_for_serialization.rb +21 -0
- data/app/models/concerns/groovestack/auth/identity.rb +39 -0
- data/app/models/concerns/groovestack/auth/user.rb +14 -0
- data/app/views/devise/mailer/magic_link.html.erb +9 -0
- data/config/initializers/core_config.rb +0 -6
- data/config/initializers/devise.rb +387 -302
- data/config/initializers/omniauth.rb +0 -19
- data/config/locales/devise.en.yml +71 -0
- data/db/migrate/20231103174050_add_devise_to_users_and_identities.rb +59 -0
- data/groovestack-auth.gemspec +7 -7
- data/lib/groovestack/auth/{railtie.rb → engine.rb} +13 -2
- data/lib/groovestack/auth/graphql/authorized_field.rb +19 -0
- data/lib/groovestack/auth/graphql/authorized_object.rb +11 -0
- data/lib/groovestack/auth/graphql/schema_visibility.rb +40 -0
- data/lib/groovestack/auth/graphql/visible_field.rb +21 -0
- data/lib/groovestack/auth/graphql/visible_object.rb +17 -0
- data/lib/groovestack/auth/passwordless/t_otp_tokenizer.rb +89 -0
- data/lib/groovestack/auth/provider.rb +7 -0
- data/lib/groovestack/auth/providers/apple.rb +5 -5
- data/lib/groovestack/auth/providers/facebook.rb +17 -0
- data/lib/groovestack/auth/providers/google.rb +1 -1
- data/lib/groovestack/auth/providers/omni_auth.rb +2 -2
- data/lib/groovestack/auth/routes.rb +26 -0
- data/lib/groovestack/auth/settings.rb +43 -0
- data/lib/groovestack/auth/version.rb +1 -1
- data/lib/groovestack/auth.rb +33 -83
- metadata +55 -50
- data/config/initializers/devise_token_auth.rb +0 -72
- data/config/initializers/graphql_devise.rb +0 -58
- data/config/routes.rb +0 -11
- data/db/migrate/20231103172517_create_users.rb +0 -54
- data/db/migrate/20231103174037_create_identities.rb +0 -19
- data/lib/fabricators/user_fabricator.rb +0 -17
- data/lib/graphql/identity/filter.rb +0 -13
- data/lib/graphql/identity/mutations.rb +0 -27
- data/lib/graphql/identity/queries.rb +0 -25
- data/lib/graphql/identity/type.rb +0 -22
- data/lib/graphql/user/filter.rb +0 -15
- data/lib/graphql/user/mutations.rb +0 -63
- data/lib/graphql/user/queries.rb +0 -40
- data/lib/graphql/user/type.rb +0 -30
- data/lib/groovestack/auth/authenticated_api_controller.rb +0 -13
- data/lib/groovestack/auth/omniauth_callbacks_controller.rb +0 -111
- data/lib/groovestack/auth/schema_plugin.rb +0 -19
- data/lib/identity.rb +0 -31
- data/lib/user.rb +0 -53
- data/lib/users/roles.rb +0 -42
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module User
|
5
|
-
module Mutations
|
6
|
-
class Update < ::Groovestack::Base::GraphQL::Base::Mutation
|
7
|
-
argument :current_password, String, required: false
|
8
|
-
argument :email, String, required: false
|
9
|
-
argument :id, ID, required: true
|
10
|
-
argument :image, String, required: false
|
11
|
-
argument :language, String, required: false
|
12
|
-
argument :name, String, required: false
|
13
|
-
argument :password, String, required: false
|
14
|
-
argument :roles, [String], required: false
|
15
|
-
|
16
|
-
type ::GraphQL::User::Type
|
17
|
-
|
18
|
-
def current_user
|
19
|
-
context[:current_resource]
|
20
|
-
end
|
21
|
-
|
22
|
-
def perform(id:, **attrs)
|
23
|
-
obj = id == current_user&.id ? current_user : ::User.find(id)
|
24
|
-
|
25
|
-
return update_with_password!(obj, **attrs) if attrs.keys.intersect?(%i[password current_password])
|
26
|
-
|
27
|
-
if !current_user.admin? && attrs[:roles].present?
|
28
|
-
raise GraphQL::ExecutionError,
|
29
|
-
'Validation Failed: only admins can update user roles'
|
30
|
-
end
|
31
|
-
|
32
|
-
obj.update!(attrs)
|
33
|
-
|
34
|
-
obj
|
35
|
-
end
|
36
|
-
|
37
|
-
def update_with_password!(user, **attrs) # rubocop:disable Metrics/AbcSize
|
38
|
-
unless current_user&.id == user.id
|
39
|
-
raise GraphQL::ExecutionError,
|
40
|
-
'Validation Failed: user can only update their own password'
|
41
|
-
end
|
42
|
-
|
43
|
-
::User.transaction do
|
44
|
-
# if password isn't set yet, allow them to set it
|
45
|
-
user.password = attrs[:current_password] = attrs[:password] unless user.email_provider?
|
46
|
-
|
47
|
-
raise GraphQL::ExecutionError, user.errors.full_messages.join(' ') unless user.update_with_password(attrs)
|
48
|
-
|
49
|
-
context[:bypass_sign_in].call user.reload, scope: :user
|
50
|
-
end
|
51
|
-
|
52
|
-
user
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
extend ActiveSupport::Concern
|
57
|
-
|
58
|
-
included do
|
59
|
-
field :update_user, mutation: ::GraphQL::User::Mutations::Update, description: 'update user'
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
data/lib/graphql/user/queries.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module User
|
5
|
-
module Queries
|
6
|
-
extend ActiveSupport::Concern
|
7
|
-
|
8
|
-
included do
|
9
|
-
include ::Groovestack::Base::GraphQL::Providers::ReactAdmin::Resource
|
10
|
-
|
11
|
-
react_admin_resource :users, graphql_path: 'GraphQL'
|
12
|
-
|
13
|
-
def User(id:) # rubocop:disable Naming/MethodName
|
14
|
-
id == 'me' ? current_user : ::User.find(id)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def current_user
|
19
|
-
context[:current_resource]
|
20
|
-
end
|
21
|
-
|
22
|
-
def users_scope(base_scope:, sort_field: nil, sort_order: nil, filter: {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
23
|
-
scope = base_scope
|
24
|
-
scope = scope.where(id: filter.ids) if filter.ids.present?
|
25
|
-
|
26
|
-
if filter.q.present?
|
27
|
-
scope = scope.where(id: scope.fuzzysearch(filter.q)).or(scope.where(id: scope.emailsearch(filter.q)))
|
28
|
-
end
|
29
|
-
scope = scope.fuzzysearch(filter.name) if filter.name.present?
|
30
|
-
scope = scope.with_roles(filter.roles) if filter.roles.present?
|
31
|
-
|
32
|
-
return scope if sort_field.blank?
|
33
|
-
|
34
|
-
sort_field = 'last_sign_in_at' if sort_field == 'last_login_at'
|
35
|
-
|
36
|
-
scope.order({ sort_field.underscore => sort_order || 'desc' })
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
data/lib/graphql/user/type.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module User
|
5
|
-
class Type < ::Groovestack::Base::GraphQL::Base::Object
|
6
|
-
include ::Groovestack::Base::GraphQL::Types::TypeResolver
|
7
|
-
|
8
|
-
description 'An user'
|
9
|
-
|
10
|
-
field :created_at, ::GraphQL::Types::ISO8601DateTime, null: false, description: 'created at'
|
11
|
-
field :id, ID, null: false, description: 'id'
|
12
|
-
field :updated_at, ::GraphQL::Types::ISO8601DateTime, null: false, description: 'updated at'
|
13
|
-
|
14
|
-
field :email, String, null: true, description: 'email'
|
15
|
-
field :has_email_provider, Boolean, null: true, description: 'user has email provider',
|
16
|
-
method: :email_provider?
|
17
|
-
field :image, String, null: true, description: 'user image url'
|
18
|
-
field :language, String, null: true, description: 'user language'
|
19
|
-
field :name, String, null: true, description: 'name'
|
20
|
-
field :roles, [String], null: true, description: 'roles'
|
21
|
-
|
22
|
-
# devise fields
|
23
|
-
field :last_login_at, ::GraphQL::Types::ISO8601DateTime, null: true, description: 'last login in at',
|
24
|
-
method: :last_sign_in_at
|
25
|
-
field :sign_in_count, Integer, null: true, description: 'sign in count'
|
26
|
-
|
27
|
-
field :identities, [::GraphQL::Identity::Type], null: false, description: 'identities'
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,13 +0,0 @@
|
|
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
|
@@ -1,111 +0,0 @@
|
|
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: Groovestack::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
|
@@ -1,19 +0,0 @@
|
|
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
|
data/lib/identity.rb
DELETED
@@ -1,31 +0,0 @@
|
|
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
DELETED
@@ -1,53 +0,0 @@
|
|
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
DELETED
@@ -1,42 +0,0 @@
|
|
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
|