devise_auth0 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c18b3f8598776472a2f76cf1b1dfa63b00f53cf0490a8088d663a4f54ecbef5b
4
+ data.tar.gz: 04a20bad11da869d3b9724f68d66a4b2382e3c26c98d481f9425c1589a756330
5
+ SHA512:
6
+ metadata.gz: 8efc4197e1280051359ba69c9ddb8ed62fc70617eeaf65ddb518256ad6e1f11711cfee3651154cd4f9930e3401cc3ad29c68d8e0ad512df80a8e32e3b0c0d72d
7
+ data.tar.gz: b373a943d168c14277870c54eff0227077a5c8cfc6c14aaaab60de05a5782068aaf9efc3e5a769ccd87e840db54509385552f01cc53c8ac8c0dcfd352aab786b
data/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2023-09-08)
4
+
5
+
6
+ ### Tests
7
+
8
+ * Fixes rspec-rails issues on rails edge ([ea4a050](https://github.com/itsmechlark/devise-auth0/commit/ea4a050528196936960d38b29edfb36a6e6cfcf1))
9
+
10
+
11
+ ### Miscellaneous
12
+
13
+ * Bump changelog-enforcer to v3 ([5ffcab8](https://github.com/itsmechlark/devise-auth0/commit/5ffcab87952644b40cf73f45bce8cb2bd82ab534))
14
+ * Bump rubocop-shopify to v2.14 ([9102709](https://github.com/itsmechlark/devise-auth0/commit/91027094bbc243ef05fb10bfe2b9da91189ca8a4))
15
+ * Change Rubocop code scanning name ([ccae036](https://github.com/itsmechlark/devise-auth0/commit/ccae036e25f8f5d3d9fe0341e81d6b5b53af2df9))
16
+ * CI/CD workflow ([b82655d](https://github.com/itsmechlark/devise-auth0/commit/b82655d9a7af844f1a62d558e65799ef796951fc))
17
+ * Code scanning ([a2f9d1b](https://github.com/itsmechlark/devise-auth0/commit/a2f9d1b98994be941abfdba41346976cb717ba5a))
18
+ * Configure version file on release config ([53b3b5b](https://github.com/itsmechlark/devise-auth0/commit/53b3b5b4b88bc52bddee7bcee4cba1e889d21eda))
19
+ * Fixes Code Scanning permissions ([765f698](https://github.com/itsmechlark/devise-auth0/commit/765f698abe70f30a16b84549bb6256c22379d0fd))
20
+ * Fixes release please config ([fb8aba4](https://github.com/itsmechlark/devise-auth0/commit/fb8aba487088409c783c7022845be9677e22dd04))
21
+ * Fixes rubocop lint ([e02858b](https://github.com/itsmechlark/devise-auth0/commit/e02858b561f3d99691524795d43ae84fd5d8b978))
22
+ * Install bundler on DevContainer startup ([9ad89e4](https://github.com/itsmechlark/devise-auth0/commit/9ad89e43b1b1a99f06250c7bdc5b6006cd505cca))
23
+ * Move changelog type to release config ([01b4d9e](https://github.com/itsmechlark/devise-auth0/commit/01b4d9e02c981a3adc6c69fd64e9331838c3f618))
24
+ * Release Please config ([08af26d](https://github.com/itsmechlark/devise-auth0/commit/08af26d4002d3e89cb210cabd89ea768039193bc))
25
+ * Remove changelog ([bb7befe](https://github.com/itsmechlark/devise-auth0/commit/bb7befec90baad2c9f6f4671cb830a9e42491e11))
26
+ * Remove old changelog ([fae0a6c](https://github.com/itsmechlark/devise-auth0/commit/fae0a6cdad32a043c53820c78277df2c6acc872a))
27
+ * Support DevContainers ([5b937c9](https://github.com/itsmechlark/devise-auth0/commit/5b937c9696fea630eabaa9f4d745e4c64418e753))
28
+ * Test against Ruby 3.2 ([185bae5](https://github.com/itsmechlark/devise-auth0/commit/185bae5d12b17409e6ed62d0aa7be9870ca9c424))
29
+ * Update Changelog ([6df1d33](https://github.com/itsmechlark/devise-auth0/commit/6df1d33cf6810b752efc5bc4bf6d3f8691117847))
30
+ * Use Python 3.8 for codespell ([a6a8f35](https://github.com/itsmechlark/devise-auth0/commit/a6a8f3556ae54dd1ed0608eaa431772ef2ff1977))
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 John Chlark Sumatra
4
+ Copyright (c) 2022 First Circle Growth Finance Corp.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ class Auth0CallbacksController < Devise::OmniauthCallbacksController
5
+ def callback
6
+ user = resource_class.from_auth0_omniauth(request.env["omniauth.auth"])
7
+
8
+ if user&.persisted?
9
+ set_flash_message(:notice, :success, kind: "Auth0") if is_navigational_format?
10
+ sign_in_and_redirect(user, event: :authentication)
11
+ else
12
+ session["devise.auth0_data"] = request.env["omniauth.auth"].except(:extra)
13
+ redirect_to(after_omniauth_callback_path_for(resource_name))
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def after_omniauth_callback_path_for(scope)
20
+ new_session_path(scope)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "auth0"
4
+
5
+ module Devise
6
+ module Auth0
7
+ class Client < ::Auth0::Client
8
+ def initialize(config)
9
+ super(
10
+ client_id: config.client_id,
11
+ client_secret: config.client_secret,
12
+ domain: config.domain,
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "devise/auth0/exceptions"
4
+
5
+ module Devise
6
+ module Auth0
7
+ module Controllers
8
+ # Those helpers are convenience methods added to ApplicationController.
9
+ module Helpers
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ if respond_to?(:devise_group)
14
+ devise_group :auth0, contains: Devise.mappings.keys
15
+ end
16
+
17
+ if respond_to?(:helper_method)
18
+ helper_method :can?, :cannot?
19
+ end
20
+ end
21
+
22
+ def authorize!(*args)
23
+ options = args.extract_options!
24
+ message = options[:message]
25
+
26
+ if cannot?(*args)
27
+ raise AccessDenied.new(message, *args)
28
+ end
29
+
30
+ args
31
+ end
32
+
33
+ def can?(*args)
34
+ !!current_auth0&.can?(*args)
35
+ end
36
+
37
+ def cannot?(*args)
38
+ !!current_auth0&.cannot?(*args)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Auth0
5
+ # A general CanCan exception
6
+ class Error < StandardError; end
7
+
8
+ class AccessDenied < Error
9
+ attr_reader :action, :subject
10
+
11
+ def initialize(message = nil, action = nil, resource_class = nil)
12
+ @message = message.presence || I18n.t(
13
+ :"unauthorized.default",
14
+ default: "You are not authorized to access this page.",
15
+ )
16
+ @action = action
17
+ @resource_class = resource_class
18
+
19
+ super(@message)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Auth0
5
+ # Helpers to parse token from a request and to a response
6
+ module Helpers
7
+ AUTH_METHOD = "Bearer"
8
+
9
+ class << self
10
+ # Parses the token from a rack request
11
+ #
12
+ # @param env [Hash] rack env hash
13
+ # @return [String] JWT token
14
+ # @return [nil] if token is not present
15
+ def get_auth(env)
16
+ auth = env["HTTP_AUTHORIZATION"].presence
17
+ return unless auth
18
+
19
+ method, token = auth.split
20
+ method == AUTH_METHOD ? token : nil
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Routing
5
+ class Mapper
6
+ protected
7
+
8
+ def devise_auth0_callback(mapping, controllers) # :nodoc:
9
+ return unless mapping.to.auth0_config.omniauth
10
+
11
+ path_prefix = Devise.omniauth_path_prefix || "/#{mapping.fullpath}/auth".squeeze("/")
12
+ set_omniauth_path_prefix!(path_prefix)
13
+
14
+ match(
15
+ "/auth/auth0",
16
+ to: "#{controllers[:auth0_callbacks]}#passthru",
17
+ as: :auth0_omniauth_authorize,
18
+ via: [:get, :post],
19
+ )
20
+
21
+ match(
22
+ "/auth/auth0/failure",
23
+ to: "#{controllers[:auth0_callbacks]}#failure",
24
+ as: :auth0_omniauth_failure,
25
+ via: [:get, :post],
26
+ )
27
+
28
+ match(
29
+ "/auth/auth0/callback",
30
+ to: "#{controllers[:auth0_callbacks]}#callback",
31
+ as: :auth0_omniauth_callback,
32
+ via: [:get, :post],
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "omniauth-auth0"
4
+ require "omniauth/rails_csrf_protection"
5
+
6
+ require "devise/auth0/rails/routes"
7
+ require "devise/auth0/controllers/helpers"
8
+
9
+ module Devise
10
+ module Auth0
11
+ class Engine < ::Rails::Engine
12
+ initializer "devise.auth0.cache", after: "initialize_cache" do |_app|
13
+ Devise.auth0.cache = Rails.cache
14
+ end
15
+
16
+ initializer "devise.auth0", before: "devise.omniauth" do |_app|
17
+ config = Devise.auth0
18
+ if config.omniauth
19
+ Devise.setup do |devise|
20
+ devise.omniauth(
21
+ :auth0,
22
+ config.client_id,
23
+ config.client_secret,
24
+ config.custom_domain,
25
+ {
26
+ authorize_params: {
27
+ audience: config.aud.join(","),
28
+ scope: config.scope,
29
+ },
30
+ },
31
+ )
32
+ end
33
+
34
+ # Patches the existing devise failure app to ensure a right mapping is used.
35
+ # Read more: devise/omniauth.rb
36
+ ::OmniAuth.config.on_failure = proc do |env|
37
+ env["devise.mapping"] = ::Devise::Mapping.find_by_path!(env["PATH_INFO"], :fullpath)
38
+ controller_name = ActiveSupport::Inflector.camelize(env["devise.mapping"].controllers[:omniauth_callbacks])
39
+ controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller")
40
+ controller_klass.action(:failure).call(env)
41
+ end
42
+ end
43
+
44
+ ActiveSupport.on_load(:action_controller) do
45
+ include Devise::Auth0::Controllers::Helpers
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/http_cache"
5
+ require "jwt"
6
+
7
+ module Devise
8
+ module Auth0
9
+ # Helpers to parse token from a request and to a response
10
+ class Token
11
+ class << self
12
+ def parse(auth, resource_class)
13
+ token = new(auth, resource_class)
14
+ token.verify
15
+ token
16
+ end
17
+ end
18
+
19
+ def initialize(auth, resource_class)
20
+ @auth = auth.presence
21
+ @resource_class = resource_class
22
+ end
23
+
24
+ def provider
25
+ auth0_id&.split("|")&.first
26
+ end
27
+
28
+ def uid
29
+ auth0_id&.split("|")&.last
30
+ end
31
+
32
+ def auth0_id
33
+ return if verify.nil?
34
+ return "auth0|#{verify[0]["azp"]}" if bot?
35
+
36
+ verify[0]["sub"]
37
+ end
38
+
39
+ def user
40
+ @user ||= if bot?
41
+ {
42
+ "user_id" => uid,
43
+ "email" => "#{uid}@#{config.domain}",
44
+ }
45
+ else
46
+ ::Devise.auth0.cache.fetch("devise-auth0/#{auth0_id}", expires_in: ::Devise.auth0.cache_expires_in) do
47
+ client.user(auth0_id)
48
+ end
49
+ end
50
+ end
51
+
52
+ def bot?
53
+ return false if verify.nil?
54
+
55
+ verify[0]["gty"] == "client-credentials"
56
+ end
57
+
58
+ def scopes
59
+ return [] if verify.nil?
60
+
61
+ verify[0]["scope"].to_s.split(" ")
62
+ end
63
+
64
+ def permissions
65
+ return [] if verify.nil?
66
+
67
+ verify[0]["permissions"].presence || []
68
+ end
69
+
70
+ def verify
71
+ @payload ||= JWT.decode(
72
+ @auth,
73
+ nil,
74
+ true, # Verify the signature of this token
75
+ algorithms: config.algorithm,
76
+ iss: issuer,
77
+ verify_iss: true,
78
+ aud: config.aud,
79
+ verify_aud: true,
80
+ ) do |header|
81
+ jwks_hash[header["kid"]]
82
+ end
83
+ rescue JWT::DecodeError
84
+ nil
85
+ end
86
+
87
+ def valid?
88
+ !verify.nil?
89
+ end
90
+
91
+ private
92
+
93
+ def config
94
+ @resource_class.auth0_config
95
+ end
96
+
97
+ def client
98
+ @resource_class.auth0_client
99
+ end
100
+
101
+ def issuer
102
+ "https://#{config.custom_domain.presence || config.domain.presence}/"
103
+ end
104
+
105
+ def jwks_hash
106
+ conn = ::Faraday.new("https://#{config.custom_domain}") do |f|
107
+ f.use(:http_cache, store: ::Devise.auth0.cache)
108
+ f.request(:retry, max: 3)
109
+ f.adapter(::Faraday.default_adapter)
110
+ end
111
+ jwks_keys = JSON.parse(conn.get("/.well-known/jwks.json").body)["keys"]
112
+ Hash[
113
+ jwks_keys
114
+ .map do |k|
115
+ [
116
+ k["kid"],
117
+ OpenSSL::X509::Certificate.new(
118
+ Base64.decode64(k["x5c"].first),
119
+ ).public_key,
120
+ ]
121
+ end
122
+ ]
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Auth0
5
+ VERSION = "1.0.0"
6
+
7
+ class << self
8
+ def gem_version
9
+ Gem::Version.new(VERSION)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "devise"
4
+ require "dry-configurable"
5
+
6
+ require "devise/auth0/client"
7
+
8
+ # Authentication library
9
+ module Devise
10
+ # Yields to Devise::Auth0.config
11
+ class << self
12
+ def auth0
13
+ return Devise::Auth0.config unless block_given?
14
+
15
+ yield(Devise::Auth0.config)
16
+ end
17
+ end
18
+
19
+ module Models
20
+ autoload :Auth0, "devise/models/auth0"
21
+ end
22
+
23
+ module Strategies
24
+ autoload :Auth0, "devise/strategies/auth0"
25
+ end
26
+
27
+ # Auth0 extension for devise
28
+ module Auth0
29
+ extend ::Dry::Configurable
30
+
31
+ setting(:algorithm, default: "RS256")
32
+ setting(
33
+ :aud,
34
+ default: ENV["AUTH0_AUDIENCE"].presence,
35
+ constructor: ->(aud) { aud.is_a?(Array) ? aud : aud.to_s.split(",") },
36
+ )
37
+ setting(:client_id, default: ENV["AUTH0_CLIENT_ID"].presence)
38
+ setting(:client_secret, default: ENV["AUTH0_CLIENT_SECRET"].presence)
39
+ setting(:custom_domain, default: ENV["AUTH0_CUSTOM_DOMAIN"].presence)
40
+ setting(:domain, default: ENV["AUTH0_DOMAIN"].presence)
41
+ setting(:omniauth, default: false)
42
+ setting(:scope, default: "openid")
43
+ setting(:email_domains_allowlist, default: [])
44
+ setting(:email_domains_blocklist, default: [])
45
+ setting(:cache)
46
+ setting(:cache_expires_in, default: 15.minutes)
47
+
48
+ class << self
49
+ def logout(record)
50
+ record.class.auth0_client.grants(user_id: record.auth0_id).each do |grant|
51
+ record.class.auth0_client.delete_grant(grant["id"], record.auth0_id)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ add_module(:auth0, strategy: true, controller: :auth0_callbacks, route: { auth0_callback: [:callback] })
58
+ end
59
+
60
+ require "devise/auth0/version"
61
+ require "devise/auth0/rails"
62
+ require "devise/strategies/auth0"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ Warden::Manager.before_logout do |record, _warden, _options|
4
+ if record.respond_to?(:auth0_id)
5
+ Devise::Auth0.logout(record)
6
+ end
7
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mail"
4
+ require "devise/hooks/auth0"
5
+
6
+ module Devise
7
+ module Models
8
+ module Auth0
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ validates :uid, allow_blank: true, uniqueness: { scope: :provider, message: "should happen once per provider" }
13
+ with_options if: -> { respond_to?(:email) } do
14
+ validates :email, uniqueness: true
15
+ validate :email_domain_allowed, :email_domain_disallowed
16
+ end
17
+ end
18
+
19
+ class << self
20
+ def required_fields(klass)
21
+ []
22
+ end
23
+ end
24
+
25
+ def email_domain_allowed
26
+ return if self.class.auth0_config.email_domains_allowlist.empty?
27
+
28
+ m = Mail::Address.new(email)
29
+ return if m.domain.nil?
30
+
31
+ unless self.class.auth0_config.email_domains_allowlist.include?(m.domain)
32
+ errors.add(:email, :not_allowed)
33
+ end
34
+ end
35
+
36
+ def email_domain_disallowed
37
+ return if self.class.auth0_config.email_domains_blocklist.empty?
38
+
39
+ m = Mail::Address.new(email)
40
+ return if m.domain.nil?
41
+
42
+ if self.class.auth0_config.email_domains_blocklist.include?(m.domain)
43
+ errors.add(:email, :not_allowed)
44
+ end
45
+ end
46
+
47
+ def can?(action, resource_class = nil)
48
+ scope = [action]
49
+ if resource_class.is_a?(String)
50
+ scope << resource_class
51
+ elsif resource_class
52
+ resource_name = resource_class.name.underscore.split("/")
53
+ resource_name[-1] = resource_name[-1].pluralize
54
+ scope << resource_name.join("/")
55
+ end
56
+ auth0_scopes.include?(scope.join(":"))
57
+ end
58
+
59
+ # Convenience method which works the same as "can?" but returns the opposite value.
60
+ #
61
+ # cannot? :destroy, @project
62
+ #
63
+ def cannot?(*args)
64
+ !can?(*args)
65
+ end
66
+
67
+ def auth0_scopes=(scopes)
68
+ ::Devise.auth0.cache.write(
69
+ "devise-auth0/#{auth0_id}/scopes",
70
+ scopes,
71
+ expires_in: ::Devise.auth0.cache_expires_in,
72
+ )
73
+ end
74
+
75
+ def auth0_scopes
76
+ ::Devise.auth0.cache.fetch("devise-auth0/#{auth0_id}/scopes", expires_in: ::Devise.auth0.cache_expires_in) do
77
+ if bot?
78
+ self.class.auth0_client.client_grants(
79
+ client_id: uid,
80
+ audience: self.class.auth0_config.aud,
81
+ ).first.try(:[], "scope")
82
+ else
83
+ user = self.class.auth0_client.users_by_email(email).find do |u|
84
+ u["identities"].any? { |i| i["user_id"] == uid }
85
+ end
86
+ return [] if user.nil?
87
+
88
+ permissions = []
89
+ page = 0
90
+ loop do
91
+ response_data = self.class.auth0_client
92
+ .get_user_permissions(
93
+ user["user_id"],
94
+ { page: page, per_page: 100, include_totals: true },
95
+ )
96
+
97
+ response_data["permissions"].select do |permission|
98
+ self.class.auth0_config.aud.include?(permission["resource_server_identifier"])
99
+ end.each do |permission|
100
+ permissions << permission["permission_name"]
101
+ end
102
+
103
+ break if response_data["start"] / 100 == response_data["total"] / 100
104
+
105
+ page += 1
106
+ end
107
+ permissions
108
+ end
109
+ end
110
+ end
111
+
112
+ def auth0_id
113
+ "#{provider}|#{uid}"
114
+ end
115
+
116
+ def after_auth0_token_created(token)
117
+ end
118
+
119
+ def after_auth0_token(token)
120
+ set_auth_id(token.provider, token.uid)
121
+ end
122
+
123
+ def after_auth0_omniauth_created(auth)
124
+ end
125
+
126
+ def after_auth0_omniauth(auth)
127
+ set_auth_id(auth.provider, auth.uid)
128
+ end
129
+
130
+ private
131
+
132
+ def set_auth_id(provider, uid)
133
+ return if self.provider == provider && self.uid == uid
134
+
135
+ self.provider = provider
136
+ self.uid = uid.include?("|") ? uid.split("|").last : uid
137
+ save
138
+ end
139
+
140
+ module ClassMethods
141
+ Devise::Models.config(self, :auth0_options)
142
+
143
+ def from_auth0_token(token)
144
+ user = where(
145
+ auth0_where_conditions(
146
+ provider: token.provider,
147
+ uid: token.uid,
148
+ email: token.user["email"],
149
+ ),
150
+ ).first_or_create do |user|
151
+ user.provider = token.provider
152
+ user.uid = token.uid
153
+ user.email = token.user["email"] if user.respond_to?(:email=)
154
+ user.password = Devise.friendly_token[0, 20] if user.respond_to?(:password=)
155
+ user.bot = token.bot? if user.respond_to?(:bot=)
156
+ user.after_auth0_token_created(token)
157
+ end
158
+ user.auth0_scopes = token.scopes.dup.concat(token.permissions).uniq
159
+ user.after_auth0_token(token)
160
+ user
161
+ end
162
+
163
+ def parse_auth0_token(token)
164
+ ::Devise::Auth0::Token.parse(token, self)
165
+ end
166
+
167
+ def from_auth0_omniauth(auth)
168
+ return unless auth0_config.omniauth
169
+
170
+ uid = auth.uid.include?("|") ? auth.uid.split("|").last : auth.uid
171
+ user = where(
172
+ auth0_where_conditions(
173
+ provider: auth.provider,
174
+ uid: uid,
175
+ email: auth.info.email,
176
+ ),
177
+ ).first_or_create do |user|
178
+ user.provider = auth.provider
179
+ user.uid = uid
180
+ user.email = auth.info.email if user.respond_to?(:email=)
181
+ user.password = Devise.friendly_token[0, 20] if user.respond_to?(:password=)
182
+ user.after_auth0_omniauth_created(auth)
183
+ end
184
+ user.after_auth0_omniauth(auth)
185
+ user
186
+ end
187
+
188
+ def auth0_where_conditions(provider:, uid:, email: nil)
189
+ sql = arel_table[:provider].eq(provider).and(arel_table[:uid].eq(uid))
190
+ sql = sql.or(arel_table[:email].eq(email)) if email && column_names.include?("email")
191
+ sql
192
+ end
193
+
194
+ def auth0_config
195
+ return @auth0_config unless @auth0_config.nil?
196
+
197
+ @auth0_config ||= ::Devise.auth0.pristine
198
+ @auth0_config.update(::Devise.auth0.values)
199
+ @auth0_config.update(auth0_options) if defined?(@auth0_options)
200
+ @auth0_config.finalize!
201
+ @auth0_config
202
+ end
203
+
204
+ def auth0_client
205
+ @auth0_client ||= ::Devise::Auth0::Client.new(auth0_config)
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "devise/strategies/base"
4
+
5
+ require_relative "../auth0/helpers"
6
+ require_relative "../auth0/token"
7
+
8
+ module Devise
9
+ module Strategies
10
+ # Warden strategy to authenticate an user through a JWT token in the
11
+ # `Authorization` request header
12
+ class Auth0 < Devise::Strategies::Base
13
+ def valid?
14
+ !auth.nil?
15
+ end
16
+
17
+ def store?
18
+ false
19
+ end
20
+
21
+ def authenticate!
22
+ if token.valid?
23
+ resource = mapping.to.from_auth0_token(token)
24
+ return success!(resource) if resource.persisted?
25
+ end
26
+
27
+ fail!(:invalid)
28
+ end
29
+
30
+ private
31
+
32
+ def token
33
+ @token ||= mapping.to.parse_auth0_token(auth)
34
+ end
35
+
36
+ def auth
37
+ @auth ||= ::Devise::Auth0::Helpers.get_auth(env)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ Warden::Strategies.add(:auth0, Devise::Strategies::Auth0)
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "devise/auth0"
metadata ADDED
@@ -0,0 +1,484 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devise_auth0
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - John Chlark Sumatra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: auth0
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: devise
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-configurable
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.13'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.13'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.10.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.10.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday-http-cache
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: jwt
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mail
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: net-smtp
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: omniauth-auth0
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: omniauth-rails_csrf_protection
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: appraisal
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.4'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.4'
167
+ - !ruby/object:Gem::Dependency
168
+ name: bundler
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: code-scanning-rubocop
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: dotenv
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: faker
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '2.0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '2.0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: multi_json
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: multipart-parser
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - "~>"
242
+ - !ruby/object:Gem::Version
243
+ version: 0.1.1
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - "~>"
249
+ - !ruby/object:Gem::Version
250
+ version: 0.1.1
251
+ - !ruby/object:Gem::Dependency
252
+ name: pry-byebug
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - "~>"
256
+ - !ruby/object:Gem::Version
257
+ version: '3.7'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - "~>"
263
+ - !ruby/object:Gem::Version
264
+ version: '3.7'
265
+ - !ruby/object:Gem::Dependency
266
+ name: rack_session_access
267
+ requirement: !ruby/object:Gem::Requirement
268
+ requirements:
269
+ - - ">="
270
+ - !ruby/object:Gem::Version
271
+ version: '0'
272
+ type: :development
273
+ prerelease: false
274
+ version_requirements: !ruby/object:Gem::Requirement
275
+ requirements:
276
+ - - ">="
277
+ - !ruby/object:Gem::Version
278
+ version: '0'
279
+ - !ruby/object:Gem::Dependency
280
+ name: rake
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - "~>"
284
+ - !ruby/object:Gem::Version
285
+ version: '13.0'
286
+ type: :development
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - "~>"
291
+ - !ruby/object:Gem::Version
292
+ version: '13.0'
293
+ - !ruby/object:Gem::Dependency
294
+ name: rspec-rails
295
+ requirement: !ruby/object:Gem::Requirement
296
+ requirements:
297
+ - - ">="
298
+ - !ruby/object:Gem::Version
299
+ version: '0'
300
+ type: :development
301
+ prerelease: false
302
+ version_requirements: !ruby/object:Gem::Requirement
303
+ requirements:
304
+ - - ">="
305
+ - !ruby/object:Gem::Version
306
+ version: '0'
307
+ - !ruby/object:Gem::Dependency
308
+ name: rubocop-performance
309
+ requirement: !ruby/object:Gem::Requirement
310
+ requirements:
311
+ - - ">="
312
+ - !ruby/object:Gem::Version
313
+ version: '0'
314
+ type: :development
315
+ prerelease: false
316
+ version_requirements: !ruby/object:Gem::Requirement
317
+ requirements:
318
+ - - ">="
319
+ - !ruby/object:Gem::Version
320
+ version: '0'
321
+ - !ruby/object:Gem::Dependency
322
+ name: rubocop-rails
323
+ requirement: !ruby/object:Gem::Requirement
324
+ requirements:
325
+ - - ">="
326
+ - !ruby/object:Gem::Version
327
+ version: '0'
328
+ type: :development
329
+ prerelease: false
330
+ version_requirements: !ruby/object:Gem::Requirement
331
+ requirements:
332
+ - - ">="
333
+ - !ruby/object:Gem::Version
334
+ version: '0'
335
+ - !ruby/object:Gem::Dependency
336
+ name: rubocop-rake
337
+ requirement: !ruby/object:Gem::Requirement
338
+ requirements:
339
+ - - ">="
340
+ - !ruby/object:Gem::Version
341
+ version: '0'
342
+ type: :development
343
+ prerelease: false
344
+ version_requirements: !ruby/object:Gem::Requirement
345
+ requirements:
346
+ - - ">="
347
+ - !ruby/object:Gem::Version
348
+ version: '0'
349
+ - !ruby/object:Gem::Dependency
350
+ name: rubocop-rspec
351
+ requirement: !ruby/object:Gem::Requirement
352
+ requirements:
353
+ - - ">="
354
+ - !ruby/object:Gem::Version
355
+ version: '0'
356
+ type: :development
357
+ prerelease: false
358
+ version_requirements: !ruby/object:Gem::Requirement
359
+ requirements:
360
+ - - ">="
361
+ - !ruby/object:Gem::Version
362
+ version: '0'
363
+ - !ruby/object:Gem::Dependency
364
+ name: rubocop-shopify
365
+ requirement: !ruby/object:Gem::Requirement
366
+ requirements:
367
+ - - "~>"
368
+ - !ruby/object:Gem::Version
369
+ version: '2.14'
370
+ type: :development
371
+ prerelease: false
372
+ version_requirements: !ruby/object:Gem::Requirement
373
+ requirements:
374
+ - - "~>"
375
+ - !ruby/object:Gem::Version
376
+ version: '2.14'
377
+ - !ruby/object:Gem::Dependency
378
+ name: simplecov
379
+ requirement: !ruby/object:Gem::Requirement
380
+ requirements:
381
+ - - ">="
382
+ - !ruby/object:Gem::Version
383
+ version: 0.21.2
384
+ type: :development
385
+ prerelease: false
386
+ version_requirements: !ruby/object:Gem::Requirement
387
+ requirements:
388
+ - - ">="
389
+ - !ruby/object:Gem::Version
390
+ version: 0.21.2
391
+ - !ruby/object:Gem::Dependency
392
+ name: timecop
393
+ requirement: !ruby/object:Gem::Requirement
394
+ requirements:
395
+ - - ">="
396
+ - !ruby/object:Gem::Version
397
+ version: '0'
398
+ type: :development
399
+ prerelease: false
400
+ version_requirements: !ruby/object:Gem::Requirement
401
+ requirements:
402
+ - - ">="
403
+ - !ruby/object:Gem::Version
404
+ version: '0'
405
+ - !ruby/object:Gem::Dependency
406
+ name: vcr
407
+ requirement: !ruby/object:Gem::Requirement
408
+ requirements:
409
+ - - "~>"
410
+ - !ruby/object:Gem::Version
411
+ version: '6.0'
412
+ type: :development
413
+ prerelease: false
414
+ version_requirements: !ruby/object:Gem::Requirement
415
+ requirements:
416
+ - - "~>"
417
+ - !ruby/object:Gem::Version
418
+ version: '6.0'
419
+ - !ruby/object:Gem::Dependency
420
+ name: webmock
421
+ requirement: !ruby/object:Gem::Requirement
422
+ requirements:
423
+ - - "~>"
424
+ - !ruby/object:Gem::Version
425
+ version: '3.4'
426
+ type: :development
427
+ prerelease: false
428
+ version_requirements: !ruby/object:Gem::Requirement
429
+ requirements:
430
+ - - "~>"
431
+ - !ruby/object:Gem::Version
432
+ version: '3.4'
433
+ description: Auth0 authentication for devise
434
+ email:
435
+ - clark@sumatra.com.ph
436
+ executables: []
437
+ extensions: []
438
+ extra_rdoc_files:
439
+ - LICENSE.md
440
+ files:
441
+ - CHANGELOG.md
442
+ - LICENSE.md
443
+ - app/controllers/devise/auth0_callbacks_controller.rb
444
+ - lib/devise/auth0.rb
445
+ - lib/devise/auth0/client.rb
446
+ - lib/devise/auth0/controllers/helpers.rb
447
+ - lib/devise/auth0/exceptions.rb
448
+ - lib/devise/auth0/helpers.rb
449
+ - lib/devise/auth0/rails.rb
450
+ - lib/devise/auth0/rails/routes.rb
451
+ - lib/devise/auth0/token.rb
452
+ - lib/devise/auth0/version.rb
453
+ - lib/devise/hooks/auth0.rb
454
+ - lib/devise/models/auth0.rb
455
+ - lib/devise/strategies/auth0.rb
456
+ - lib/devise_auth0.rb
457
+ homepage: https://github.com/itsmechlark/devise_auth0
458
+ licenses:
459
+ - MIT
460
+ metadata:
461
+ homepage_uri: https://github.com/itsmechlark/devise_auth0
462
+ changelog_uri: https://github.com/itsmechlark/devise_auth0/releases/tag/v1.0.0
463
+ source_code_uri: https://github.com/itsmechlark/devise_auth0/tree/v1.0.0
464
+ bug_tracker_uri: https://github.com/itsmechlark/devise_auth0/issues
465
+ post_install_message:
466
+ rdoc_options: []
467
+ require_paths:
468
+ - lib
469
+ required_ruby_version: !ruby/object:Gem::Requirement
470
+ requirements:
471
+ - - ">="
472
+ - !ruby/object:Gem::Version
473
+ version: '0'
474
+ required_rubygems_version: !ruby/object:Gem::Requirement
475
+ requirements:
476
+ - - ">="
477
+ - !ruby/object:Gem::Version
478
+ version: '0'
479
+ requirements: []
480
+ rubygems_version: 3.2.3
481
+ signing_key:
482
+ specification_version: 4
483
+ summary: Auth0 authentication for devise
484
+ test_files: []