beyond_canvas 0.15.1.pre → 0.16.2.pre

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -6
  3. data/Rakefile +3 -3
  4. data/app/assets/javascripts/beyond_canvas/base.js +248 -99
  5. data/app/assets/stylesheets/beyond_canvas/settings/_breakpoints.scss +6 -6
  6. data/app/assets/stylesheets/beyond_canvas/settings/_variables.scss +13 -13
  7. data/app/controllers/beyond_canvas/authentications_controller.rb +62 -0
  8. data/app/controllers/concerns/beyond_canvas/authentication.rb +24 -0
  9. data/app/controllers/concerns/beyond_canvas/request_validation.rb +1 -1
  10. data/app/controllers/concerns/beyond_canvas/resource_management.rb +33 -0
  11. data/app/javascript/beyond_canvas/base.js +0 -2
  12. data/app/javascript/beyond_canvas/initializers/buttons.js +7 -40
  13. data/app/javascript/beyond_canvas/initializers/flash.js +5 -13
  14. data/app/javascript/beyond_canvas/initializers/functions.js +41 -0
  15. data/app/javascript/beyond_canvas/initializers/inputs.js +3 -7
  16. data/app/views/beyond_canvas/authentications/new.html.erb +18 -0
  17. data/app/views/layouts/beyond_canvas/public.html.erb +3 -1
  18. data/config/locales/en.yml +4 -0
  19. data/config/routes.rb +6 -0
  20. data/lib/beyond_canvas.rb +18 -2
  21. data/lib/beyond_canvas/configuration.rb +4 -1
  22. data/lib/beyond_canvas/engine.rb +4 -0
  23. data/lib/beyond_canvas/models/authentication.rb +66 -0
  24. data/lib/beyond_canvas/models/shop.rb +28 -0
  25. data/lib/beyond_canvas/models/utils.rb +55 -0
  26. data/lib/beyond_canvas/parameter_sanitizer.rb +43 -0
  27. data/lib/beyond_canvas/rails/routes.rb +21 -0
  28. data/lib/beyond_canvas/version.rb +1 -1
  29. data/lib/generators/beyond_canvas/auth_model/auth_model_generator.rb +50 -0
  30. data/lib/generators/beyond_canvas/auth_model/templates/migration.erb +20 -0
  31. data/lib/generators/beyond_canvas/auth_model/templates/model.erb +5 -0
  32. data/lib/generators/beyond_canvas/controller/controller_generator.rb +20 -0
  33. data/lib/generators/beyond_canvas/controller/templates/controller.erb +37 -0
  34. data/lib/generators/beyond_canvas/custom_styles/templates/beyond_canvas_custom_styles.scss +153 -0
  35. data/lib/generators/beyond_canvas/install/install_generator.rb +15 -5
  36. data/lib/generators/beyond_canvas/install/templates/beyond_canvas.rb.erb +11 -0
  37. data/lib/generators/beyond_canvas/views/views_generator.rb +19 -0
  38. metadata +50 -5
  39. data/lib/generators/beyond_canvas/custom_styles/templates/beyond_canvas_custom_styles.sass +0 -123
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'beyond_canvas/application_controller'
4
+
5
+ module BeyondCanvas
6
+ class AuthenticationsController < ApplicationController # :nodoc:
7
+ layout 'beyond_canvas/public'
8
+
9
+ include ::BeyondCanvas::Authentication
10
+ include ::BeyondCanvas::ResourceManagement
11
+
12
+ before_action :validate_app_installation_request!, only: :new
13
+
14
+ def new
15
+ self.resource = resource_class.new
16
+ end
17
+
18
+ def create
19
+ # Search for the api url. If there is no record it creates a new record.
20
+ resource_params = new_resource_params
21
+ self.resource = resource_class.find_or_create_by(beyond_api_url: resource_params[:api_url])
22
+ # Assign the attributes to the record
23
+ raise ActiveRecord::RecordNotSaved unless resource.update(resource_params)
24
+ # Get and save access_token and refresh_token using the authentication code
25
+ raise BeyondApi::Error if resource.authenticate.is_a?(BeyondApi::Error)
26
+
27
+ redirect_to after_create_path
28
+ rescue ActiveRecord::RecordNotSaved, BeyondApi::Error, StandardError => e
29
+ logger.error "[BeyondCanvas] #{e.message}".red
30
+ send "handle_#{e.class.name.split('::').first.underscore}_exception", e
31
+ end
32
+
33
+ def update
34
+ create
35
+ end
36
+
37
+ private
38
+
39
+ def new_resource_params
40
+ send "new_#{resource_name}_params"
41
+ end
42
+
43
+ def after_create_path
44
+ new_resource_params[:return_url]
45
+ end
46
+
47
+ def handle_active_record_exception(_exception)
48
+ flash[:error] = t('beyond_canvas.authentications.failure')
49
+ render :new
50
+ end
51
+
52
+ def handle_beyond_api_exception(_exception)
53
+ flash[:error] = t('beyond_canvas.authentications.failure')
54
+ render :new
55
+ end
56
+
57
+ def handle_standard_error_exception(_exception)
58
+ flash[:error] = t('beyond_canvas.authentications.failure')
59
+ render :new
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module Authentication # :nodoc:
5
+ extend ActiveSupport::Concern
6
+ AUTH_RESOURCE = BeyondCanvas.auth_model
7
+
8
+ class_eval <<-METHODS, __FILE__, __LINE__ + 1
9
+ def current_#{AUTH_RESOURCE}
10
+ instance_variable_get("@#{AUTH_RESOURCE}")
11
+ end
12
+
13
+ def new_#{AUTH_RESOURCE}_params
14
+ beyond_canvas_parameter_sanitizer.sanitize
15
+ end
16
+ METHODS
17
+
18
+ private
19
+
20
+ def beyond_canvas_parameter_sanitizer
21
+ @beyond_canvas_parameter_sanitizer ||= BeyondCanvas::ParameterSanitizer.new(AUTH_RESOURCE, params)
22
+ end
23
+ end
24
+ end
@@ -31,7 +31,7 @@ module BeyondCanvas
31
31
  def valid_signature?(signature, data, secret)
32
32
  digest = OpenSSL::Digest.new('SHA1')
33
33
  hmac = OpenSSL::HMAC.digest(digest, secret, data)
34
- signature == Base64.encode64(hmac).chop
34
+ URI.decode(signature) == Base64.encode64(hmac).chop
35
35
  end
36
36
  end
37
37
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module ResourceManagement # :nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Share some methods defined in the controller to make them available for the view
9
+ if respond_to?(:helper_method)
10
+ helpers = %w[resource resource_name resource_class]
11
+ helper_method(*helpers)
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def resource_name
18
+ BeyondCanvas.auth_model
19
+ end
20
+
21
+ def resource
22
+ instance_variable_get(:"@#{resource_name}")
23
+ end
24
+
25
+ def resource=(new_resource)
26
+ instance_variable_set(:"@#{resource_name}", new_resource)
27
+ end
28
+
29
+ def resource_class
30
+ resource_name.classify.constantize
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,3 @@
1
- import 'jquery';
2
-
3
1
  import './initializers/buttons';
4
2
  import './initializers/flash';
5
3
  import './initializers/inputs';
@@ -1,6 +1,6 @@
1
- const SPINNER_ANIMATION_TIMEOUT = 125;
1
+ import { disableActionElements, enableActionElements, hideSpinner, showSpinner } from './functions';
2
2
 
3
- (function($) {
3
+ (function ($) {
4
4
  const onDOMReady = function () {
5
5
  const inputs = $('input, textarea, select').not(':input[type=button], :input[type=submit], :input[type=reset]');
6
6
 
@@ -11,9 +11,7 @@ const SPINNER_ANIMATION_TIMEOUT = 125;
11
11
  if ($(input).is(':hidden')) {
12
12
  e.preventDefault();
13
13
  }
14
- $('button[class^="button"]').each(function () {
15
- hideSpinner($(this));
16
- });
14
+ hideSpinner();
17
15
  enableActionElements();
18
16
  });
19
17
  });
@@ -31,18 +29,17 @@ const SPINNER_ANIMATION_TIMEOUT = 125;
31
29
  <div class="bounce1"></div>
32
30
  <div class="bounce2"></div>
33
31
  <div class="bounce3"></div>
34
- </div>`
35
- );
32
+ </div>`);
36
33
 
37
34
  // Bind ajax:success and ajax:error to the form the button belongs to
38
35
  button
39
36
  .closest('form')
40
37
  .on('ajax:success', function () {
41
- hideSpinner(button);
38
+ hideSpinner();
42
39
  enableActionElements();
43
40
  })
44
41
  .on('ajax:error', function () {
45
- hideSpinner(button);
42
+ hideSpinner();
46
43
  enableActionElements();
47
44
  });
48
45
  });
@@ -53,35 +50,5 @@ const SPINNER_ANIMATION_TIMEOUT = 125;
53
50
  showSpinner($(this));
54
51
  });
55
52
 
56
- $(document)
57
- .on('ready page:load turbolinks:load', onDOMReady);
53
+ $(document).on('ready page:load turbolinks:load', onDOMReady);
58
54
  })(jQuery);
59
-
60
- function showSpinner(button) {
61
- // Adjust the width of the button
62
- button.width(button.width() + $('.spinner').outerWidth(true));
63
- // Show the spinner
64
- setTimeout(function() {
65
- button.find('.spinner').css('display', 'flex');
66
- }, SPINNER_ANIMATION_TIMEOUT);
67
- }
68
-
69
- function hideSpinner(button) {
70
- setTimeout(function () {
71
- // Hide the spinner
72
- button.find('.spinner').hide();
73
- // Adjust the width of the button
74
- button.width(button.data('oldWidth'));
75
- }, SPINNER_ANIMATION_TIMEOUT);
76
- }
77
-
78
- function disableActionElements() {
79
- $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function() {
80
- $(this).addClass('actions--disabled');
81
- });
82
- }
83
- function enableActionElements() {
84
- $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function() {
85
- $(this).removeClass('actions--disabled');
86
- });
87
- }
@@ -1,4 +1,6 @@
1
- (function($) {
1
+ import { closeAlert } from './functions';
2
+
3
+ (function ($) {
2
4
  const onDOMReady = function () {
3
5
  $('.flash').each(function () {
4
6
  $(this).css('right', -$(this).width() + 'px');
@@ -9,19 +11,9 @@
9
11
  }, 100);
10
12
  };
11
13
 
12
- $(document).on('click', '.flash', function() {
14
+ $(document).on('click', '.flash', function () {
13
15
  closeAlert();
14
16
  });
15
17
 
16
- $(document)
17
- .on('ready page:load turbolinks:load', onDOMReady);
18
+ $(document).on('ready page:load turbolinks:load', onDOMReady);
18
19
  })(jQuery);
19
-
20
- function closeAlert() {
21
- $('.flash')
22
- .removeClass('flash--shown')
23
- .delay(700)
24
- .queue(function() {
25
- $(this).remove();
26
- });
27
- }
@@ -0,0 +1,41 @@
1
+ const SPINNER_ANIMATION_TIMEOUT = 125;
2
+
3
+ export function showSpinner(button) {
4
+ // Adjust the width of the button
5
+ button.width(button.width() + $('.spinner').outerWidth(true));
6
+ // Show the spinner
7
+ setTimeout(function () {
8
+ button.find('.spinner').css('display', 'flex');
9
+ }, SPINNER_ANIMATION_TIMEOUT);
10
+ }
11
+
12
+ export function hideSpinner() {
13
+ $('button[class^="button"]').each(function (_, button) {
14
+ setTimeout(function () {
15
+ // Hide the spinner
16
+ $(button).find('.spinner').hide();
17
+ // Adjust the width of the button
18
+ $(button).width($(button).data('oldWidth'));
19
+ }, SPINNER_ANIMATION_TIMEOUT);
20
+ });
21
+ }
22
+
23
+ export function disableActionElements() {
24
+ $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function (_, button) {
25
+ $(button).addClass('actions--disabled');
26
+ });
27
+ }
28
+ export function enableActionElements() {
29
+ $('a, input[type="submit"], input[type="button"], input[type="reset"], button').each(function (_, button) {
30
+ $(button).removeClass('actions--disabled');
31
+ });
32
+ }
33
+
34
+ export function closeAlert() {
35
+ $('.flash')
36
+ .removeClass('flash--shown')
37
+ .delay(700)
38
+ .queue(function () {
39
+ $(this).remove();
40
+ });
41
+ }
@@ -1,4 +1,4 @@
1
- (function($) {
1
+ (function ($) {
2
2
  const onDOMReady = function () {
3
3
  $('input[type="file"]').each(function () {
4
4
  var $input = $(this),
@@ -9,10 +9,7 @@
9
9
  var fileName = '';
10
10
 
11
11
  if (this.files && this.files.length > 1)
12
- fileName = (this.getAttribute('data-multiple-caption') || '').replace(
13
- '{count}',
14
- this.files.length
15
- );
12
+ fileName = (this.getAttribute('data-multiple-caption') || '').replace('{count}', this.files.length);
16
13
  else if (e.target.value) fileName = e.target.value.split('\\').pop();
17
14
 
18
15
  if (fileName)
@@ -33,6 +30,5 @@
33
30
  });
34
31
  };
35
32
 
36
- $(document)
37
- .on('ready page:load turbolinks:load', onDOMReady);
33
+ $(document).on('ready page:load turbolinks:load', onDOMReady);
38
34
  })(jQuery);
@@ -0,0 +1,18 @@
1
+ <div class='card card--padding'>
2
+
3
+ <%= form_for(resource, as: resource_name) do |f| %>
4
+
5
+ <h2 class='card__headline'>Install <%= BeyondCanvas.configuration.site_title %> in your shop</h2>
6
+
7
+ <%= f.hidden_field :code, value: params[:code] || resource.code %>
8
+ <%= f.hidden_field :signature, value: params[:signature] || resource.signature %>
9
+ <%= f.hidden_field :return_url, value: params[:return_url] || resource.return_url %>
10
+ <%= f.hidden_field :api_url, value: params[:api_url] || resource.api_url %>
11
+ <%= f.hidden_field :access_token_url, value: params[:access_token_url] || resource.access_token_url %>
12
+
13
+ <div class='form__actions--spaced'>
14
+ <%= f.button 'Save', type: :submit, class: 'button__solid--primary' %>
15
+ </div>
16
+
17
+ <% end %>
18
+ </div>
@@ -5,7 +5,9 @@
5
5
 
6
6
  <body class="body--public">
7
7
  <main class="main <%= params[:controller].gsub(/[\/_]/, "-") %>--<%= params[:action] %>">
8
- <%= render 'beyond_canvas/shared/flash' %>
8
+ <div id="flash">
9
+ <%= render 'beyond_canvas/shared/flash' %>
10
+ </div>
9
11
  <%= render 'beyond_canvas/locales/edit' %>
10
12
  <div class="main-wrapper">
11
13
  <%= render 'beyond_canvas/shared/logo' %>
@@ -0,0 +1,4 @@
1
+ en:
2
+ beyond_canvas:
3
+ authentications:
4
+ failure: Shop could not be saved
@@ -2,4 +2,10 @@
2
2
 
3
3
  BeyondCanvas::Engine.routes.draw do
4
4
  put '/locale', to: 'system#update_locale', as: :update_locale
5
+
6
+ def create_default_routes(resource_name)
7
+ resources resource_name, controller: 'authentications', except: :destroy
8
+ end
9
+
10
+ create_default_routes(BeyondCanvas.auth_model.pluralize.to_sym) unless BeyondCanvas.use_rails_app_controller
5
11
  end
@@ -12,10 +12,26 @@ require 'http/accept'
12
12
  require 'premailer/rails'
13
13
 
14
14
  require 'beyond_api'
15
+ require 'attr_encrypted'
16
+ require 'blind_index'
15
17
 
16
18
  module BeyondCanvas # :nodoc:
17
- autoload :AssetRegistration, 'beyond_canvas/asset_registration'
18
- autoload :Configuration, 'beyond_canvas/configuration'
19
+ autoload :AssetRegistration, 'beyond_canvas/asset_registration'
20
+ autoload :Configuration, 'beyond_canvas/configuration'
21
+
22
+ module Models # :nodoc:
23
+ autoload :Authentication, 'beyond_canvas/models/authentication'
24
+ autoload :Shop, 'beyond_canvas/models/shop'
25
+ autoload :Utils, 'beyond_canvas/models/utils'
26
+ end
27
+
28
+ autoload :ParameterSanitizer, 'beyond_canvas/parameter_sanitizer'
29
+
30
+ mattr_accessor :use_rails_app_controller
31
+ @@use_rails_app_controller = false # rubocop:disable Style/ClassVars
32
+
33
+ mattr_accessor :auth_model
34
+ @@auth_model = 'shop' # rubocop:disable Style/ClassVars
19
35
 
20
36
  class << self
21
37
  def configuration
@@ -2,7 +2,7 @@
2
2
 
3
3
  module BeyondCanvas
4
4
  class Configuration # :nodoc:
5
- attr_accessor :site_title, :site_logo, :favicon, :skip_webpacker
5
+ attr_accessor :site_title, :site_logo, :favicon, :skip_webpacker, :encryption_key, :blind_index_key, :namespace
6
6
 
7
7
  include AssetRegistration
8
8
 
@@ -11,6 +11,9 @@ module BeyondCanvas
11
11
  @site_logo = nil
12
12
  @favicon = nil
13
13
  @skip_webpacker = false
14
+ @encryption_key = nil
15
+ @blind_index_key = nil
16
+ @namespace = '/'
14
17
  end
15
18
 
16
19
  def setup!
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'beyond_canvas/rails/routes'
4
+
3
5
  module BeyondCanvas
4
6
  class Engine < ::Rails::Engine # :nodoc:
5
7
  isolate_namespace BeyondCanvas
@@ -15,7 +17,9 @@ module BeyondCanvas
15
17
 
16
18
  config.before_initialize do
17
19
  ActiveSupport.on_load :action_controller do
20
+ include ::BeyondCanvas::Authentication
18
21
  include ::BeyondCanvas::LocaleManagement
22
+ include ::BeyondCanvas::ResourceManagement
19
23
  include ::BeyondCanvas::RequestValidation
20
24
  include ::BeyondCanvas::StatusCodes
21
25
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module Models
5
+ module Authentication # :nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_accessor :code, :signature, :access_token_url
10
+
11
+ ##############################################################################
12
+ # Encrypted attribute configuration
13
+ ##############################################################################
14
+
15
+ attr_encrypted :beyond_api_url, key: [BeyondCanvas.configuration.encryption_key].pack('H*')
16
+ attr_encrypted :beyond_access_token, key: [BeyondCanvas.configuration.encryption_key].pack('H*')
17
+ attr_encrypted :beyond_refresh_token, key: [BeyondCanvas.configuration.encryption_key].pack('H*')
18
+
19
+ blind_index :beyond_api_url, key: [BeyondCanvas.configuration.blind_index_key].pack('H*')
20
+
21
+ ##############################################################################
22
+ # Validations
23
+ ##############################################################################
24
+
25
+ # Callback url params
26
+
27
+ validates :code,
28
+ presence: true,
29
+ on: :create
30
+ validates :signature,
31
+ presence: true,
32
+ on: :create
33
+ validates :access_token_url,
34
+ presence: true,
35
+ on: :create
36
+
37
+ # Database fields
38
+
39
+ validates :beyond_api_url,
40
+ presence: true
41
+ validates :beyond_access_token,
42
+ presence: true,
43
+ unless: -> { encrypted_beyond_access_token_was.blank? }
44
+ validates :beyond_refresh_token,
45
+ presence: true,
46
+ unless: -> { encrypted_beyond_refresh_token_was.blank? }
47
+
48
+ ##############################################################################
49
+ # Instance methods
50
+ ##############################################################################
51
+
52
+ #
53
+ # Get and save access_token and refresh_token using the authentication code
54
+ # NOTE: This method is used during the shop creation, as it is the only point
55
+ # we know about the authentication code
56
+ #
57
+ def authenticate
58
+ session = BeyondApi::Session.new(api_url: beyond_api_url)
59
+ session.token.create(code)
60
+ update(beyond_access_token: session.access_token,
61
+ beyond_refresh_token: session.refresh_token)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end