omniauth-shopify-app 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,486 @@
1
+ require_relative 'test_helper'
2
+
3
+ class IntegrationTest < Minitest::Test
4
+ def setup
5
+ build_app(scope: OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
6
+ end
7
+
8
+ def teardown
9
+ FakeWeb.clean_registry
10
+ FakeWeb.last_request = nil
11
+ end
12
+
13
+ def test_authorize
14
+ response = authorize('snowdevil.myshopify.com')
15
+ assert_equal 302, response.status
16
+ assert_match %r{\A#{Regexp.quote(shopify_authorize_url)}}, response.location
17
+ redirect_params = Rack::Utils.parse_query(URI(response.location).query)
18
+ assert_equal "123", redirect_params['client_id']
19
+ assert_equal "https://app.example.com/auth/shopify/callback", redirect_params['redirect_uri']
20
+ assert_equal "read_products", redirect_params['scope']
21
+ assert_nil redirect_params['grant_options']
22
+ end
23
+
24
+ def test_authorize_includes_auth_type_when_per_user_permissions_are_requested
25
+ build_app(per_user_permissions: true)
26
+ response = authorize('snowdevil.myshopify.com')
27
+ redirect_params = Rack::Utils.parse_query(URI(response.location).query)
28
+ assert_equal 'per-user', redirect_params['grant_options[]']
29
+ end
30
+
31
+ def test_authorize_overrides_site_with_https_scheme
32
+ build_app setup: lambda { |env|
33
+ params = Rack::Utils.parse_query(env['QUERY_STRING'])
34
+ env['omniauth.strategy'].options[:client_options][:site] = "http://#{params['shop']}"
35
+ }
36
+
37
+ response = request.get('https://app.example.com/auth/shopify?shop=snowdevil.myshopify.com')
38
+ assert_match %r{\A#{Regexp.quote(shopify_authorize_url)}}, response.location
39
+ end
40
+
41
+ def test_site_validation
42
+ code = SecureRandom.hex(16)
43
+
44
+ [
45
+ 'foo.example.com', # shop doesn't end with .myshopify.com
46
+ 'http://snowdevil.myshopify.com', # shop contains protocol
47
+ 'snowdevil.myshopify.com/path', # shop contains path
48
+ 'user@snowdevil.myshopify.com', # shop contains user
49
+ 'snowdevil.myshopify.com:22', # shop contains port
50
+ ].each do |shop, valid|
51
+ @shop = shop
52
+ response = authorize(shop)
53
+ assert_auth_failure(response, 'invalid_site')
54
+
55
+ response = callback(sign_with_new_secret(shop: shop, code: code))
56
+ assert_auth_failure(response, 'invalid_site')
57
+ end
58
+ end
59
+
60
+ def test_callback
61
+ access_token = SecureRandom.hex(16)
62
+ code = SecureRandom.hex(16)
63
+ expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
64
+
65
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
66
+
67
+ assert_callback_success(response, access_token, code)
68
+ end
69
+
70
+ def test_callback_with_legacy_signature
71
+ build_app scope: OmniAuth::Strategies::Shopify::DEFAULT_SCOPE
72
+ access_token = SecureRandom.hex(16)
73
+ code = SecureRandom.hex(16)
74
+ expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
75
+
76
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]).merge(signature: 'ignored'))
77
+
78
+ assert_callback_success(response, access_token, code)
79
+ end
80
+
81
+ def test_callback_custom_params
82
+ access_token = SecureRandom.hex(16)
83
+ code = SecureRandom.hex(16)
84
+
85
+ expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
86
+
87
+ now = Time.now.to_i
88
+ params = { shop: 'snowdevil.myshopify.com', code: code, timestamp: now, next: '/products?page=2&q=red%20shirt', state: opts["rack.session"]["omniauth.state"] }
89
+ encoded_params = "code=#{code}&next=%2Fproducts%3Fpage%3D2%26q%3Dred%2520shirt&shop=snowdevil.myshopify.com&state=#{opts["rack.session"]["omniauth.state"]}&timestamp=#{now}"
90
+ params[:hmac] = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @secret, encoded_params)
91
+
92
+ response = callback(params)
93
+
94
+ assert_callback_success(response, access_token, code)
95
+ end
96
+
97
+ def test_callback_with_spaces_in_scope
98
+ build_app scope: 'write_products, read_orders'
99
+ access_token = SecureRandom.hex(16)
100
+ code = SecureRandom.hex(16)
101
+ expect_access_token_request(access_token, 'read_orders,write_products')
102
+
103
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
104
+
105
+ assert_callback_success(response, access_token, code)
106
+ end
107
+
108
+ def test_callback_rejects_invalid_hmac
109
+ @secret = 'wrong_secret'
110
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: SecureRandom.hex(16)))
111
+
112
+ assert_auth_failure(response, 'invalid_signature')
113
+ end
114
+
115
+ def test_callback_rejects_old_timestamps
116
+ expired_timestamp = Time.now.to_i - OmniAuth::Strategies::Shopify::CODE_EXPIRES_AFTER - 1
117
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: SecureRandom.hex(16), timestamp: expired_timestamp))
118
+
119
+ assert_auth_failure(response, 'invalid_signature')
120
+ end
121
+
122
+ def test_callback_rejects_missing_hmac
123
+ code = SecureRandom.hex(16)
124
+
125
+ response = callback(shop: 'snowdevil.myshopify.com', code: code, timestamp: Time.now.to_i)
126
+
127
+ assert_auth_failure(response, 'invalid_signature')
128
+ end
129
+
130
+ def test_callback_rejects_body_params
131
+ code = SecureRandom.hex(16)
132
+ params = sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code)
133
+ body = Rack::Utils.build_nested_query(unsigned: 'value')
134
+
135
+ response = request.get("https://app.example.com/auth/shopify/callback?#{Rack::Utils.build_query(params)}",
136
+ input: body,
137
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded',
138
+ 'rack.session' => {
139
+ 'shopify.omniauth_params' => { shop: 'snowdevil.myshopify.com' }
140
+ })
141
+
142
+ assert_auth_failure(response, 'invalid_signature')
143
+ end
144
+
145
+ def test_provider_options
146
+ build_app scope: 'read_products,read_orders,write_content',
147
+ callback_path: '/admin/auth/legacy/callback',
148
+ myshopify_domain: 'myshopify.dev:3000',
149
+ setup: lambda { |env|
150
+ shop = Rack::Request.new(env).GET['shop']
151
+ shop += ".myshopify.dev:3000" unless shop.include?(".")
152
+ env['omniauth.strategy'].options[:client_options][:site] = "https://#{shop}"
153
+ }
154
+
155
+ response = request.get("https://app.example.com/auth/shopify?shop=snowdevil.myshopify.dev:3000")
156
+ assert_equal 302, response.status
157
+ assert_match %r{\A#{Regexp.quote("https://snowdevil.myshopify.dev:3000/admin/oauth/authorize?")}}, response.location
158
+ redirect_params = Rack::Utils.parse_query(URI(response.location).query)
159
+ assert_equal 'read_products,read_orders,write_content', redirect_params['scope']
160
+ assert_equal 'https://app.example.com/admin/auth/legacy/callback', redirect_params['redirect_uri']
161
+ end
162
+
163
+ def test_default_setup_reads_shop_from_session
164
+ build_app
165
+ response = authorize('snowdevil.myshopify.com')
166
+ assert_equal 302, response.status
167
+ assert_match %r{\A#{Regexp.quote("https://snowdevil.myshopify.com/admin/oauth/authorize?")}}, response.location
168
+ redirect_params = Rack::Utils.parse_query(URI(response.location).query)
169
+ assert_equal 'https://app.example.com/auth/shopify/callback', redirect_params['redirect_uri']
170
+ end
171
+
172
+ def test_default_setup_reads_shop_from_params
173
+ build_app
174
+
175
+ response = request.get('https://app.example.com/auth/shopify?shop=snowdevil.myshopify.com', opts)
176
+
177
+ assert_equal 302, response.status
178
+ assert_match %r{\A#{Regexp.quote("https://snowdevil.myshopify.com/admin/oauth/authorize?")}}, response.location
179
+ redirect_params = Rack::Utils.parse_query(URI(response.location).query)
180
+ assert_equal 'https://app.example.com/auth/shopify/callback', redirect_params['redirect_uri']
181
+ end
182
+
183
+ def test_unnecessary_read_scopes_are_removed
184
+ build_app scope: 'read_content,read_products,write_products,unauthenticated_read_checkouts,unauthenticated_write_checkouts',
185
+ callback_path: '/admin/auth/legacy/callback',
186
+ myshopify_domain: 'myshopify.dev:3000',
187
+ setup: lambda { |env|
188
+ shop = Rack::Request.new(env).GET['shop']
189
+ env['omniauth.strategy'].options[:client_options][:site] = "https://#{shop}"
190
+ }
191
+
192
+ response = request.get("https://app.example.com/auth/shopify?shop=snowdevil.myshopify.dev:3000")
193
+ assert_equal 302, response.status
194
+ redirect_params = Rack::Utils.parse_query(URI(response.location).query)
195
+ assert_equal 'read_content,write_products,unauthenticated_write_checkouts', redirect_params['scope']
196
+ end
197
+
198
+ def test_callback_with_invalid_state_fails
199
+ access_token = SecureRandom.hex(16)
200
+ code = SecureRandom.hex(16)
201
+ expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
202
+
203
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: 'invalid'))
204
+
205
+ assert_equal 302, response.status
206
+ assert_equal '/auth/failure?message=csrf_detected&strategy=shopify', response.location
207
+ end
208
+
209
+ def test_callback_with_mismatching_scope_succeeds
210
+ access_token = SecureRandom.hex(16)
211
+ code = SecureRandom.hex(16)
212
+ expect_access_token_request(access_token, 'some_invalid_scope', nil)
213
+
214
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
215
+
216
+ assert_callback_success(response, access_token, code)
217
+ end
218
+
219
+ def test_callback_with_no_scope_succeeds
220
+ access_token = SecureRandom.hex(16)
221
+ code = SecureRandom.hex(16)
222
+ expect_access_token_request(access_token, nil)
223
+
224
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
225
+
226
+ assert_callback_success(response, access_token, code)
227
+ end
228
+
229
+ def test_callback_with_missing_access_scope_succeeds
230
+ build_app scope: 'first_scope,second_scope'
231
+
232
+ access_token = SecureRandom.hex(16)
233
+ code = SecureRandom.hex(16)
234
+ expect_access_token_request(access_token, 'first_scope')
235
+
236
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
237
+
238
+ assert_callback_success(response, access_token, code)
239
+ end
240
+
241
+ def test_callback_with_extra_access_scope_succeeds
242
+ build_app scope: 'first_scope,second_scope'
243
+
244
+ access_token = SecureRandom.hex(16)
245
+ code = SecureRandom.hex(16)
246
+ expect_access_token_request(access_token, 'second_scope,first_scope,third_scope')
247
+
248
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
249
+
250
+ assert_callback_success(response, access_token, code)
251
+ end
252
+
253
+ def test_callback_with_scopes_out_of_order_works
254
+ build_app scope: 'first_scope,second_scope'
255
+
256
+ access_token = SecureRandom.hex(16)
257
+ code = SecureRandom.hex(16)
258
+ expect_access_token_request(access_token, 'second_scope,first_scope')
259
+
260
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
261
+
262
+ assert_callback_success(response, access_token, code)
263
+ end
264
+
265
+ def test_callback_with_duplicate_read_scopes_works
266
+ build_app scope: 'read_products,write_products,unauthenticated_read_products,unauthenticated_write_products'
267
+
268
+ access_token = SecureRandom.hex(16)
269
+ code = SecureRandom.hex(16)
270
+ expect_access_token_request(access_token, 'write_products,unauthenticated_write_products')
271
+
272
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
273
+
274
+ assert_callback_success(response, access_token, code)
275
+ end
276
+
277
+ def test_callback_with_extra_coma_works
278
+ build_app scope: 'read_content,,write_products,'
279
+
280
+ access_token = SecureRandom.hex(16)
281
+ code = SecureRandom.hex(16)
282
+ expect_access_token_request(access_token, 'read_content,write_products')
283
+
284
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
285
+
286
+ assert_callback_success(response, access_token, code)
287
+ end
288
+
289
+ def test_callback_when_per_user_permissions_are_present_but_not_requested
290
+ build_app(scope: 'scope', per_user_permissions: false)
291
+
292
+ access_token = SecureRandom.hex(16)
293
+ code = SecureRandom.hex(16)
294
+ expect_access_token_request(access_token, 'scope', { id: 1, email: 'bob@bobsen.com'})
295
+
296
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
297
+
298
+ assert_equal 302, response.status
299
+ assert_equal '/auth/failure?message=invalid_permissions&strategy=shopify', response.location
300
+ end
301
+
302
+ def test_callback_when_per_user_permissions_are_not_present_and_options_is_nil
303
+ build_app(scope: 'scope', per_user_permissions: nil)
304
+
305
+ access_token = SecureRandom.hex(16)
306
+ code = SecureRandom.hex(16)
307
+ expect_access_token_request(access_token, 'scope', nil)
308
+
309
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
310
+
311
+ assert_callback_success(response, access_token, code)
312
+ end
313
+
314
+ def test_callback_when_per_user_permissions_are_not_present_but_requested
315
+ build_app(scope: 'scope', per_user_permissions: true)
316
+
317
+ access_token = SecureRandom.hex(16)
318
+ code = SecureRandom.hex(16)
319
+ expect_access_token_request(access_token, 'scope', nil)
320
+
321
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
322
+
323
+ assert_equal 302, response.status
324
+ assert_equal '/auth/failure?message=invalid_permissions&strategy=shopify', response.location
325
+ end
326
+
327
+ def test_callback_works_when_per_user_permissions_are_present_and_requested
328
+ build_app(scope: 'scope', per_user_permissions: true)
329
+
330
+ access_token = SecureRandom.hex(16)
331
+ code = SecureRandom.hex(16)
332
+ expect_access_token_request(access_token, 'scope', { id: 1, email: 'bob@bobsen.com'})
333
+
334
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
335
+
336
+ assert_equal 200, response.status
337
+ end
338
+
339
+ def test_callback_when_a_session_is_present
340
+ build_app(scope: 'scope', per_user_permissions: true)
341
+
342
+ access_token = SecureRandom.hex(16)
343
+ code = SecureRandom.hex(16)
344
+ session = SecureRandom.hex
345
+ expect_access_token_request(access_token, 'scope', { id: 1, email: 'bob@bobsen.com'}, session)
346
+
347
+ response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
348
+
349
+ assert_equal 200, response.status
350
+ end
351
+
352
+ def test_callback_works_with_old_secret
353
+ build_app scope: OmniAuth::Strategies::Shopify::DEFAULT_SCOPE
354
+ access_token = SecureRandom.hex(16)
355
+ code = SecureRandom.hex(16)
356
+ expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
357
+
358
+ signed_params = sign_with_old_secret(
359
+ shop: 'snowdevil.myshopify.com',
360
+ code: code,
361
+ state: opts["rack.session"]["omniauth.state"]
362
+ )
363
+
364
+ response = callback(signed_params)
365
+
366
+ assert_callback_success(response, access_token, code)
367
+ end
368
+
369
+ def test_callback_when_creds_are_invalid
370
+ build_app scope: OmniAuth::Strategies::Shopify::DEFAULT_SCOPE
371
+
372
+ FakeWeb.register_uri(
373
+ :post,
374
+ %r{snowdevil.myshopify.com/admin/oauth/access_token},
375
+ status: [ "401", "Invalid token" ],
376
+ body: "Token is invalid or has already been requested"
377
+ )
378
+
379
+ signed_params = sign_with_new_secret(
380
+ shop: 'snowdevil.myshopify.com',
381
+ code: SecureRandom.hex(16),
382
+ state: opts["rack.session"]["omniauth.state"]
383
+ )
384
+
385
+ response = callback(signed_params)
386
+
387
+ assert_equal 302, response.status
388
+ assert_equal '/auth/failure?message=invalid_credentials&strategy=shopify', response.location
389
+ end
390
+
391
+ private
392
+
393
+ def sign_with_old_secret(params)
394
+ params = add_time(params)
395
+ encoded_params = OmniAuth::Strategies::Shopify.encoded_params_for_signature(params)
396
+ params['hmac'] = OmniAuth::Strategies::Shopify.hmac_sign(encoded_params, @old_secret)
397
+ params
398
+ end
399
+
400
+ def add_time(params)
401
+ params = params.dup
402
+ params[:timestamp] ||= Time.now.to_i
403
+ params
404
+ end
405
+
406
+ def sign_with_new_secret(params)
407
+ params = add_time(params)
408
+ encoded_params = OmniAuth::Strategies::Shopify.encoded_params_for_signature(params)
409
+ params['hmac'] = OmniAuth::Strategies::Shopify.hmac_sign(encoded_params, @secret)
410
+ params
411
+ end
412
+
413
+ def expect_access_token_request(access_token, scope, associated_user=nil, session=nil)
414
+ FakeWeb.register_uri(:post, %r{snowdevil.myshopify.com/admin/oauth/access_token},
415
+ body: JSON.dump(
416
+ access_token: access_token,
417
+ scope: scope,
418
+ associated_user: associated_user,
419
+ session: session,
420
+ ),
421
+ content_type: 'application/json')
422
+ end
423
+
424
+ def assert_callback_success(response, access_token, code)
425
+ credentials = ::Base64.decode64(FakeWeb.last_request['authorization'].split(" ", 2)[1] || "")
426
+ assert_equal "123:#{@secret}", credentials
427
+
428
+ token_request_params = Rack::Utils.parse_query(FakeWeb.last_request.body)
429
+ assert_equal code, token_request_params['code']
430
+
431
+ assert_equal 'snowdevil.myshopify.com', @omniauth_result.uid
432
+ assert_equal access_token, @omniauth_result.credentials.token
433
+ assert_equal false, @omniauth_result.credentials.expires
434
+
435
+ assert_equal 200, response.status
436
+ assert_equal "OK", response.body
437
+ end
438
+
439
+ def assert_auth_failure(response, reason)
440
+ assert_nil FakeWeb.last_request
441
+ assert_equal 302, response.status
442
+ assert_match %r{\A#{Regexp.quote("/auth/failure?message=#{reason}")}}, response.location
443
+ end
444
+
445
+ def build_app(options={})
446
+ @old_secret = '12d34s1'
447
+ @secret = '53cr3tz'
448
+ options.merge!(old_client_secret: @old_secret)
449
+ app = proc { |env|
450
+ @omniauth_result = env['omniauth.auth']
451
+ [200, {Rack::CONTENT_TYPE => "text/plain"}, "OK"]
452
+ }
453
+
454
+ opts["rack.session"]["omniauth.state"] = SecureRandom.hex(32)
455
+ app = OmniAuth::Builder.new(app) do
456
+ provider :shopify, '123', '53cr3tz' , options
457
+ end
458
+ @app = Rack::Session::Cookie.new(app, secret: SecureRandom.hex(64))
459
+ end
460
+
461
+ def shop
462
+ @shop ||= 'snowdevil.myshopify.com'
463
+ end
464
+
465
+ def authorize(shop)
466
+ @opts['rack.session']['shopify.omniauth_params'] = { shop: shop }
467
+ request.get('https://app.example.com/auth/shopify', opts)
468
+ end
469
+
470
+ def callback(params)
471
+ @opts['rack.session']['shopify.omniauth_params'] = { shop: shop }
472
+ request.get("https://app.example.com/auth/shopify/callback?#{Rack::Utils.build_query(params)}", opts)
473
+ end
474
+
475
+ def opts
476
+ @opts ||= { "rack.session" => {} }
477
+ end
478
+
479
+ def request
480
+ Rack::MockRequest.new(@app)
481
+ end
482
+
483
+ def shopify_authorize_url
484
+ "https://snowdevil.myshopify.com/admin/oauth/authorize?"
485
+ end
486
+ end
@@ -0,0 +1,16 @@
1
+ $: << File.expand_path("../../lib", __FILE__)
2
+ require 'bundler/setup'
3
+ require 'omniauth-shopify-app'
4
+
5
+ require 'minitest/autorun'
6
+ require 'minitest/focus'
7
+ require 'rack/session'
8
+ require 'fakeweb'
9
+ require 'json'
10
+ require 'active_support/core_ext/hash'
11
+ require 'byebug'
12
+
13
+ OmniAuth.config.logger = Logger.new(nil)
14
+ OmniAuth.config.allowed_request_methods = [:post, :get]
15
+
16
+ FakeWeb.allow_net_connect = false
metadata ADDED
@@ -0,0 +1,194 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-shopify-app
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Hopper Gee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oauth2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1.4'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: omniauth
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: activesupport
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: minitest
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '5.6'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '5.6'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 3.9.0
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 3.9.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: fakeweb
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.3'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.3'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rack-session
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rake
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: minitest-focus
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ description:
146
+ email:
147
+ - hopper.gee@hey.com
148
+ executables: []
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - ".github/workflows/build.yml"
153
+ - ".gitignore"
154
+ - Gemfile
155
+ - README.md
156
+ - Rakefile
157
+ - example/Gemfile
158
+ - example/config.ru
159
+ - lib/omniauth-shopify-app.rb
160
+ - lib/omniauth/shopify.rb
161
+ - lib/omniauth/shopify/encryptor.rb
162
+ - lib/omniauth/shopify/oauth_session.rb
163
+ - lib/omniauth/shopify/version.rb
164
+ - lib/omniauth/strategies/shopify.rb
165
+ - omniauth-shopify-app.gemspec
166
+ - test/integration_test.rb
167
+ - test/test_helper.rb
168
+ homepage: https://github.com/OldWayShopifyDev/omniauth-shopify-app
169
+ licenses:
170
+ - MIT
171
+ metadata:
172
+ allowed_push_host: https://rubygems.org
173
+ post_install_message:
174
+ rdoc_options: []
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: 2.1.9
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubygems_version: 3.3.26
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Shopify strategy for OmniAuth
192
+ test_files:
193
+ - test/integration_test.rb
194
+ - test/test_helper.rb