rodauth-omniauth 0.1.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: 99f2cd5c47082f18ff2b0feeea0106c104edf6454e354fb04f8c74044b7796b2
4
+ data.tar.gz: 58c096a1176cce8c2aa1ef0741c35e0ec81a36fb1d3d7bf9350b01c143daffdb
5
+ SHA512:
6
+ metadata.gz: 3ee9f500a18535a215d74d6cbc250561a7d0b61cc5818166e7bf481b74bf67dedf2547228ffbec177273715d1001cce2178e7590cadc8c21b102ef1b9585f9c5
7
+ data.tar.gz: fe0337fdc2ef82ea53c5b8514383df10d60433900deaa35fa519ffe0e846c3a5e79deead46b1e790e29a35ffac0a00a2301c6437376d8fd91832d8ee1cb49a8e
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Janko Marohnić
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,364 @@
1
+ # rodauth-omniauth
2
+
3
+ [Rodauth] feature that offers login and registration via multiple external providers using [OmniAuth]. The external identities are automatically stored in the database, and associated to the main account record.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your project:
8
+
9
+ ```sh
10
+ $ bundle add rodauth-omniauth
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ You'll first need to create the table for storing external identities:
16
+
17
+ ```rb
18
+ Sequel.migration do # class CreateAccountIdentities < ActiveRecord::Migration
19
+ change do # def change
20
+ create_table :account_identities do # create_table :account_identities do |t|
21
+ primary_key :id # t.references :account, null: false, foreign_key: { on_delete: :cascade }
22
+ foreign_key :account_id, :accounts # t.string :provider, null: false
23
+ String :provider, null: false # t.string :uid, null: false
24
+ String :uid, null: false # t.index [:provider, :uid], unique: true
25
+ unique [:provider, :uid] # end
26
+ end # end
27
+ end # end
28
+ end
29
+ ```
30
+
31
+ Then enable the `omniauth` feature and register providers in your Rodauth configuration:
32
+
33
+ ```sh
34
+ $ bundle add omniauth-facebook omniauth-twitter, omniauth-google_oauth2
35
+ ```
36
+ ```rb
37
+ plugin :rodauth do
38
+ enable :omniauth
39
+
40
+ omniauth_provider :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"], scope: "email"
41
+ omniauth_provider :twitter, ENV["TWITTER_API_KEY"], ENV["TWITTER_API_SECRET"]
42
+ omniauth_provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"], name: :google
43
+ end
44
+ ```
45
+
46
+ You can now add authentication links to your login form:
47
+
48
+ ```erb
49
+ <!-- app/views/rodauth/_login_form_footer.html.erb -->
50
+ <%== rodauth.login_form_footer_links_heading %>
51
+
52
+ <ul>
53
+ <li><%= button_to "Login via Facebook", rodauth.omniauth_request_path(:facebook), method: :post, data: { turbo: false }, class: "btn btn-link p-0" %></li>
54
+ <li><%= button_to "Login via Twitter", rodauth.omniauth_request_path(:twitter), method: :post, data: { turbo: false }, class: "btn btn-link p-0" %></li>
55
+ <li><%= button_to "Login via Google", rodauth.omniauth_request_path(:google), method: :post, data: { turbo: false }, class: "btn btn-link p-0" %></li>
56
+ <% rodauth.login_form_footer_links.each do |_, link, text| %>
57
+ <li><%= link_to text, link %></li>
58
+ <% end %>
59
+ </ul>
60
+ ```
61
+
62
+ Assuming you configured the providers correctly, you should now be able to authenticate via an external provider. The `omniauth` feature handles the callback request, automatically creating new identities and verified accounts from those identities as needed.
63
+
64
+ ```rb
65
+ DB[:accounts].all
66
+ #=> [{ id: 123, status_id: 2, email: "user@example.com" }]
67
+ DB[:account_identities].all
68
+ #=> [{ id: 456, account_id: 123, provider: "facebook", uid: "984346198764" },
69
+ # { id: 789, account_id: 123, provider: "google", uid: "5871623487134"}]
70
+ ```
71
+
72
+ Currently, provider login is required to return the user's email address, and account creation is assumed not to require additional fields that need to be entered manually. There is currently also no built-in functionality for connecting/removing external identities when signed in. Both features are planned for future versions.
73
+
74
+ ### Login
75
+
76
+ If the local account associated to the external identity exists and is unverified (e.g. it was created through normal registration), the external login will abort during the callback phase. You can change the default error flash and redirect location in this case:
77
+
78
+ ```rb
79
+ omniauth_login_unverified_account_error_flash "The account matching the external identity is currently awaiting verification"
80
+ omniauth_login_failure_redirect { require_login_redirect }
81
+ ```
82
+
83
+ ### Account creation
84
+
85
+ Since provider accounts have verified the email address, local accounts created via external logins are automatically considered verified.
86
+
87
+ If you want to use extra user information for account creation, you can do so via hooks:
88
+
89
+ ```rb
90
+ before_omniauth_create_account { account[:name] = omniauth_name }
91
+ # or
92
+ after_omniauth_create_account do
93
+ Profile.create(account_id: account_id, bio: omniauth_info["description"], image_url: omniauth_info["image"])
94
+ end
95
+ ```
96
+
97
+ When the account is closed, its external identities are automatically cleared from the database.
98
+
99
+ ### Identity data
100
+
101
+ You can also store extra data on the external identities. For example, we could override the update hash to store `info`, `credentials`, and `extra` data from the auth hash into separate columns:
102
+
103
+ ```rb
104
+ alter_table :account_identities do
105
+ add_column :info, :json, default: "{}"
106
+ add_column :credentials, :json, default: "{}"
107
+ add_column :extra, :json, default: "{}"
108
+ end
109
+ ```
110
+ ```rb
111
+ # this data will be refreshed on each login
112
+ omniauth_identity_update_hash do
113
+ {
114
+ info: omniauth_info.to_json,
115
+ credentials: omniauth_credentials.to_json,
116
+ extra: omniauth_extra.to_json,
117
+ }
118
+ end
119
+ ```
120
+
121
+ With this configuration, the identity record will be automatically synced with most recent state on each provider login. If you would like to only save provider data on first login, you can override the insert hash instead:
122
+
123
+ ```rb
124
+ # this data will be stored only on first login
125
+ omniauth_identity_insert_hash do
126
+ super().merge(
127
+ info: omniauth_info.to_json,
128
+ credentials: omniauth_credentials.to_json,
129
+ extra: omniauth_extra.to_json,
130
+ }
131
+ end
132
+ ```
133
+
134
+ ### Model associations
135
+
136
+ When using the [rodauth-model] gem, an `identities` one-to-many association will be defined on the account model:
137
+
138
+ ```rb
139
+ require "rodauth/model"
140
+
141
+ class Account < Sequel::Model
142
+ include Rodauth::Model(RodauthApp.rodauth)
143
+ end
144
+ ```
145
+ ```rb
146
+ Account.first.identities #=>
147
+ # [
148
+ # #<Account::Identity id=123 provider="facebook" uid="987434628">,
149
+ # #<Account::Identity id=456 provider="google" uid="274673644">
150
+ # ]
151
+ ```
152
+
153
+ ## Base
154
+
155
+ The `omniauth` feature builds on top of the `omniauth_base` feature, which sets up OmniAuth and routes its requests, but has no interaction with the database. So, if you would prefer to handle external logins differently, you can load just the `omniauth_base` feature, and implement your own callbacks.
156
+
157
+ ```rb
158
+ plugin :rodauth do
159
+ enable :omniauth_base
160
+
161
+ omniauth_provider :github, ENV["GITHUB_KEY"], ENV["GITHUB_SECRET"], scope: "user"
162
+ omniauth_provider :apple, ENV["CLIENT_ID"], { scope: "email name", ... }
163
+ end
164
+
165
+ route do |r|
166
+ r.rodauth # routes Rodauth and OmniAuth requests
167
+
168
+ r.get "auth", String, "callback" do
169
+ # ... handle callback request ...
170
+ end
171
+ end
172
+ ```
173
+
174
+ ### Helpers
175
+
176
+ There are various helper methods available for reading OmniAuth data:
177
+
178
+ ```rb
179
+ # retrieving the auth hash:
180
+ rodauth.omniauth_auth #=> { "provider" => "twitter", "uid" => "49823724", "info" => { "email" => "user@example.com", "name" => "John Smith", ... }, ... }
181
+ rodauth.omniauth_provider #=> "twitter"
182
+ rodauth.omniauth_uid #=> "49823724"
183
+ rodauth.omniauth_info #=> { "email" => "user@example.com", "name" => "John Smith", ... }
184
+ rodauth.omniauth_email #=> "user@example.com"
185
+ rodauth.omniauth_name #=> "John Smith"
186
+ rodauth.omniauth_credentials #=> returns "credentials" value from auth hash
187
+ rodauth.omniauth_extra #=> returns "extra" value from auth hash
188
+
189
+ # retrieving additional information:
190
+ rodauth.omniauth_strategy #=> #<OmniAuth::Strategies::Twitter ...>
191
+ rodauth.omniauth_params # returns GET params from request phase
192
+ rodauth.omniauth_origin # returns origin from request phase (usually referrer)
193
+
194
+ # retrieving error information in case of a login failure
195
+ rodauth.omniauth_error # returns the exception object
196
+ rodauth.omniauth_error_type # returns the error type symbol (strategy-specific)
197
+ rodauth.omniauth_error_strategy # returns the strategy for which the error occured
198
+ ```
199
+
200
+ ### URLs
201
+
202
+ URL helpers are provided as well:
203
+
204
+ ```rb
205
+ rodauth.prefix #=> "/user"
206
+ rodauth.omniauth_prefix #=> "/auth"
207
+
208
+ rodauth.omniauth_request_route #=> "auth/facebook"
209
+ rodauth.omniauth_request_path #=> "/user/auth/facebook"
210
+ rodauth.omniauth_request_url #=> "https://example.com/user/auth/facebook"
211
+
212
+ rodauth.omniauth_callback_route #=> "auth/facebook/callback"
213
+ rodauth.omniauth_callback_path #=> "/user/auth/facebook/callback"
214
+ rodauth.omniauth_callback_url #=> "https://example.com/user/auth/facebook/callback"
215
+ ```
216
+
217
+ The prefix for the OmniAuth app can be changed:
218
+
219
+ ```rb
220
+ omniauth_prefix "/external"
221
+ ```
222
+
223
+ ### Hooks
224
+
225
+ OmniAuth configuration has global hooks for various phases, which get called with the Rack env hash. Here you can use corresponding Rodauth configuration methods, which are executed in Rodauth context:
226
+
227
+ ```rb
228
+ omniauth_setup { ... }
229
+ omniauth_request_validation_phase { ... }
230
+ omniauth_before_request_phase { ... }
231
+ omniauth_before_callback_phase { ... }
232
+ omniauth_on_failure { ... }
233
+ ```
234
+
235
+ You can use the `omniauth_strategy` helper method to differentiate between strategies:
236
+
237
+ ```rb
238
+ omniauth_setup do
239
+ if omniauth_strategy.name == :github
240
+ omniauth_strategy.options[:foo] = "bar"
241
+ end
242
+ end
243
+ ```
244
+
245
+ #### Failure
246
+
247
+ The default reaction to login failure is to redirect to the root page with an error flash message. You can change the configuration:
248
+
249
+ ```rb
250
+ omniauth_failure_error_flash "There was an error logging in with the external provider"
251
+ omniauth_failure_redirect { default_redirect }
252
+ omniauth_failure_error_status 500 # for JSON API
253
+ ```
254
+
255
+ Or provide your own implementation:
256
+
257
+ ```rb
258
+ omniauth_on_failure do
259
+ case omniauth_error_type
260
+ when :no_authorization_code then ...
261
+ when :uknown_signature_algorithm then ...
262
+ else ...
263
+ end
264
+ end
265
+ ```
266
+
267
+ #### CSRF protection
268
+
269
+ The default request validation phase uses Rodauth's configured CSRF protection, so there is no need for external gems such as `omniauth-rails_csrf_protection`.
270
+
271
+ ### Inheritance
272
+
273
+ The registered providers are inherited between Rodauth auth classes, so you can have fine-grained configuration for different account types.
274
+
275
+ ```rb
276
+ class RodauthBase < Rodauth::Auth
277
+ configure do
278
+ enable :omniauth_base
279
+ omniauth_provider :google_oauth2, ...
280
+ end
281
+ end
282
+ ```
283
+ ```rb
284
+ class RodauthMain < RodauthBase
285
+ configure do
286
+ omniauth_provider :facebook, ...
287
+ end
288
+ end
289
+ ```
290
+ ```rb
291
+ class RodauthAdmin < RodauthBase
292
+ configure do
293
+ omniauth_provider :twitter, ...
294
+ omniauth_provider :github, ...
295
+ end
296
+ end
297
+ ```
298
+ ```rb
299
+ class RodauthApp < Roda
300
+ plugin :rodauth, auth_class: RodauthMain
301
+ plugin :rodauth, auth_class: RodauthAdmin, name: :admin
302
+ end
303
+ ```
304
+ ```rb
305
+ rodauth.omniauth_providers #=> [:google_oauth2, :facebook]
306
+ rodauth(:admin).omniauth_providers #=> [:google_oauth2, :twitter, :github]
307
+ ```
308
+
309
+ ### JSON
310
+
311
+ JSON requests are supported for the request and callback phases. The request phase endpoint will return the authorize URL:
312
+
313
+ ```http
314
+ POST /auth/facebook
315
+ Accept: application/json
316
+ Content-Type: application/json
317
+
318
+ 200 OK
319
+ Content-Type: application/json
320
+ { "authorize_url": "https://external.com/login" }
321
+ ```
322
+
323
+ If there was a login failure, the error type will be included in the response:
324
+
325
+ ```http
326
+ POST /auth/facebook/callback
327
+ Accept: application/json
328
+ Content-Type: application/json
329
+
330
+ 500 Internal Server Error
331
+ Content-Type: application/json
332
+ { "error_type": "some_error", "error": "There was an error logging in with the external provider" }
333
+ ```
334
+
335
+ You can change authorize URL and error type keys:
336
+
337
+ ```rb
338
+ omniauth_authorize_url_key "authorize_url"
339
+ omniauth_error_type_key "error_type"
340
+ ```
341
+
342
+ ### JWT
343
+
344
+ JWT requests are supported for the request and callback phases. OmniAuth information will be stored in JWT session data during the request phase, and restored during the callback phase, as long as the updated JWT token is passed.
345
+
346
+ ## Development
347
+
348
+ Run tests with Rake:
349
+
350
+ ```sh
351
+ $ bundle exec rake test
352
+ ```
353
+
354
+ ## License
355
+
356
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
357
+
358
+ ## Code of Conduct
359
+
360
+ Everyone interacting in the rodauth-omniauth project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/janko/rodauth-pwned/blob/master/CODE_OF_CONDUCT.md).
361
+
362
+ [Rodauth]: https://github.com/jeremyevans/rodauth
363
+ [OmniAuth]: https://github.com/omniauth/omniauth
364
+ [rodauth-model]: https://github.com/janko/rodauth-model
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "omniauth"
4
+
5
+ module Rodauth
6
+ Feature.define(:omniauth, :Omniauth) do
7
+ depends :omniauth_base, :login
8
+
9
+ before :omniauth_callback_route
10
+ before :omniauth_create_account
11
+ after :omniauth_create_account
12
+
13
+ error_flash "The account matching the external identity is currently awaiting verification", :omniauth_login_unverified_account
14
+
15
+ redirect(:omniauth_login_failure) { require_login_redirect }
16
+
17
+ auth_value_method :omniauth_identities_table, :account_identities
18
+ auth_value_method :omniauth_identities_id_column, :id
19
+ auth_value_method :omniauth_identities_account_id_column, :account_id
20
+ auth_value_method :omniauth_identities_provider_column, :provider
21
+ auth_value_method :omniauth_identities_uid_column, :uid
22
+
23
+ auth_methods(
24
+ :create_omniauth_identity,
25
+ :omniauth_identity_insert_hash,
26
+ :omniauth_identity_update_hash,
27
+ :remove_omniauth_identities,
28
+ :update_omniauth_identity,
29
+ :omniauth_save_account,
30
+ )
31
+
32
+ auth_private_methods(
33
+ :retrieve_omniauth_identity,
34
+ :account_from_omniauth_identity,
35
+ :omniauth_new_account,
36
+ )
37
+
38
+ def route_omniauth!
39
+ result = super
40
+ handle_omniauth_callback if omniauth_request?
41
+ result
42
+ end
43
+
44
+ def handle_omniauth_callback
45
+ request.is omniauth_callback_route(omniauth_provider) do
46
+ _handle_omniauth_callback
47
+ end
48
+ end
49
+
50
+ def _handle_omniauth_callback
51
+ before_omniauth_callback_route
52
+
53
+ retrieve_omniauth_identity
54
+
55
+ if !account && omniauth_identity
56
+ account_from_omniauth_identity
57
+ end
58
+
59
+ unless account
60
+ account_from_login(omniauth_email)
61
+ end
62
+
63
+ if account && !open_account?
64
+ set_redirect_error_status unopen_account_error_status
65
+ set_redirect_error_flash omniauth_login_unverified_account_error_flash
66
+ redirect omniauth_login_failure_redirect
67
+ end
68
+
69
+ transaction do
70
+ unless account
71
+ omniauth_new_account
72
+ before_omniauth_create_account
73
+ omniauth_save_account
74
+ after_omniauth_create_account
75
+ end
76
+
77
+ if omniauth_identity
78
+ update_omniauth_identity
79
+ else
80
+ create_omniauth_identity
81
+ end
82
+ end
83
+
84
+ login("omniauth")
85
+ end
86
+
87
+ def retrieve_omniauth_identity
88
+ @omniauth_identity = _retrieve_omniauth_identity(omniauth_provider, omniauth_uid)
89
+ end
90
+
91
+ def account_from_omniauth_identity
92
+ @account = _account_from_omniauth_identity
93
+ end
94
+
95
+ def omniauth_new_account
96
+ @account = _omniauth_new_account(omniauth_email)
97
+ end
98
+
99
+ def omniauth_save_account
100
+ account[account_id_column] = db[accounts_table].insert(account)
101
+ end
102
+
103
+ def remove_omniauth_identities
104
+ omniauth_account_identities_ds.delete
105
+ end
106
+
107
+ def possible_authentication_methods
108
+ methods = super
109
+ methods << "omniauth" unless methods.include?("password") || omniauth_account_identities_ds.empty?
110
+ methods
111
+ end
112
+
113
+ private
114
+
115
+ def allow_email_auth?
116
+ (defined?(super) ? super : true) && omniauth_account_identities_ds.empty?
117
+ end
118
+
119
+ attr_reader :omniauth_identity
120
+
121
+ def _omniauth_new_account(login)
122
+ acc = { login_column => login }
123
+ unless skip_status_checks?
124
+ acc[account_status_column] = account_open_status_value
125
+ end
126
+ acc
127
+ end
128
+
129
+ def create_omniauth_identity
130
+ identity_id = omniauth_identities_ds.insert(omniauth_identity_insert_hash)
131
+ @omniauth_identity = { omniauth_identities_id_column => identity_id }
132
+ end
133
+
134
+ def update_omniauth_identity(identity_id = omniauth_identity_id)
135
+ update_hash = omniauth_identity_update_hash
136
+ return if update_hash.empty?
137
+
138
+ omniauth_identities_ds
139
+ .where(omniauth_identities_id_column => identity_id)
140
+ .update(update_hash)
141
+ end
142
+
143
+ def omniauth_identity_insert_hash
144
+ {
145
+ omniauth_identities_account_id_column => account_id,
146
+ omniauth_identities_provider_column => omniauth_provider.to_s,
147
+ omniauth_identities_uid_column => omniauth_uid,
148
+ }.merge(omniauth_identity_update_hash)
149
+ end
150
+
151
+ def omniauth_identity_update_hash
152
+ {}
153
+ end
154
+
155
+ def _retrieve_omniauth_identity(provider, uid)
156
+ omniauth_identities_ds.first(
157
+ omniauth_identities_provider_column => provider.to_s,
158
+ omniauth_identities_uid_column => uid,
159
+ )
160
+ end
161
+
162
+ def _account_from_omniauth_identity
163
+ account_ds(omniauth_identity_account_id).first
164
+ end
165
+
166
+ def after_close_account
167
+ super if defined?(super)
168
+ remove_omniauth_identities
169
+ end
170
+
171
+ def omniauth_identity_id
172
+ omniauth_identity[omniauth_identities_id_column]
173
+ end
174
+
175
+ def omniauth_identity_account_id
176
+ omniauth_identity[omniauth_identities_account_id_column]
177
+ end
178
+
179
+ def omniauth_account_identities_ds(acct_id = nil)
180
+ acct_id ||= account ? account_id : session_value
181
+
182
+ omniauth_identities_ds.where(omniauth_identities_account_id_column => acct_id)
183
+ end
184
+
185
+ def omniauth_identities_ds
186
+ db[omniauth_identities_table]
187
+ end
188
+ end
189
+ end
190
+
191
+ if defined?(Rodauth::Model)
192
+ Rodauth::Model.register_association(:identities) do
193
+ { name: :identities, type: :many, table: omniauth_identities_table, key: omniauth_identities_id_column }
194
+ end
195
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "omniauth"
4
+
5
+ module Rodauth
6
+ Feature.define(:omniauth_base, :OmniauthBase) do
7
+ error_flash "There was an error logging in with the external provider", :omniauth_failure
8
+
9
+ redirect :omniauth_failure
10
+
11
+ auth_value_method :omniauth_prefix, OmniAuth.config.path_prefix
12
+ auth_value_method :omniauth_failure_error_status, 500
13
+
14
+ auth_value_method :omniauth_authorize_url_key, "authorize_url"
15
+ auth_value_method :omniauth_error_type_key, "error_type"
16
+
17
+ auth_methods(
18
+ :build_omniauth_app,
19
+ :omniauth_before_callback_phase,
20
+ :omniauth_before_request_phase,
21
+ :omniauth_on_failure,
22
+ :omniauth_request_validation_phase,
23
+ :omniauth_setup,
24
+ )
25
+
26
+ configuration_module_eval do
27
+ def omniauth_provider(provider, *args)
28
+ @auth.instance_exec { @omniauth_providers << [provider, *args] }
29
+ end
30
+ end
31
+
32
+ def post_configure
33
+ super
34
+
35
+ omniauth_app = build_omniauth_app.to_app
36
+ self.class.send(:define_method, :omniauth_app) { omniauth_app }
37
+
38
+ self.class.roda_class.plugin :run_handler
39
+ end
40
+
41
+ def route!
42
+ super
43
+ route_omniauth!
44
+ end
45
+
46
+ def route_omniauth!
47
+ omniauth_run omniauth_app
48
+ nil
49
+ end
50
+
51
+ { request: "", callback: "/callback" }.each do |phase, suffix|
52
+ define_method(:"omniauth_#{phase}_url") do |provider, params = {}|
53
+ route_url(send(:"omniauth_#{phase}_route", provider), params)
54
+ end
55
+
56
+ define_method(:"omniauth_#{phase}_path") do |provider, params = {}|
57
+ route_path(send(:"omniauth_#{phase}_route", provider), params)
58
+ end
59
+
60
+ define_method(:"omniauth_#{phase}_route") do |provider|
61
+ "#{omniauth_prefix[1..-1]}/#{provider}#{suffix}"
62
+ end
63
+ end
64
+
65
+ %w[email name].each do |info_key|
66
+ define_method(:"omniauth_#{info_key}") do
67
+ omniauth_info[info_key]
68
+ end
69
+ end
70
+
71
+ %w[provider uid info credentials extra].each do |auth_key|
72
+ define_method(:"omniauth_#{auth_key}") do
73
+ omniauth_auth.fetch(auth_key)
74
+ end
75
+ end
76
+
77
+ %w[auth params strategy origin error error_type error_strategy].each do |data|
78
+ define_method(:"omniauth_#{data}") do
79
+ request.env.fetch("omniauth.#{data.tr("_", ".")}")
80
+ end
81
+ end
82
+
83
+ def omniauth_providers
84
+ self.class.instance_variable_get(:@omniauth_providers).map do |(provider, *args)|
85
+ options = args.last.is_a?(Hash) ? args.last : {}
86
+ options[:name] || provider
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def omniauth_run(app)
93
+ omniauth_around_run do
94
+ request.run app, not_found: :pass do |res|
95
+ handle_omniauth_response(res)
96
+ end
97
+ end
98
+ end
99
+
100
+ # returns rack app with all registered strategies added to the middleware stack
101
+ def build_omniauth_app
102
+ builder = OmniAuth::Builder.new
103
+ builder.options(
104
+ path_prefix: omniauth_prefix,
105
+ setup: -> (env) { env["rodauth.omniauth.instance"].send(:omniauth_setup) }
106
+ )
107
+ builder.configure do |config|
108
+ [:request_validation_phase, :before_request_phase, :before_callback_phase, :on_failure].each do |hook|
109
+ config.send(:"#{hook}=", -> (env) { env["rodauth.omniauth.instance"].send(:"omniauth_#{hook}") })
110
+ end
111
+ end
112
+ self.class.instance_variable_get(:@omniauth_providers).each do |(provider, *args)|
113
+ options = args.pop if args.last.is_a?(Hash)
114
+ builder.provider provider, *args, **(options || {})
115
+ end
116
+ builder.run -> (env) { [404, {}, []] } # pass through
117
+ builder
118
+ end
119
+
120
+ def omniauth_request_validation_phase
121
+ check_csrf if check_csrf?
122
+ end
123
+
124
+ def omniauth_before_request_phase
125
+ # can be overrridden to perform code before request phase
126
+ end
127
+
128
+ def omniauth_before_callback_phase
129
+ # can be overrridden to perform code before callback phase
130
+ end
131
+
132
+ def omniauth_setup
133
+ # can be overridden to setup the strategy
134
+ end
135
+
136
+ def omniauth_on_failure
137
+ if features.include?(:json) && use_json?
138
+ json_response[omniauth_error_type_key] = omniauth_error_type
139
+ end
140
+
141
+ set_redirect_error_status omniauth_failure_error_status
142
+ set_redirect_error_flash omniauth_failure_error_flash
143
+ redirect omniauth_failure_redirect
144
+ end
145
+
146
+ def omniauth_around_run
147
+ set_omniauth_rodauth do
148
+ set_omniauth_session do
149
+ yield
150
+ end
151
+ end
152
+ end
153
+
154
+ # Ensures the OmniAuth app uses the same session as Rodauth.
155
+ def set_omniauth_session(&block)
156
+ if features.include?(:jwt) && use_jwt?
157
+ set_omniauth_jwt_session(&block)
158
+ else
159
+ session # ensure "rack.session" is set when roda sessions plugin is used
160
+ yield
161
+ end
162
+ end
163
+
164
+ # Makes OmniAuth strategies use the JWT session hash.
165
+ def set_omniauth_jwt_session
166
+ rack_session = request.env["rack.session"]
167
+ request.env["rack.session"] = session
168
+ yield
169
+ ensure
170
+ request.env["rack.session"] = rack_session
171
+ end
172
+
173
+ # Makes the Rodauth instance accessible inside OmniAuth strategies
174
+ # and callbacks.
175
+ def set_omniauth_rodauth
176
+ request.env["rodauth.omniauth.instance"] = self
177
+ yield
178
+ ensure
179
+ request.env.delete("rodauth.omniauth.instance")
180
+ end
181
+
182
+ # Returns authorization URL when using the JSON feature.
183
+ def handle_omniauth_response(res)
184
+ return unless features.include?(:json) && use_json?
185
+
186
+ if res[0] == 302
187
+ json_response[omniauth_authorize_url_key] = res[1]["Location"]
188
+ return_json_response
189
+ end
190
+ end
191
+
192
+ def omniauth_request?
193
+ request.env.key?("omniauth.strategy")
194
+ end
195
+
196
+ def self.included(auth)
197
+ auth.extend ClassMethods
198
+ auth.instance_variable_set(:@omniauth_providers, [])
199
+ end
200
+
201
+ module ClassMethods
202
+ def inherited(subclass)
203
+ super
204
+ subclass.instance_variable_set(:@omniauth_providers, @omniauth_providers.clone)
205
+ end
206
+
207
+ def freeze
208
+ super
209
+ @omniauth_providers.freeze
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,31 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "rodauth-omniauth"
3
+ spec.version = "0.1.0"
4
+ spec.authors = ["Janko Marohnić"]
5
+ spec.email = ["janko@hey.com"]
6
+
7
+ spec.summary = "Rodauth extension for logging in and creating account via OmniAuth authentication."
8
+ spec.description = "Rodauth extension for logging in and creating account via OmniAuth authentication."
9
+ spec.homepage = "https://github.com/janko/rodauth-omniauth"
10
+ spec.license = "MIT"
11
+
12
+ spec.required_ruby_version = ">= 2.3"
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = spec.homepage
16
+
17
+ spec.files = Dir["README.md", "LICENSE.txt", "*.gemspec", "lib/**/*"]
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "rodauth", "~> 2.0"
21
+ spec.add_dependency "omniauth", "~> 2.0"
22
+
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "minitest-hooks"
25
+ spec.add_development_dependency "tilt"
26
+ spec.add_development_dependency "bcrypt"
27
+ spec.add_development_dependency "mail"
28
+ spec.add_development_dependency "net-smtp"
29
+ spec.add_development_dependency "capybara"
30
+ spec.add_development_dependency "jwt"
31
+ end
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rodauth-omniauth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Janko Marohnić
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-11-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rodauth
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: omniauth
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-hooks
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tilt
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
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: bcrypt
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
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: :development
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: :development
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: capybara
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: jwt
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: Rodauth extension for logging in and creating account via OmniAuth authentication.
154
+ email:
155
+ - janko@hey.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - LICENSE.txt
161
+ - README.md
162
+ - lib/rodauth/features/omniauth.rb
163
+ - lib/rodauth/features/omniauth_base.rb
164
+ - rodauth-omniauth.gemspec
165
+ homepage: https://github.com/janko/rodauth-omniauth
166
+ licenses:
167
+ - MIT
168
+ metadata:
169
+ homepage_uri: https://github.com/janko/rodauth-omniauth
170
+ source_code_uri: https://github.com/janko/rodauth-omniauth
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '2.3'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubygems_version: 3.3.3
187
+ signing_key:
188
+ specification_version: 4
189
+ summary: Rodauth extension for logging in and creating account via OmniAuth authentication.
190
+ test_files: []