• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mgmodell / devise_token_auth_multi_email / #663

17 Mar 2026 01:12AM UTC coverage: 12.22% (-78.4%) from 90.649%
#663

push

mgmodell
switching back to mult-email

202 of 1653 relevant lines covered (12.22%)

0.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/app/controllers/devise_token_auth/omniauth_callbacks_controller.rb
1
# frozen_string_literal: true
2

3
module DeviseTokenAuth
×
4
  class OmniauthCallbacksController < DeviseTokenAuth::ApplicationController
×
5
    attr_reader :auth_params
×
6

7
    before_action :validate_auth_origin_url_param
×
8

9
    skip_before_action :set_user_by_token, raise: false
×
10
    skip_after_action :update_auth_header
×
11

12
    # intermediary route for successful omniauth authentication. omniauth does
13
    # not support multiple models, so we must resort to this terrible hack.
14
    def redirect_callbacks
×
15

16
      # derive target redirect route from 'resource_class' param, which was set
17
      # before authentication.
18
      devise_mapping = get_devise_mapping
×
19
      redirect_route = get_redirect_route(devise_mapping)
×
20

21
      # preserve omniauth info for success route. ignore 'extra' in twitter
22
      # auth response to avoid CookieOverflow.
23
      session['dta.omniauth.auth'] = request.env['omniauth.auth'].except('extra')
×
24
      session['dta.omniauth.params'] = request.env['omniauth.params']
×
25

26
      redirect_to redirect_route, {status: 307}.merge(redirect_options)
×
27
    end
×
28

29
    def get_redirect_route(devise_mapping)
×
30
      path = "#{Devise.mappings[devise_mapping.to_sym].fullpath}/#{params[:provider]}/callback"
×
31
      klass = request.scheme == 'https' ? URI::HTTPS : URI::HTTP
×
32
      redirect_route = klass.build(host: request.host, port: request.port, path: path).to_s
×
33
    end
×
34

35
    def get_devise_mapping
×
36
       # derive target redirect route from 'resource_class' param, which was set
37
       # before authentication.
38
       devise_mapping = [request.env['omniauth.params']['namespace_name'],
×
39
                         request.env['omniauth.params']['resource_class'].underscore.gsub('/', '_')].compact.join('_')
×
40
    rescue NoMethodError => err
×
41
      default_devise_mapping
×
42
    end
×
43

44
    # This method will only be called if `get_devise_mapping` cannot
45
    # find the mapping in `omniauth.params`.
46
    #
47
    # One example use-case here is for IDP-initiated SAML login.  In that
48
    # case, there will have been no initial request in which to save
49
    # the devise mapping.  If you are in a situation like that, and
50
    # your app allows for you to determine somehow what the devise
51
    # mapping should be (because, for example, it is always the same),
52
    # then you can handle it by overriding this method.
53
    def default_devise_mapping
×
54
      raise NotImplementedError.new('no default_devise_mapping set')
×
55
    end
×
56

57
    def omniauth_success
×
58
      get_resource_from_auth_hash
×
59
      set_token_on_resource
×
60
      create_auth_params
×
61

62
      if confirmable_enabled?
×
63
        # don't send confirmation email!!!
64
        @resource.skip_confirmation!
×
65
      end
×
66

67
      sign_in(:user, @resource, store: false, bypass: false)
×
68

69
      @resource.save!
×
70

71
      yield @resource if block_given?
×
72

73
      if DeviseTokenAuth.cookie_enabled
×
74
        set_token_in_cookie(@resource, @token)
×
75
      end
×
76

77
      render_data_or_redirect('deliverCredentials', @auth_params.as_json, @resource.as_json)
×
78
    end
×
79

80
    def omniauth_failure
×
81
      @error = params[:message]
×
82
      render_data_or_redirect('authFailure', error: @error)
×
83
    end
×
84

85
    def validate_auth_origin_url_param
×
86
      return render_error_not_allowed_auth_origin_url if auth_origin_url && blacklisted_redirect_url?(auth_origin_url)
