beyond_canvas 0.15.1.pre → 0.16.2.pre

Sign up to get free protection for your applications and to get access to all the features.
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