omniauth-shopify-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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