×
87
    end
×
88

89

90
    protected
×
91

92
    # this will be determined differently depending on the action that calls
93
    # it. redirect_callbacks is called upon returning from successful omniauth
94
    # authentication, and the target params live in an omniauth-specific
95
    # request.env variable. this variable is then persisted thru the redirect
96
    # using our own dta.omniauth.params session var. the omniauth_success
97
    # method will access that session var and then destroy it immediately
98
    # after use.  In the failure case, finally, the omniauth params
99
    # are added as query params in our monkey patch to OmniAuth in engine.rb
100
    def omniauth_params
×
101
      unless defined?(@_omniauth_params)
×
102
        if request.env['omniauth.params'] && request.env['omniauth.params'].any?
×
103
          @_omniauth_params = request.env['omniauth.params']
×
104
        elsif session['dta.omniauth.params'] && session['dta.omniauth.params'].any?
×
105
          @_omniauth_params ||= session.delete('dta.omniauth.params')
×
106
          @_omniauth_params
×
107
        elsif params['omniauth_window_type']
×
108
          @_omniauth_params = params.slice('omniauth_window_type', 'auth_origin_url', 'resource_class', 'origin')
×
109
        else
×
110
          @_omniauth_params = {}
×
111
        end
×
112
      end
×
113
      @_omniauth_params
×
114
    end
×
115

116
    # break out provider attribute assignment for easy method extension
117
    def assign_provider_attrs(user, auth_hash)
×
118
      attrs = auth_hash['info'].to_hash
×
119
      attrs = attrs.slice(*user.attribute_names)
×
120
      user.assign_attributes(attrs)
×
121
    end
×
122

123
    # derive allowed params from the standard devise parameter sanitizer
124
    def whitelisted_params
×
125
      whitelist = params_for_resource(:sign_up)
×
126

127
      whitelist.inject({}) do |coll, key|
×
128
        param = omniauth_params[key.to_s]
×
129
        coll[key] = param if param
×
130
        coll
×
131
      end
×
132
    end
×
133

134
    def resource_class(mapping = nil)
×
135
      return @resource_class if defined?(@resource_class)
×
136

137
      constant_name = omniauth_params['resource_class'].presence || params['resource_class'].presence
×
138
      @resource_class = ObjectSpace.each_object(Class).detect { |cls| cls.to_s == constant_name && cls.pretty_print_inspect.starts_with?(constant_name) }
×
139
      raise 'No resource_class found' if @resource_class.nil?
×
140

141
      @resource_class
×
142
    end
×
143

144
    def resource_name
×
145
      resource_class
×
146
    end
×
147

148
    def unsafe_auth_origin_url
×
149
      omniauth_params['auth_origin_url'] || omniauth_params['origin']
×
150
    end
×
151

152

153
    def auth_origin_url
×
154
      if unsafe_auth_origin_url && blacklisted_redirect_url?(unsafe_auth_origin_url)
×
155
        return nil
×
156
      end
×
157
      return unsafe_auth_origin_url
×
158
    end
×
159

160
    # in the success case, omniauth_window_type is in the omniauth_params.
161
    # in the failure case, it is in a query param.  See monkey patch above
162
    def omniauth_window_type
×
163
      omniauth_params.nil? ? params['omniauth_window_type'] : omniauth_params['omniauth_window_type']
×
164
    end
×
165

166
    # this session value is set by the redirect_callbacks method. its purpose
167
    # is to persist the omniauth auth hash value thru a redirect. the value
168
    # must be destroyed immediately after it is accessed by omniauth_success
169
    def auth_hash
×
170
      @_auth_hash ||= session.delete('dta.omniauth.auth')
×
171
    end
×
172

173
    # ensure that this controller responds to :devise_controller? conditionals.
174
    # this is used primarily for access to the parameter sanitizers.
175
    def assert_is_devise_resource!
×
176
      true
×
177
    end
×
178

179
    def set_random_password
