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,24 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
Rails.application.config.middleware.use OmniAuth::Builder do
|
4
|
-
Groovestack::Auth.configured_providers(ancestor: Groovestack::Auth::Providers::OmniAuth).each do |p|
|
5
|
-
provider(*p.generate_omniauth_args)
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
module Groovestack
|
10
|
-
module Auth
|
11
|
-
class OmniauthFailureEndpoint < OmniAuth::FailureEndpoint
|
12
|
-
def call
|
13
|
-
# raise_out! if OmniAuth.config.failure_raise_out_environments.include?(ENV['RACK_ENV'].to_s)
|
14
|
-
redirect_to_failure
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
OmniAuth.config.on_failure = Groovestack::Auth::OmniauthFailureEndpoint
|
21
|
-
|
22
3
|
module OmniAuth
|
23
4
|
module Strategies
|
24
5
|
class Apple < OmniAuth::Strategies::OAuth2
|
@@ -0,0 +1,71 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
devise:
|
4
|
+
confirmations:
|
5
|
+
confirmed: "Your email address has been successfully confirmed."
|
6
|
+
send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
|
7
|
+
send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
|
8
|
+
failure:
|
9
|
+
already_authenticated: "You are already signed in."
|
10
|
+
inactive: "Your account is not activated yet."
|
11
|
+
invalid: "Invalid %{authentication_keys} or password."
|
12
|
+
locked: "Your account is locked."
|
13
|
+
last_attempt: "You have one more attempt before your account is locked."
|
14
|
+
not_found_in_database: "Invalid %{authentication_keys} or password."
|
15
|
+
timeout: "Your session expired. Please sign in again to continue."
|
16
|
+
unauthenticated: "You need to sign in or sign up before continuing."
|
17
|
+
unconfirmed: "You have to confirm your email address before continuing."
|
18
|
+
magic_link_invalid: "Invalid or expired login link."
|
19
|
+
mailer:
|
20
|
+
confirmation_instructions:
|
21
|
+
subject: "Confirmation instructions"
|
22
|
+
reset_password_instructions:
|
23
|
+
subject: "Reset password instructions"
|
24
|
+
unlock_instructions:
|
25
|
+
subject: "Unlock instructions"
|
26
|
+
email_changed:
|
27
|
+
subject: "Email Changed"
|
28
|
+
password_change:
|
29
|
+
subject: "Password Changed"
|
30
|
+
magic_link:
|
31
|
+
subject: "Here's your magic login link ✨"
|
32
|
+
omniauth_callbacks:
|
33
|
+
failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
|
34
|
+
success: "Successfully authenticated from %{kind} account."
|
35
|
+
passwords:
|
36
|
+
no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
|
37
|
+
send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
|
38
|
+
send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
|
39
|
+
updated: "Your password has been changed successfully. You are now signed in."
|
40
|
+
updated_not_active: "Your password has been changed successfully."
|
41
|
+
registrations:
|
42
|
+
destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
|
43
|
+
signed_up: "Welcome! You have signed up successfully."
|
44
|
+
signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
|
45
|
+
signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
|
46
|
+
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
|
47
|
+
update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address."
|
48
|
+
updated: "Your account has been updated successfully."
|
49
|
+
updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again."
|
50
|
+
sessions:
|
51
|
+
signed_in: "Signed in successfully."
|
52
|
+
signed_out: "Signed out successfully."
|
53
|
+
already_signed_out: "Signed out successfully."
|
54
|
+
unlocks:
|
55
|
+
send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
|
56
|
+
send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
|
57
|
+
unlocked: "Your account has been unlocked successfully. Please sign in to continue."
|
58
|
+
passwordless:
|
59
|
+
not_found_in_database: "Could not find a user for that email address"
|
60
|
+
magic_link_sent: "A login link has been sent to your email address. Please follow the link to log in to your account."
|
61
|
+
magic_link_sent_paranoid: "If your account exists, you will receive an email with a login link. Please follow the link to log in to your account."
|
62
|
+
errors:
|
63
|
+
messages:
|
64
|
+
already_confirmed: "was already confirmed, please try signing in"
|
65
|
+
confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
|
66
|
+
expired: "has expired, please request a new one"
|
67
|
+
not_found: "not found"
|
68
|
+
not_locked: "was not locked"
|
69
|
+
not_saved:
|
70
|
+
one: "1 error prohibited this %{resource} from being saved:"
|
71
|
+
other: "%{count} errors prohibited this %{resource} from being saved:"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddDeviseToUsersAndIdentities < ActiveRecord::Migration[7.0]
|
4
|
+
def change # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
5
|
+
# Add authentication columns to the existing users table
|
6
|
+
change_column_default :users, :email, from: nil, to: ''
|
7
|
+
change_column_null :users, :email, false
|
8
|
+
|
9
|
+
change_table :users do |t|
|
10
|
+
## Database authenticatable
|
11
|
+
if Groovestack::Auth.devise_modules.include?(:database_authenticatable)
|
12
|
+
t.string :encrypted_password, null: false,
|
13
|
+
default: ''
|
14
|
+
end
|
15
|
+
|
16
|
+
## Recoverable
|
17
|
+
if Groovestack::Auth.devise_modules.include?(:recoverable)
|
18
|
+
t.string :reset_password_token
|
19
|
+
t.datetime :reset_password_sent_at
|
20
|
+
end
|
21
|
+
|
22
|
+
## Rememberable
|
23
|
+
t.datetime :remember_created_at if Groovestack::Auth.devise_modules.include?(:rememberable)
|
24
|
+
|
25
|
+
## Trackable
|
26
|
+
if Groovestack::Auth.devise_modules.include?(:trackable)
|
27
|
+
t.integer :sign_in_count, default: 0, null: false
|
28
|
+
t.datetime :current_sign_in_at
|
29
|
+
t.datetime :last_sign_in_at
|
30
|
+
t.string :current_sign_in_ip
|
31
|
+
t.string :last_sign_in_ip
|
32
|
+
end
|
33
|
+
|
34
|
+
## Confirmable
|
35
|
+
if Groovestack::Auth.devise_modules.include?(:confirmable)
|
36
|
+
t.string :confirmation_token
|
37
|
+
t.datetime :confirmed_at
|
38
|
+
t.datetime :confirmation_sent_at
|
39
|
+
t.string :unconfirmed_email # Only if using reconfirmable
|
40
|
+
end
|
41
|
+
|
42
|
+
## Lockable
|
43
|
+
if Groovestack::Auth.devise_modules.include?(:lockable)
|
44
|
+
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
|
45
|
+
t.string :unlock_token # Only if unlock strategy is :email or :both
|
46
|
+
t.datetime :locked_at
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
add_index :users, :reset_password_token, unique: true if Groovestack::Auth.devise_modules.include?(:recoverable)
|
51
|
+
add_index :users, :confirmation_token, unique: true if Groovestack::Auth.devise_modules.include?(:confirmable)
|
52
|
+
add_index :users, :unlock_token, unique: true if Groovestack::Auth.devise_modules.include?(:lockable)
|
53
|
+
|
54
|
+
# Add Omniauth-related columns to the existing identities table
|
55
|
+
change_table :identities do |t|
|
56
|
+
t.jsonb :omniauth_data
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/groovestack-auth.gemspec
CHANGED
@@ -26,17 +26,17 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
27
27
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
28
|
end
|
29
|
+
spec.files += Dir['{app}/**/*']
|
30
|
+
|
29
31
|
spec.bindir = 'exe'
|
30
32
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
33
|
spec.require_paths = ['lib']
|
32
34
|
|
33
|
-
spec.add_dependency '
|
34
|
-
spec.add_dependency '
|
35
|
-
spec.add_dependency '
|
36
|
-
|
37
|
-
|
38
|
-
spec.add_dependency 'omniauth-apple'
|
39
|
-
spec.add_dependency 'omniauth-google-oauth2'
|
35
|
+
spec.add_dependency 'devise'
|
36
|
+
spec.add_dependency 'devise-passwordless'
|
37
|
+
spec.add_dependency 'groovestack-config', '~> 0.1', '>= 0.1.4'
|
38
|
+
spec.add_dependency 'groovestack-identities', '~> 0.1', '>= 0.1.3'
|
39
|
+
spec.add_dependency 'rotp'
|
40
40
|
|
41
41
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
42
42
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
require_relative 'provider'
|
4
4
|
|
5
|
-
if defined?(Rails)
|
5
|
+
if defined?(Rails::Engine)
|
6
6
|
module Groovestack
|
7
7
|
module Auth
|
8
|
-
class
|
8
|
+
class Engine < ::Rails::Engine
|
9
9
|
include ::Groovestack::Base::CoreRailtie
|
10
10
|
|
11
11
|
def dx_validations
|
@@ -47,9 +47,20 @@ if defined?(Rails)
|
|
47
47
|
append_initializers app
|
48
48
|
end
|
49
49
|
|
50
|
+
initializer :append_locales do |app|
|
51
|
+
append_locales app
|
52
|
+
end
|
53
|
+
|
50
54
|
config.after_initialize do
|
51
55
|
after_init
|
52
56
|
end
|
57
|
+
|
58
|
+
config.to_prepare do
|
59
|
+
# Include Auth concerns after Rails is fully loaded to ensure Devise is loaded
|
60
|
+
|
61
|
+
::User.include ::Groovestack::Auth::User
|
62
|
+
::Identity.include ::Groovestack::Auth::Identity
|
63
|
+
end
|
53
64
|
end
|
54
65
|
end
|
55
66
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module GraphQL
|
6
|
+
class AuthorizedField < ::Groovestack::Base::GraphQL::Base::Field
|
7
|
+
def authorized?(obj, args, ctx)
|
8
|
+
authorized_fields = begin
|
9
|
+
obj.authorized_fields_for_serialization(ctx[:current_user])
|
10
|
+
rescue StandardError
|
11
|
+
[]
|
12
|
+
end
|
13
|
+
|
14
|
+
super && authorized_fields.include?(name.to_sym)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module GraphQL
|
6
|
+
module SchemaVisibility
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
use ::GraphQL::Schema::Visibility
|
11
|
+
|
12
|
+
# NOTE: currently, this is user role based
|
13
|
+
# visibility_profiles enumerate the permissions each
|
14
|
+
# role has access to
|
15
|
+
|
16
|
+
@visibility_profiles = [
|
17
|
+
{ key: :anon, permissions: [:public] },
|
18
|
+
{ key: :user, permissions: %i[public basic] },
|
19
|
+
{ key: ::User::Role::STAFF, permissions: [:public, :basic, ::User::Role::STAFF] },
|
20
|
+
{ key: ::User::Role::EXEC, permissions: [:public, :basic, ::User::Role::STAFF, ::User::Role::EXEC] },
|
21
|
+
{ key: ::User::Role::ADMIN,
|
22
|
+
permissions: [:public, :basic, ::User::Role::STAFF, ::User::Role::EXEC, ::User::Role::ADMIN] }
|
23
|
+
].each_with_object({}) do |profile, acc|
|
24
|
+
acc[profile[:key].to_sym] = profile[:permissions].map(&:to_sym)
|
25
|
+
end.freeze
|
26
|
+
|
27
|
+
class << self
|
28
|
+
attr_reader :visibility_profiles
|
29
|
+
|
30
|
+
def visibility_profile_for_context(context)
|
31
|
+
return [] if context[:visibility_profile].blank?
|
32
|
+
|
33
|
+
visibility_profiles[context[:visibility_profile].to_sym] || []
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module GraphQL
|
6
|
+
class VisibleField < ::Groovestack::Base::GraphQL::Base::Field
|
7
|
+
def visible?(context)
|
8
|
+
return super unless @visibility_permission
|
9
|
+
|
10
|
+
# visibility profile are the visibility levels the
|
11
|
+
# current user is authorized for
|
12
|
+
visibility_profile = context.schema.visibility_profile_for_context(context).map(&:to_sym)
|
13
|
+
|
14
|
+
return super unless visibility_profile
|
15
|
+
|
16
|
+
super && visibility_profile.include?(@visibility_permission.to_sym)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module GraphQL
|
6
|
+
class VisibleObject < ::Groovestack::Base::GraphQL::Base::Object
|
7
|
+
field_class ::Groovestack::Auth::GraphQL::VisibleField
|
8
|
+
|
9
|
+
def self.visible?(context)
|
10
|
+
any_visible_fields = fields.values.any? { |field| field.visible?(context) }
|
11
|
+
|
12
|
+
super && any_visible_fields
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rotp'
|
4
|
+
# require 'base32' # You can use any Base32 encoder
|
5
|
+
|
6
|
+
module Groovestack
|
7
|
+
module Auth
|
8
|
+
module Passwordless
|
9
|
+
module TOtpTokenizer
|
10
|
+
module_function
|
11
|
+
|
12
|
+
# Validity period (in seconds)
|
13
|
+
def token_validity_seconds
|
14
|
+
Devise.passwordless_login_within.seconds
|
15
|
+
end
|
16
|
+
|
17
|
+
# Generate a TOTP instance for the resource.
|
18
|
+
#
|
19
|
+
# We derive a per‑resource secret from a combination of the resource’s id and Rails’ secret.
|
20
|
+
def totp_for(resource, digits: 4, interval: token_validity_seconds)
|
21
|
+
# add last sign in at to prevent same otp being generated multiple times
|
22
|
+
identifier = resource.id
|
23
|
+
identifier += ":#{resource.current_sign_in_at.to_i}" if resource.current_sign_in_at.present?
|
24
|
+
|
25
|
+
raw_secret = OpenSSL::HMAC.digest('sha1', Rails.application.secret_key_base, identifier)
|
26
|
+
base32_secret = ROTP::Base32.encode raw_secret
|
27
|
+
|
28
|
+
# Create a TOTP object configured for 4 digits and the given time interval.
|
29
|
+
ROTP::TOTP.new(base32_secret, digits: digits, interval: interval)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Generates the token for the current time window.
|
33
|
+
def encode(resource, _extra: nil, _expires_at: nil)
|
34
|
+
# otp = totp_for(resource).now
|
35
|
+
|
36
|
+
# otp_as_formatted_code(otp)
|
37
|
+
totp_for(resource).now
|
38
|
+
end
|
39
|
+
|
40
|
+
# Verifies that the provided token is valid (i.e. within the current time window).
|
41
|
+
#
|
42
|
+
# Since the token is time‐dependent, expiration is enforced by the TOTP algorithm.
|
43
|
+
def decode(code, email, resource_class, _as_of: nil, _expire_duration: nil)
|
44
|
+
raise ::Devise::Passwordless::InvalidTokenError if code.blank?
|
45
|
+
|
46
|
+
# For TOTP, we look up the resource and then verify the code.
|
47
|
+
# Note: How you locate the resource is up to your application.
|
48
|
+
# For example, if your code is combined with an email or user id in the URL, you would look it up there.
|
49
|
+
#
|
50
|
+
resource = resource_class.find_by(email: email)
|
51
|
+
|
52
|
+
raise 'Resource not found' if resource.blank?
|
53
|
+
|
54
|
+
# # Remove the hyphen to get the plain digits.
|
55
|
+
# otp = otp_from_formatted_code(code)
|
56
|
+
otp = code
|
57
|
+
|
58
|
+
verify_opts = {}
|
59
|
+
# by default, ROTP is fixed window interval. This code enables a sliding window
|
60
|
+
now = Time.zone.now
|
61
|
+
verify_opts[:at] = now
|
62
|
+
verify_opts[:drift_behind] = token_validity_seconds / 2 # (now.sec % token_validity_seconds) - 1
|
63
|
+
if resource_class.passwordless_expire_old_tokens_on_sign_in && resource.current_sign_in_at.present?
|
64
|
+
# support login / logout / login again within same token validity period
|
65
|
+
verify_opts[:after] = (resource.current_sign_in_at - token_validity_seconds).to_i
|
66
|
+
end
|
67
|
+
# end sliding window logic
|
68
|
+
decrypted_data = totp_for(resource).verify(otp, **verify_opts)
|
69
|
+
|
70
|
+
raise ::Devise::Passwordless::InvalidOrExpiredTokenError if decrypted_data.blank?
|
71
|
+
|
72
|
+
[resource, { data: decrypted_data }]
|
73
|
+
end
|
74
|
+
|
75
|
+
# # Helper: Formats an 4‑digit code as "xxxx".
|
76
|
+
# def otp_as_formatted_code(otp)
|
77
|
+
# otp = otp.to_s
|
78
|
+
# code = otp.length == 4 ? otp.dup.insert(2, '-') : otp
|
79
|
+
# code
|
80
|
+
# end
|
81
|
+
|
82
|
+
# def otp_from_formatted_code(formatted_code)
|
83
|
+
# otp = formatted_code.delete('-')
|
84
|
+
# otp
|
85
|
+
# end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -54,6 +54,13 @@ module Groovestack
|
|
54
54
|
|
55
55
|
verbose.slice(*keys)
|
56
56
|
end
|
57
|
+
|
58
|
+
def self.credential_for(key)
|
59
|
+
provider_credentials = Rails.application.credentials.groovestack.auth.send(k)
|
60
|
+
return if provider_credentials.blank?
|
61
|
+
|
62
|
+
provider_credentials.send(key)
|
63
|
+
end
|
57
64
|
end
|
58
65
|
end
|
59
66
|
end
|
@@ -5,17 +5,17 @@ module Groovestack
|
|
5
5
|
module Providers
|
6
6
|
class Apple < OmniAuth
|
7
7
|
PROVIDER = :apple
|
8
|
-
REQUIRED_CREDENTIALS = %i[
|
8
|
+
REQUIRED_CREDENTIALS = %i[client_id team_id key_id pem_content].freeze
|
9
9
|
|
10
10
|
def self.generate_omniauth_args
|
11
11
|
[
|
12
12
|
provider,
|
13
|
-
|
13
|
+
credential_for(:client_id),
|
14
14
|
'',
|
15
15
|
{
|
16
|
-
team_id:
|
17
|
-
key_id:
|
18
|
-
pem:
|
16
|
+
team_id: credential_for(:team_id),
|
17
|
+
key_id: credential_for(:key_id),
|
18
|
+
pem: credential_for(:pem_content),
|
19
19
|
origin_param: 'return_to'
|
20
20
|
}
|
21
21
|
]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module Providers
|
6
|
+
class Facebook < OmniAuth
|
7
|
+
PROVIDER = :facebook
|
8
|
+
K = :facebook
|
9
|
+
REQUIRED_CREDENTIALS = %i[app_id app_secret].freeze
|
10
|
+
|
11
|
+
def self.generate_omniauth_args
|
12
|
+
[*super, { client_options: { site: 'https://graph.facebook.com/v22.0' } }]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -6,7 +6,7 @@ module Groovestack
|
|
6
6
|
class Google < OmniAuth
|
7
7
|
PROVIDER = :google_oauth2
|
8
8
|
K = :google
|
9
|
-
REQUIRED_CREDENTIALS = %i[
|
9
|
+
REQUIRED_CREDENTIALS = %i[client_id client_secret].freeze
|
10
10
|
|
11
11
|
def self.generate_omniauth_args
|
12
12
|
super << { name: k, origin_param: 'return_to' }
|
@@ -11,7 +11,7 @@ module Groovestack
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.required_credentials_present?
|
14
|
-
required_credentials.all? { |credential|
|
14
|
+
required_credentials.all? { |credential| credential_for(credential).present? }
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.configured?
|
@@ -22,7 +22,7 @@ module Groovestack
|
|
22
22
|
# different providers require different arguments
|
23
23
|
[
|
24
24
|
provider,
|
25
|
-
*required_credentials.map { |
|
25
|
+
*required_credentials.map { |credential| credential_for(credential) }
|
26
26
|
]
|
27
27
|
end
|
28
28
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module Routes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def draw_routes(mapper)
|
10
|
+
mapper.instance_exec do
|
11
|
+
devise_for :users, ::Groovestack::Auth.devise_for_routes_kwargs
|
12
|
+
|
13
|
+
devise_scope :user do
|
14
|
+
post 'users/magic_link', to: 'groovestack/auth/passwordless/magic_links#show'
|
15
|
+
|
16
|
+
if defined?(OmniAuth)
|
17
|
+
match '/users/auth/:provider/callback', to: 'groovestack/auth/omniauth_callbacks#verified', via: %i[get post],
|
18
|
+
as: :omniauth_callback
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Groovestack
|
4
|
+
module Auth
|
5
|
+
module Settings
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
extend Dry::Configurable
|
10
|
+
|
11
|
+
# NOTE: this impact user devise migration changes. Be sure
|
12
|
+
# to set / override this when initialling integrating with groovestack-auth
|
13
|
+
setting :devise_modules, default: [
|
14
|
+
:database_authenticatable,
|
15
|
+
:registerable,
|
16
|
+
:recoverable,
|
17
|
+
:rememberable,
|
18
|
+
:validatable,
|
19
|
+
:confirmable,
|
20
|
+
:lockable,
|
21
|
+
:timeoutable,
|
22
|
+
:trackable,
|
23
|
+
:magic_link_authenticatable,
|
24
|
+
:omniauthable,
|
25
|
+
{ omniauth_providers: %i[apple facebook google] }
|
26
|
+
], reader: true
|
27
|
+
|
28
|
+
setting :devise_for_routes_kwargs, default: {
|
29
|
+
controllers: {
|
30
|
+
omniauth_callbacks: 'groovestack/auth/omniauth_callbacks',
|
31
|
+
sessions: 'groovestack/auth/passwordless/sessions'
|
32
|
+
}
|
33
|
+
}, reader: true
|
34
|
+
|
35
|
+
setting :disabled_providers, default: [], reader: true
|
36
|
+
|
37
|
+
setting :omniauth_origin_url, default: nil, reader: true
|
38
|
+
setting :after_sign_in_path, default: nil, reader: true
|
39
|
+
setting :allow_other_host_redirects, default: false, reader: true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|