shopify-sinatra-app 0.12.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4dc86b5d946b17ee4bde67fbbc9c6ce4bdae741bfd10b34eb5b9eef269d8d30a
4
- data.tar.gz: 3512d0ee6d5722ff8277256e5333e2ca779cd7381c0807add32e2787bfe3834d
3
+ metadata.gz: 46049e2afcd7dce3a1d3f115ba665a731d4fcb1b5344dcc9ff84649cfd30beda
4
+ data.tar.gz: 2ba425471b1cbb5e40f91178d646d8c0dd4da456321518a0772afb7ec4359058
5
5
  SHA512:
6
- metadata.gz: 3cef0cd037591e3d7d19d13e65732b03587e1761dbe2dab73e31c95477d327332644a71aab4f898d7f3fc3cf45c2e3ca9ee7b8ec352862499b100f636395d69c
7
- data.tar.gz: cbeeef8e569450e84ed861d8bd8ef53b0d1770b7d45256a5e4f73eb58de38ac225f2c07a7fd70360fc75d41fa3dc1adf6d9a4cc16ffeb3c3e534af3c188d03f1
6
+ metadata.gz: c7223a2223ba1a71865bdbf05ef3d20585723da858cc99b2ac3872c13c29a3cb6db4e03502a8412f329393e6458e4d2c8038355abcd81247992bd44cf4ff8826
7
+ data.tar.gz: 81f547ba807a56f66802482746ff9a7d6d4ccacf7c9c5d3650491e3cabde7280fed22ce576b42a901f0ce5a57f36cef355108ac12f82223aeca33b9dc8ca1ac3
@@ -0,0 +1,15 @@
1
+ version: 2.1
2
+ orbs:
3
+ ruby: circleci/ruby@0.1.2
4
+
5
+ jobs:
6
+ build:
7
+ docker:
8
+ - image: circleci/ruby:2.6.6
9
+ executor: ruby/default
10
+ steps:
11
+ - checkout
12
+ - ruby/bundle-install
13
+ - run:
14
+ name: Tests
15
+ command: ./bin/ci.sh
@@ -0,0 +1,14 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "09:00"
8
+ timezone: America/Toronto
9
+ open-pull-requests-limit: 3
10
+ ignore:
11
+ - dependency-name: omniauth
12
+ versions:
13
+ - 2.0.2
14
+ - 2.0.3
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ 1.0.0
2
+ -----
3
+ * Update to omniauth 2.0.4 and add rack protection authenticity token
4
+ * All forms will need an authenticity_token param added like in the login view
5
+ * Refactored the provided routes to be simpler. Now only login and logout (no more install)
6
+ * shopify_webhook now defines the route because it needs to also configure ignoring csrf for the route
7
+ * refactored other internal methods to simplify and reduce surface area
8
+ * Removed heroku rake tasks. They are out of date and don't encourage best practises
9
+
1
10
  0.12.0
2
11
  ------
3
12
  * Update to use the Shopify AppBridge instead of the ESDK
data/README.md CHANGED
@@ -58,7 +58,10 @@ This will create a new skeleton shopify-sinatra-app. The generator will create s
58
58
 
59
59
  ### Setting the app to use your Shopify API credentials
60
60
 
61
- You'll need to create a Shopify Partner Account and a new application. You can make an account [here](http://www.shopify.ca/partners) and see this [tutorial](http://docs.shopify.com/api/the-basics/getting-started) for creating a new application. This app uses the default redirect_uri from omniauth `<your domain>/auth/shopify/callback` so set it accordingly when creating your app.
61
+ You'll need to create a Shopify Partner Account and a new application. You can make an account [here](http://www.shopify.ca/partners) and see this [tutorial](http://docs.shopify.com/api/the-basics/getting-started) for creating a new application. The requires 2 redirects configured:
62
+
63
+ * the default redirect_uri from omniauth `<your domain>/auth/shopify/callback`
64
+ * and `<your domain>/login`
62
65
 
63
66
  Note - The shopify-sinatra-app creates an embedded app! You need change the embedded setting to `enabled` in the [Shopify Partner area](https://app.shopify.com/services/partners/api_clients) for your app. If you don't want your app to be embedded then remove the related code in `layout/application.erb` and delete the `layout/_top_bar.erb` file and the references to it in the other views.
64
67
 
@@ -87,13 +90,11 @@ get '/products.json' do
87
90
  end
88
91
  ```
89
92
 
90
- **shopify_webhook** - This method is for an endpoint that receives a webhook from Shopify. Webhooks are a great way to keep your app in sync with a shop's data without polling. You can read more about webhooks [here](http://docs.shopify.com/api/tutorials/using-webhooks). This method also takes a block and yields the `shop_name` and `webhook_body` as a hash (note only works for json webhooks, don't use xml). Here is an example that listens to an order creation webhook:
93
+ **shopify_webhook** - This method defines a `post` endpoint that receives a webhook from Shopify. Webhooks are a great way to keep your app in sync with a shop's data without polling. You can read more about webhooks [here](http://docs.shopify.com/api/tutorials/using-webhooks). This method also takes a block and yields the `shop_name` and `webhook_body` as a hash (note only works for json webhooks, don't use xml). Here is an example that listens to an order creation webhook:
91
94
 
92
95
  ```ruby
93
- post '/order.json' do
94
- shopify_webhook do |shop_name, webhook_data|
95
- # do something with the data
96
- end
96
+ shopify_webhook('/order.json') do |shop_name, webhook_data|
97
+ # do something with the data
97
98
  end
98
99
  ```
99
100
 
@@ -107,14 +108,11 @@ ShopifyAPI::Base.activate_session(api_session)
107
108
 
108
109
  It's impossible to control the flow of webhooks to your app from Shopify especially if a larger store installs your app or if a shop has a flash sale. To prevent your app from getting overloaded with webhook requests it is best practise to process webhooks in a background queue and return a `200` to Shopify immediately. Ruby has several good background job frameworks that work with Sinatra including [Sidekiq](https://github.com/mperham/sidekiq) and [Resque](https://github.com/resque/resque).
109
110
 
110
-
111
111
  **after_shopify_auth** - This is a private method provided with the framework that gets called whenever the app is authorized. You should fill this method in with anything you need to initialize, for example webhooks and services on Shopify or any other database models you have created specific to a shop. Note that this method will be called anytime the auth flow is completed so this method should be idempotent (running it twice has the same effect as running it once).
112
112
 
113
- **logout** - This method clears the current session
114
-
115
113
  shopify-sinatra-app includes sinatra/activerecord for creating models that can be persisted in the database. You might want to read more about sinatra/activerecord and the methods it makes available to you: [https://github.com/janko-m/sinatra-activerecord](https://github.com/janko-m/sinatra-activerecord)
116
114
 
117
- shopify-sinatra-app also includes `rack-flash3` and the flash messages are forwarded to the Shopify Embedded App SDK (see the code in `views/layouts/application.erb`). Flash messages are useful for signalling to your users that a request was successful without changing the page. The following is an example of how to use a flash message in a route:
115
+ shopify-sinatra-app also includes `sinatra-flash` and the flash messages are forwarded to the Shopify Embedded App SDK (see the code in `views/layouts/application.erb`). Flash messages are useful for signalling to your users that a request was successful without changing the page. The following is an example of how to use a flash message in a route:
118
116
 
119
117
  ```ruby
120
118
  post '/flash_message' do
@@ -128,17 +126,17 @@ note - a flash must be followed by a redirect or it won't work!
128
126
 
129
127
  Developing
130
128
  ----------
131
- The embedded app sdk won't load non https content so you'll need to use a forwarding service like [ngrok](https://ngrok.com/) or [forwardhq](https://forwardhq.com/). Set your application url in the [Shopify Partner area](https://app.shopify.com/services/partners/api_clients) to your forwarded url and set the redirect_uri to your forwarded url + `/auth/shopify/callback` which will allow you to install your app on a live shop while running it locally.
129
+ The embedded app sdk won't load non https content so you'll need to use a real domain or a forwarding service like [ngrok](https://ngrok.com/). Set your application url in the [Shopify Partner area](https://app.shopify.com/services/partners/api_clients) to your forwarded url and set the redirect_uri to your forwarded url + `/auth/shopify/callback` which will allow you to install your app on a live shop while running it locally.
132
130
 
133
- To run the app locally we use `foreman` which comes with the [Heroku Toolbelt](https://devcenter.heroku.com/articles/quickstart). Foreman handles running our application and setting our credentials as environment variables. To run the application type:
131
+ To run the app locally we use [overmind](https://github.com/DarthSim/overmind) a tool for running multiple process and setting our credentials as environment variables. To run the application run:
134
132
 
135
133
  ```
136
- PORT=4567 foreman run web
134
+ overmind start
137
135
  ```
138
136
 
139
- Note - we use `foreman run ...` not `foreman start ...` because we only want to start the single process that is our app. This means if you add a debugger in your app it will trigger properly in the command line when the debugger is hit. If you don't have any debuggers feel free to use `foreman start -p 4567`.
137
+ To connect to a single process to use a debugger/break point use `overmind connect <process>`
140
138
 
141
- To debug your app simply add `require 'byebug'` at the top and then type `byebug` where you would like to drop into an interactive session. You may also want to try out [Pry](http://pryrepl.org/).
139
+ To debug your app add `require 'byebug'` at the top and then add `byebug` to your code where you would like to drop into an interactive session. You may also want to try out [Pry](http://pryrepl.org/).
142
140
 
143
141
  If you are testing webhooks locally make sure they also go through the forwarded url and not `localhost`.
144
142
 
@@ -158,60 +156,6 @@ bundle exec rake test
158
156
  Checkout the contents of the `app_test.rb` file and the `test_helper.rb` and modify them as you add functionality to your app. You can also check the tests of other apps using this framework to see more about how to write tests for your own app.
159
157
 
160
158
 
161
- Deploying
162
- ---------
163
-
164
- This template was created with deploying to Heroku in mind. Heroku is a cloud based app hosting provider that makes it easy to get an application into a product environment.
165
-
166
- Before you can get started with Heroku you need to create a git repo for you application:
167
-
168
- ```
169
- git init
170
- git add .
171
- git commit -m "initial commit"
172
- ```
173
-
174
- Now you can create a new heroku application. Download the [Heroku Toolbelt](https://devcenter.heroku.com/articles/quickstart) and run the following command to create a new application:
175
-
176
- ```
177
- heroku apps:create <your new app name>
178
- ```
179
-
180
- You will also need to add the following (free) add-ons to your new Heroku app:
181
-
182
- ```
183
- heroku addons:add heroku-postgresql
184
- ```
185
-
186
- Now we can deploy the new application to Heroku. Deploying to Heroku is as simple as pushing the code using git:
187
-
188
- ```
189
- git push heroku master
190
- ```
191
-
192
- A `rake deploy2heroku` command is included in the generated Rakefile which does just this.
193
-
194
- Now that our application is deployed we need to run `rake db:migrate` to initialize our database on Heroku. To do this run:
195
-
196
- ```
197
- heroku run rake db:migrate
198
- ```
199
-
200
- We also need to set our environment variables on Heroku. The environment variables are stored in `.env` and are not tracked by git. This is to protect your credentials in the case of a source control breach. Heroku provides a command to set environment variables: `heroku config:set VAR=foo`. In the generated Rakefile there is a helper method that will properly set all the variables in your `.env` file:
201
-
202
- ```
203
- rake creds2heroku
204
- ```
205
-
206
- and make sure you have at least 1 dyno for web:
207
-
208
- ```
209
- heroku scale web=1
210
- ```
211
-
212
- Make sure you set your shopify apps url to your Heroku app url (and make sure to use the `https` version or else the Embedded App SDK won't work) in the Shopify Partner area https://app.shopify.com/services/partners/api_clients.
213
-
214
-
215
159
  Apps using this framework
216
160
  -------------------------
217
161
 
@@ -1,13 +1,10 @@
1
1
  #!/bin/bash
2
2
 
3
+ set -e
4
+
3
5
  cd example
4
6
 
5
7
  bundle install
6
8
  bundle exec rake db:migrate
7
9
  bundle exec rake test:prepare
8
10
  bundle exec rake test
9
- EXIT_CODE=$?
10
-
11
- cd ../../..
12
-
13
- exit $EXIT_CODE
data/example/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
1
  source 'https://rubygems.org'
2
- ruby '2.6.3'
2
+ ruby '2.6.6'
3
3
 
4
4
  gem 'shopify-sinatra-app', path: '../'
5
5
  gem 'sinatra-activerecord'
@@ -16,8 +16,6 @@ end
16
16
 
17
17
  group :development do
18
18
  gem 'rake', '>= 12.3.3'
19
- gem 'foreman'
20
- gem 'dotenv'
21
19
  end
22
20
 
23
21
  group :test do
data/example/Rakefile CHANGED
@@ -2,24 +2,6 @@ require 'sinatra/activerecord/rake'
2
2
  require 'rake/testtask'
3
3
  require './src/app'
4
4
 
5
- task :creds2heroku do
6
- Bundler.with_clean_env do
7
- File.readlines('.env').each do |var|
8
- pipe = IO.popen("heroku config:set #{var}")
9
- while (line = pipe.gets)
10
- print line
11
- end
12
- end
13
- end
14
- end
15
-
16
- task :deploy2heroku do
17
- pipe = IO.popen('git push heroku master --force')
18
- while (line = pipe.gets)
19
- print line
20
- end
21
- end
22
-
23
5
  namespace :test do
24
6
  task :prepare do
25
7
  `RACK_ENV=test rake db:create`
data/example/config.ru CHANGED
@@ -1,7 +1,3 @@
1
- if Gem::Specification.find_all_by_name('dotenv').any?
2
- require 'dotenv'
3
- Dotenv.load
4
- end
5
-
6
1
  require './src/app'
2
+ SinatraApp.set :bind, '0.0.0.0'
7
3
  SinatraApp.run!
data/example/db/schema.rb CHANGED
@@ -2,8 +2,8 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # This file is the source Rails uses to define your schema when running `rails
6
- # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
7
  # be faster and is potentially less error prone than running all of your
8
8
  # migrations from scratch. Old migrations may fail to apply correctly if those
9
9
  # migrations use external dependencies or application code.
data/example/src/app.rb CHANGED
@@ -23,10 +23,8 @@ class SinatraApp < Sinatra::Base
23
23
  # this endpoint recieves the uninstall webhook
24
24
  # and cleans up data, add to this endpoint as your app
25
25
  # stores more data.
26
- post '/uninstall' do
27
- shopify_webhook do |shop_name, params|
28
- Shop.find_by(name: shop_name).destroy
29
- end
26
+ shopify_webhook '/uninstall' do |shop_name, params|
27
+ Shop.find_by(name: shop_name).destroy
30
28
  end
31
29
 
32
30
  private
@@ -42,18 +42,18 @@ class AppTest < Minitest::Test
42
42
  def test_root_without_session_redirects_to_install
43
43
  get '/'
44
44
  assert_equal 302, last_response.status
45
- assert_equal 'http://example.org/install', last_response.location
45
+ assert_equal 'http://example.org/login', last_response.location
46
46
  end
47
47
 
48
48
  def test_root_with_shop_redirects_to_auth
49
49
  get '/?shop=othertestshop.myshopify.com'
50
- assert_match '/auth/shopify?shop=othertestshop.myshopify.com', last_response.body
50
+ assert_equal 'http://example.org/login?shop=othertestshop.myshopify.com', last_response.location
51
51
  end
52
52
 
53
53
  def test_root_with_session_and_new_shop_redirects_to_auth
54
54
  set_session
55
55
  get '/?shop=othertestshop.myshopify.com'
56
- assert_match '/auth/shopify?shop=othertestshop.myshopify.com', last_response.body
56
+ assert_equal 'http://example.org/login?shop=othertestshop.myshopify.com', last_response.location
57
57
  end
58
58
 
59
59
  def test_root_rescues_UnauthorizedAccess_clears_session_and_redirects
@@ -7,3 +7,5 @@
7
7
  <li><a href=<%="https://#{@shop.myshopify_domain}/admin/products/#{product.id}"%> target="_blank"> <%= product.id %> </a></li>
8
8
  <% end %>
9
9
  </ul>
10
+
11
+ <a href="/logout">Logout</a>
@@ -28,13 +28,15 @@
28
28
  <div style="margin-top: 30px"></div>
29
29
 
30
30
  <div class="container">
31
- <form role="form" class="form-large" action="/login" method="post">
32
- <div class="input-group form-wide">
33
- <input class="form-control" type="url" name="shop" placeholder="Shop URL">
34
- <span class="input-group-btn">
35
- <input class="btn" type="submit" value="Install App" />
36
- </span>
37
- </div>
31
+ <form id="login" role="form" class="form-large" action="/auth/shopify" method="post">
32
+ <input type="hidden" name="authenticity_token" value="<%= env['rack.session'][:csrf] %>" />
33
+ <div class="input-group form-wide">
34
+ <input class="form-control" name="shop" value="<%= params['shop'] %>" placeholder="your-shop-url.myshopify.com">
35
+ <span class="input-group-btn">
36
+ <input class="btn" type="submit" value="Login" />
37
+ </span>
38
+ </div>
38
39
  </form>
39
40
  </div>
40
- </body>
41
+ </body>
42
+
@@ -11,15 +11,10 @@ module Sinatra
11
11
  module Shopify
12
12
  module Methods
13
13
 
14
- # designed to be overriden
14
+ # designed to be overridden
15
15
  def after_shopify_auth
16
16
  end
17
17
 
18
- def logout
19
- session.delete(:shopify)
20
- session.clear
21
- end
22
-
23
18
  # for the app bridge initializer
24
19
  def shop_origin
25
20
  "#{session[:shopify][:shop]}"
@@ -31,35 +26,42 @@ module Sinatra
31
26
 
32
27
  if no_session?
33
28
  authenticate(return_to, return_params)
29
+
34
30
  elsif different_shop?
35
- logout
31
+ clear_session
36
32
  authenticate(return_to, return_params)
33
+
37
34
  else
38
35
  shop_name = session[:shopify][:shop]
39
36
  token = session[:shopify][:token]
40
37
  activate_shopify_api(shop_name, token)
41
38
  yield shop_name
42
39
  end
40
+
43
41
  rescue ActiveResource::UnauthorizedAccess
44
- clear_session shop_name
45
- redirect request.path
46
- end
42
+ clear_session
47
43
 
48
- def shopify_webhook(&blk)
49
- return unless verify_shopify_webhook
50
- shop_name = request.env['HTTP_X_SHOPIFY_SHOP_DOMAIN']
51
- webhook_body = ActiveSupport::JSON.decode(request.body.read.to_s)
52
- yield shop_name, webhook_body
53
- status 200
44
+ shop = Shop.find_by(name: shop_name)
45
+ shop.token = nil
46
+ shop.save
47
+
48
+ redirect request.path
54
49
  end
55
50
 
56
51
  private
57
52
 
58
- def request_protocol
59
- request.secure? ? 'https' : 'http'
53
+ def authenticate(return_to = '/', return_params = nil)
54
+ session[:return_params] = return_params if return_params
55
+
56
+ if shop_name = sanitized_shop_param(params)
57
+ redirect "/login?shop=#{shop_name}"
58
+ else
59
+ redirect '/login'
60
+ end
60
61
  end
61
62
 
62
63
  def base_url
64
+ request_protocol = request.secure? ? 'https' : 'http'
63
65
  "#{request_protocol}://#{request.env['HTTP_HOST']}"
64
66
  end
65
67
 
@@ -68,17 +70,12 @@ module Sinatra
68
70
  end
69
71
 
70
72
  def different_shop?
71
- params[:shop].present? && session[:shopify][:shop] != sanitize_shop_param(params)
73
+ params[:shop].present? && session[:shopify][:shop] != sanitized_shop_param(params)
72
74
  end
73
75
 
74
- def authenticate(return_to = '/', return_params = nil)
75
- if shop_name = sanitized_shop_name
76
- session[:return_params] = return_params if return_params
77
- redirect_url = "#{base_url}/auth/shopify"
78
- redirect_javascript redirect_url
79
- else
80
- redirect '/install'
81
- end
76
+ def clear_session
77
+ session.delete(:shopify)
78
+ session.clear
82
79
  end
83
80
 
84
81
  def activate_shopify_api(shop_name, token)
@@ -86,66 +83,15 @@ module Sinatra
86
83
  ShopifyAPI::Base.activate_session(api_session)
87
84
  end
88
85
 
89
- def clear_session(shop_name)
90
- logout
91
- shop = Shop.find_by(name: shop_name)
92
- shop.token = nil
93
- shop.save
94
- end
95
-
96
- def redirect_javascript(url)
97
- erb %(
98
- <!DOCTYPE html>
99
- <html lang="en">
100
- <head>
101
- <meta charset="utf-8" />
102
- <base target="_top">
103
- <title>Redirecting…</title>
104
- <script src="https://unpkg.com/@shopify/app-bridge"></script>
105
- <script type='text/javascript'>
106
- var AppBridge = window['app-bridge'];
107
- var createApp = AppBridge.createApp;
108
- var actions = AppBridge.actions;
109
- var Redirect = actions.Redirect;
110
-
111
- var apiKey = '#{settings.api_key}';
112
- var redirectUri = '#{url}';
113
- var shopOrigin = '#{sanitized_shop_name}';
114
-
115
- var permissionUrl = 'https://'+
116
- shopOrigin+
117
- '/admin'+
118
- '/oauth/authorize?client_id='+
119
- apiKey+
120
- '&scope=#{settings.scope}&redirect_uri='+
121
- redirectUri;
122
-
123
- // If the current window is the 'parent', change the URL by setting location.href
124
- if (window.top == window.self) {
125
- window.location.assign(permissionUrl);
126
-
127
- // If the current window is the 'child', change the parent's URL with Shopify App Bridge's Redirect action
128
- } else {
129
- var app = createApp({
130
- apiKey: apiKey,
131
- shopOrigin: shopOrigin
132
- });
133
-
134
- Redirect.create(app).dispatch(Redirect.Action.REMOTE, permissionUrl);
135
- }
136
- </script>
137
- </head>
138
- <body>
139
- </body>
140
- </html>
141
- ), layout: false
142
- end
143
-
144
- def sanitized_shop_name
145
- @sanitized_shop_name ||= sanitize_shop_param(params)
86
+ def receive_webhook(&blk)
87
+ return unless verify_shopify_webhook
88
+ shop_name = request.env['HTTP_X_SHOPIFY_SHOP_DOMAIN']
89
+ webhook_body = ActiveSupport::JSON.decode(request.body.read.to_s)
90
+ yield shop_name, webhook_body
91
+ status 200
146
92
  end
147
93
 
148
- def sanitize_shop_param(params)
94
+ def sanitized_shop_param(params)
149
95
  return unless params[:shop].present?
150
96
  name = params[:shop].to_s.strip
151
97
  name += '.myshopify.com' if !name.include?('myshopify.com') && !name.include?('.')
@@ -165,21 +111,30 @@ module Sinatra
165
111
  if calculated_hmac == request.env['HTTP_X_SHOPIFY_HMAC_SHA256']
166
112
  true
167
113
  else
168
- puts 'Shopify Webhook verifictation failed!'
114
+ puts 'Shopify Webhook verification failed!'
169
115
  false
170
116
  end
171
117
  end
172
118
  end
173
119
 
120
+ def shopify_webhook(route, &blk)
121
+ settings.webhook_routes << route
122
+ post(route) do
123
+ receive_webhook(&blk)
124
+ end
125
+ end
126
+
174
127
  def self.registered(app)
175
128
  app.helpers Shopify::Methods
176
129
  app.register Sinatra::ActiveRecordExtension
177
- app.enable :inline_templates
178
130
 
179
131
  app.set :database_file, File.expand_path('config/database.yml')
132
+
133
+ app.set :erb, layout: :'layouts/application'
180
134
  app.set :views, File.expand_path('views')
181
135
  app.set :public_folder, File.expand_path('public')
182
- app.set :erb, layout: :'layouts/application'
136
+ app.enable :inline_templates
137
+
183
138
  app.set :protection, except: :frame_options
184
139
 
185
140
  app.set :api_version, '2019-07'
@@ -189,7 +144,12 @@ module Sinatra
189
144
  app.set :shared_secret, ENV['SHOPIFY_SHARED_SECRET']
190
145
  app.set :secret, ENV['SECRET']
191
146
 
147
+ # csrf needs to be disabled for webhook routes
148
+ app.set :webhook_routes, ['/uninstall']
149
+
150
+ # add support for put/patch/delete
192
151
  app.use Rack::MethodOverride
152
+
193
153
  app.use Rack::Session::Cookie, key: 'rack.session',
194
154
  path: '/',
195
155
  secure: true,
@@ -197,18 +157,27 @@ module Sinatra
197
157
  secret: app.settings.secret,
198
158
  expire_after: 60 * 30 # half an hour in seconds
199
159
 
160
+ app.use Rack::Protection::AuthenticityToken, allow_if: lambda { |env|
161
+ app.settings.webhook_routes.include?(env["PATH_INFO"])
162
+ }
163
+
164
+ OmniAuth.config.allowed_request_methods = [:post]
165
+
200
166
  app.use OmniAuth::Builder do
201
167
  provider :shopify,
202
- app.settings.api_key,
203
- app.settings.shared_secret,
168
+ app.settings.api_key,
169
+ app.settings.shared_secret,
170
+ scope: app.settings.scope,
171
+ setup: lambda { |env|
172
+ shop = if env['REQUEST_METHOD'] == 'POST'
173
+ env['rack.request.form_hash']['shop']
174
+ else
175
+ Rack::Utils.parse_query(env['QUERY_STRING'])['shop']
176
+ end
204
177
 
205
- scope: app.settings.scope,
206
-
207
- setup: lambda { |env|
208
- params = Rack::Utils.parse_query(env['QUERY_STRING'])
209
- site_url = "https://#{params['shop']}"
210
- env['omniauth.strategy'].options[:client_options][:site] = site_url
211
- }
178
+ site_url = "https://#{shop}"
179
+ env['omniauth.strategy'].options[:client_options][:site] = site_url
180
+ }
212
181
  end
213
182
 
214
183
  ShopifyAPI::Session.setup(
@@ -216,21 +185,13 @@ module Sinatra
216
185
  secret: app.settings.shared_secret
217
186
  )
218
187
 
219
- app.get '/install' do
220
- if params[:shop].present?
221
- authenticate
222
- else
223
- erb :install, layout: false
224
- end
225
- end
226
-
227
- app.post '/login' do
228
- authenticate
188
+ app.get '/login' do
189
+ erb :login, layout: false
229
190
  end
230
191
 
231
192
  app.get '/logout' do
232
- logout
233
- redirect '/install'
193
+ clear_session
194
+ redirect '/login'
234
195
  end
235
196
 
236
197
  app.get '/auth/shopify/callback' do
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'shopify-sinatra-app'
3
- s.version = '0.12.0'
3
+ s.version = '1.0.0'
4
4
 
5
5
  s.summary = 'A classy shopify app'
6
6
  s.description = 'A Sinatra extension for building Shopify Apps. Akin to the shopify_app gem but for Sinatra'
@@ -13,14 +13,14 @@ Gem::Specification.new do |s|
13
13
  s.files = `git ls-files`.split("\n")
14
14
  s.executables << 'shopify-sinatra-app-generator'
15
15
 
16
- s.add_runtime_dependency 'sinatra', '~> 2.0.2'
16
+ s.add_runtime_dependency 'sinatra', '>= 2.0.2', '< 2.2.0'
17
17
  s.add_runtime_dependency 'sinatra-activerecord', '~> 2.0.9'
18
18
  s.add_runtime_dependency 'activesupport'
19
19
  s.add_runtime_dependency 'attr_encrypted', '~> 3.1.0'
20
20
 
21
- s.add_runtime_dependency 'shopify_api', '>= 7.0.1', '< 9.3.0'
21
+ s.add_runtime_dependency 'shopify_api', '>= 7.0.1', '< 9.4.0'
22
22
  s.add_runtime_dependency 'omniauth-shopify-oauth2', '>= 2.3.2'
23
- s.add_runtime_dependency 'omniauth', '1.9.1'
23
+ s.add_runtime_dependency 'omniauth', '>= 2.0.4'
24
24
 
25
25
  s.add_development_dependency 'rake', '>= 12.3.3'
26
26
  s.add_development_dependency 'sqlite3'
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify-sinatra-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Hughes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-15 00:00:00.000000000 Z
11
+ date: 2021-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 2.0.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 2.2.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: 2.0.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 2.2.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: sinatra-activerecord
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -75,7 +81,7 @@ dependencies:
75
81
  version: 7.0.1
76
82
  - - "<"
77
83
  - !ruby/object:Gem::Version
78
- version: 9.3.0
84
+ version: 9.4.0
79
85
  type: :runtime
80
86
  prerelease: false
81
87
  version_requirements: !ruby/object:Gem::Requirement
@@ -85,7 +91,7 @@ dependencies:
85
91
  version: 7.0.1
86
92
  - - "<"
87
93
  - !ruby/object:Gem::Version
88
- version: 9.3.0
94
+ version: 9.4.0
89
95
  - !ruby/object:Gem::Dependency
90
96
  name: omniauth-shopify-oauth2
91
97
  requirement: !ruby/object:Gem::Requirement
@@ -104,16 +110,16 @@ dependencies:
104
110
  name: omniauth
105
111
  requirement: !ruby/object:Gem::Requirement
106
112
  requirements:
107
- - - '='
113
+ - - ">="
108
114
  - !ruby/object:Gem::Version
109
- version: 1.9.1
115
+ version: 2.0.4
110
116
  type: :runtime
111
117
  prerelease: false
112
118
  version_requirements: !ruby/object:Gem::Requirement
113
119
  requirements:
114
- - - '='
120
+ - - ">="
115
121
  - !ruby/object:Gem::Version
116
- version: 1.9.1
122
+ version: 2.0.4
117
123
  - !ruby/object:Gem::Dependency
118
124
  name: rake
119
125
  requirement: !ruby/object:Gem::Requirement
@@ -206,12 +212,14 @@ executables:
206
212
  extensions: []
207
213
  extra_rdoc_files: []
208
214
  files:
215
+ - ".circleci/config.yml"
216
+ - ".github/dependabot.yml"
209
217
  - ".gitignore"
210
- - ".travis.yml"
211
218
  - CHANGELOG
212
219
  - Gemfile
213
220
  - LICENSE
214
221
  - README.md
222
+ - bin/ci.sh
215
223
  - bin/shopify-sinatra-app-generator
216
224
  - example/.gitignore
217
225
  - example/Gemfile
@@ -232,11 +240,10 @@ files:
232
240
  - example/views/_flash_messages.erb
233
241
  - example/views/_top_bar.erb
234
242
  - example/views/home.erb
235
- - example/views/install.erb
236
243
  - example/views/layouts/application.erb
244
+ - example/views/login.erb
237
245
  - lib/sinatra/shopify-sinatra-app.rb
238
246
  - shopify-sinatra-app.gemspec
239
- - test.sh
240
247
  homepage: https://github.com/kevinhughes27/shopify-sinatra-app/
241
248
  licenses:
242
249
  - MIT
data/.travis.yml DELETED
@@ -1,13 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.6.3
5
-
6
- gemfile: example/Gemfile
7
-
8
- script: "./test.sh"
9
-
10
- notifications:
11
- email:
12
- on_success: never
13
- on_failure: never