×
180
      # set crazy password for new oauth users. this is only used to prevent
181
      # access via email sign-in.
182
      p = SecureRandom.urlsafe_base64(nil, false)
×
183
      @resource.password = p
×
184
      @resource.password_confirmation = p
×
185
    end
×
186

187
    def create_auth_params
×
188
      @auth_params = {
×
189
        auth_token: @token.token,
×
190
        client_id:  @token.client,
×
191
        uid:        @resource.uid,
×
192
        expiry:     @token.expiry,
×
193
        config:     @config
×
194
      }
×
195
      @auth_params.merge!(oauth_registration: true) if @oauth_registration
×
196
      @auth_params
×
197
    end
×
198

199
    def set_token_on_resource
×
200
      @config = omniauth_params['config_name']
×
201
      @token  = @resource.create_token
×
202
    end
×
203

204
    def render_error_not_allowed_auth_origin_url
×
205
      message = I18n.t('devise_token_auth.omniauth.not_allowed_redirect_url', redirect_url: unsafe_auth_origin_url)
×
206
      render_data_or_redirect('authFailure', error: message)
×
207
    end
×
208

209
    def render_data(message, data)
×
210
      @data = data.merge(message: ActionController::Base.helpers.sanitize(message))
×
211
      render layout: nil, template: 'devise_token_auth/omniauth_external_window'
×
212
    end
×
213

214
    def render_data_or_redirect(message, data, user_data = {})
×
215

216
      # We handle inAppBrowser and newWindow the same, but it is nice
217
      # to support values in case people need custom implementations for each case
218
      # (For example, nbrustein does not allow new users to be created if logging in with
219
      # an inAppBrowser)
220
      #
221
      # See app/views/devise_token_auth/omniauth_external_window.html.erb to understand
222
      # why we can handle these both the same.  The view is setup to handle both cases
223
      # at the same time.
224
      if ['inAppBrowser', 'newWindow'].include?(omniauth_window_type)
×
225
        render_data(message, user_data.merge(data))
×
226

227
      elsif auth_origin_url # default to same-window implementation, which forwards back to auth_origin_url
×
228

229
        # build and redirect to destination url
230
        redirect_to DeviseTokenAuth::Url.generate(auth_origin_url, data.merge(blank: true).merge(redirect_options))
×
231
      else
×
232

233
        # there SHOULD always be an auth_origin_url, but if someone does something silly
234
        # like coming straight to this url or refreshing the page at the wrong time, there may not be one.
235
        # In that case, just render in plain text the error message if there is one or otherwise
236
        # a generic message.
237
        fallback_render data[:error] || 'An error occurred'
×
238
      end
×
239
    end
×
240

241
    def fallback_render(text)
×
242
        render inline: %Q(
×
243

244
            <html>
×
245
                    <head></head>
×
246
                    <body>
×
247
                            #{ActionController::Base.helpers.sanitize(text)}
248
                    </body>
×
249
            </html>)
×
250
    end
×
251

252
    def handle_new_resource
×
253
      @oauth_registration = true
×
254
      set_random_password
×
255
    end
×
256

257
    def assign_whitelisted_params?
×
258
      true
×
259
    end
×
260

261
    def get_resource_from_auth_hash
×
262
      # find or create user by provider and provider uid
263
      @resource = resource_class.where(
×
264
        uid: auth_hash['uid'],
×
265
        provider: auth_hash['provider']
×
266
      ).first_or_initialize
×
267

268
      if @resource.new_record?
×
269
        handle_new_resource
×
270
      end
×
271

272
      # sync user info with provider, update/generate auth token
273
      assign_provider_attrs(@resource, auth_hash)
×
274

275
      # assign any additional (whitelisted) attributes
276
      if assign_whitelisted_params?
×
277
        extra_params = whitelisted_params
×
278
        @resource.assign_attributes(extra_params) if extra_params
×
279
      end
×
280

281
      @resource
×
282
    end
×
283
  end
×
284
end
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc