From a2d8813aea90aedaac61a605cbc01915e2cf9c0e Mon Sep 17 00:00:00 2001 From: Fabian Oudhaarlem Date: Thu, 8 Aug 2019 13:55:23 +0200 Subject: [PATCH 1/6] token based api login added --- .../api/v1/users_controller_decorator.rb | 62 +++++++++++++++++++ .../spree/omniauth_callbacks_controller.rb | 4 +- app/models/spree/authentication_method.rb | 44 ++++++++++++- app/models/spree/user_decorator.rb | 9 +++ config/routes.rb | 12 ++++ lib/spree_social/engine.rb | 2 + 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 app/controllers/spree/api/v1/users_controller_decorator.rb diff --git a/app/controllers/spree/api/v1/users_controller_decorator.rb b/app/controllers/spree/api/v1/users_controller_decorator.rb new file mode 100644 index 00000000..969ccffe --- /dev/null +++ b/app/controllers/spree/api/v1/users_controller_decorator.rb @@ -0,0 +1,62 @@ +module Spree + module Api + module V1 + module UsersControllerDecorator + + def social_login + authentication_method = Spree::AuthenticationMethod.find_by_provider(params[:provider]) + render json: {exception: 'Unsupported provider'}, status: 500 and return unless authentication_method + omniauth_hash = authentication_method.get_social_user_info(params[:oauth_token]) + + authentication = Spree::UserAuthentication.find_by_provider_and_uid(params[:provider], omniauth_hash['uid']) + + + if authentication.present? and authentication.try(:user).present? + render_user_login(authentication.user) + elsif @current_api_user + @current_api_user.apply_omniauth(omniauth_hash) + @current_api_user.save! + render_user_login(@current_api_user) + else + user = Spree::User.find_by_email(params[:email]) || Spree::User.new + user.apply_omniauth(omniauth_hash) + if user.save! + render_user_login(user) + end + end + + if @order + user = @current_api_user || authentication.user + @order.associate_user!(user) + end + end + + def oauth_providers + auth_methods = Spree::AuthenticationMethod.active_authentication_methods.pluck(:provider) + auth_methods.map! do |provider| + oauth_provider = SpreeSocial::OAUTH_PROVIDERS.detect {|p| p[1] == provider} + { + name: oauth_provider[0], + provider: provider, + signup_support: oauth_provider[2] + } + end + render json: auth_methods, status: :ok + end + + private + + def render_user_login(user) + render :json => {:result => { + :user => "#{user.login}", + :api_key => "#{user.spree_api_key}", + :user_id => "#{user.id}" + }} + end + + end + end + end +end + +Spree::Api::V1::UsersController.prepend(Spree::Api::V1::UsersControllerDecorator) \ No newline at end of file diff --git a/app/controllers/spree/omniauth_callbacks_controller.rb b/app/controllers/spree/omniauth_callbacks_controller.rb index bf826a97..f04dd1ca 100644 --- a/app/controllers/spree/omniauth_callbacks_controller.rb +++ b/app/controllers/spree/omniauth_callbacks_controller.rb @@ -6,7 +6,7 @@ class Spree::OmniauthCallbacksController < Devise::OmniauthCallbacksController def self.provides_callback_for(*providers) providers.each do |provider| - class_eval <<-FUNCTION_DEFS, __FILE__, __LINE__ + 1 + class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{provider} if request.env['omniauth.error'].present? flash[:error] = I18n.t('devise.omniauth_callbacks.failure', kind: auth_hash['provider'], reason: Spree.t(:user_was_not_valid)) @@ -44,7 +44,7 @@ def #{provider} session[:guest_token] = nil end end - FUNCTION_DEFS + RUBY end end diff --git a/app/models/spree/authentication_method.rb b/app/models/spree/authentication_method.rb index b2aeb0bf..82bd4669 100644 --- a/app/models/spree/authentication_method.rb +++ b/app/models/spree/authentication_method.rb @@ -1,13 +1,53 @@ class Spree::AuthenticationMethod < ActiveRecord::Base validates :provider, :api_key, :api_secret, presence: true + validate :provider_must_be_backed_by_omniauth_strategy + + def self.active_authentication_methods + where(environment: ::Rails.env, active: true) + end + def self.active_authentication_methods? - where(environment: ::Rails.env, active: true).exists? + active_authentication_methods.exists? end - scope :available_for, lambda { |user| + scope :available_for, lambda {|user| sc = where(environment: ::Rails.env) sc = sc.where.not(provider: user.user_authentications.pluck(:provider)) if user && !user.user_authentications.empty? sc } + + def get_social_user_info(token) + strategy(token).auth_hash + end + + def provider_must_be_backed_by_omniauth_strategy + begin + strategy_class + rescue NameError => e + errors.add(:provider, 'must be backed by an omniauth strategy') + end + end + + private + + def strategy_class + "::OmniAuth::Strategies::#{provider.classify}".constantize + end + + def client + ::OAuth2::Client.new(api_key, api_secret, strategy_class.default_options.client_options.to_h).tap do |c| + c.site = strategy_class.default_options.client_options['site'] + end + end + + def access_token(token) + ::OAuth2::AccessToken.new(client, token) + end + + def strategy(token) + app = lambda {|env| [200, {}, ["Hello World."]]} + options = [api_key, api_secret] + strategy_class.new(app, *options).tap {|s| s.access_token = access_token(token)} + end end diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 203270a4..2b687162 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -14,4 +14,13 @@ def apply_omniauth(omniauth) def password_required? (user_authentications.empty? || !password.blank?) && super end + + def oauth_providers + user_authentications.map do |user_authentication| + { + provider: user_authentication.provider, + uid: user_authentication.uid + } + end + end end diff --git a/config/routes.rb b/config/routes.rb index 7db57cb2..20bb3df3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,4 +11,16 @@ namespace :admin do resources :authentication_methods end + + namespace :api do + namespace :v1 do + resources :users do + collection do + post :social_login + post 'social_login/:provider', to: :social_login + get :oauth_providers + end + end + end + end end diff --git a/lib/spree_social/engine.rb b/lib/spree_social/engine.rb index ceb1bdd5..6c202479 100644 --- a/lib/spree_social/engine.rb +++ b/lib/spree_social/engine.rb @@ -20,6 +20,8 @@ def self.activate Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c| Rails.configuration.cache_classes ? require(c) : load(c) end + + Spree::Api::ApiHelpers.user_attributes.push :oauth_providers end config.to_prepare(&method(:activate).to_proc) From 0c5ddd8e65e68756413ec941393aba57a12a5d71 Mon Sep 17 00:00:00 2001 From: Fabian Oudhaarlem Date: Thu, 8 Aug 2019 22:40:11 +0200 Subject: [PATCH 2/6] set rake to < 11 since rake >= 11 breaks rspec 3.2 --- spree_social.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/spree_social.gemspec b/spree_social.gemspec index 365b0a6a..d1816f48 100644 --- a/spree_social.gemspec +++ b/spree_social.gemspec @@ -46,4 +46,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'guard-rspec' s.add_development_dependency 'rubocop', '>= 0.24.1' s.add_development_dependency 'ruby_dep', '~> 1.3.0' + s.add_development_dependency 'rake', '< 11.0' end From b47824fe7e3299ca0ff5b342dabf80884e3edd56 Mon Sep 17 00:00:00 2001 From: Fabian Oudhaarlem Date: Thu, 8 Aug 2019 22:43:49 +0200 Subject: [PATCH 3/6] minor touchup --- app/controllers/spree/api/v1/users_controller_decorator.rb | 6 ++---- app/models/spree/authentication_method.rb | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/controllers/spree/api/v1/users_controller_decorator.rb b/app/controllers/spree/api/v1/users_controller_decorator.rb index 969ccffe..6af7043b 100644 --- a/app/controllers/spree/api/v1/users_controller_decorator.rb +++ b/app/controllers/spree/api/v1/users_controller_decorator.rb @@ -5,12 +5,10 @@ module UsersControllerDecorator def social_login authentication_method = Spree::AuthenticationMethod.find_by_provider(params[:provider]) - render json: {exception: 'Unsupported provider'}, status: 500 and return unless authentication_method - omniauth_hash = authentication_method.get_social_user_info(params[:oauth_token]) - + render json: {exception: 'Unsupported provider'}, status: 422 and return unless authentication_method + omniauth_hash = authentication_method.get_omniauth_hash(params[:oauth_token]) authentication = Spree::UserAuthentication.find_by_provider_and_uid(params[:provider], omniauth_hash['uid']) - if authentication.present? and authentication.try(:user).present? render_user_login(authentication.user) elsif @current_api_user diff --git a/app/models/spree/authentication_method.rb b/app/models/spree/authentication_method.rb index 82bd4669..8e5e6296 100644 --- a/app/models/spree/authentication_method.rb +++ b/app/models/spree/authentication_method.rb @@ -17,7 +17,7 @@ def self.active_authentication_methods? sc } - def get_social_user_info(token) + def get_omniauth_hash(token) strategy(token).auth_hash end From 4f1cff8ecfbde1006e93b09acec9d8b9be55b019 Mon Sep 17 00:00:00 2001 From: Fabian Oudhaarlem Date: Wed, 14 Aug 2019 11:46:09 +0200 Subject: [PATCH 4/6] Refactor strategy class method to not depend on existence exception but use safe_constantize instead --- app/models/spree/authentication_method.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/models/spree/authentication_method.rb b/app/models/spree/authentication_method.rb index 8e5e6296..df245620 100644 --- a/app/models/spree/authentication_method.rb +++ b/app/models/spree/authentication_method.rb @@ -22,17 +22,13 @@ def get_omniauth_hash(token) end def provider_must_be_backed_by_omniauth_strategy - begin - strategy_class - rescue NameError => e - errors.add(:provider, 'must be backed by an omniauth strategy') - end + errors.add(:provider, 'must be backed by an omniauth strategy') unless strategy_class.safe_constantize end private def strategy_class - "::OmniAuth::Strategies::#{provider.classify}".constantize + "::OmniAuth::Strategies::#{provider.classify}".safe_constantize end def client From 113e259c1244b287ce819af20f0fb988441805a8 Mon Sep 17 00:00:00 2001 From: Fabian Oudhaarlem Date: Wed, 14 Aug 2019 11:46:44 +0200 Subject: [PATCH 5/6] Add API key to oauth_providers api call to allow any frontend to use the api key in their SDK --- app/controllers/spree/api/v1/users_controller_decorator.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/api/v1/users_controller_decorator.rb b/app/controllers/spree/api/v1/users_controller_decorator.rb index 6af7043b..dd366da4 100644 --- a/app/controllers/spree/api/v1/users_controller_decorator.rb +++ b/app/controllers/spree/api/v1/users_controller_decorator.rb @@ -30,12 +30,13 @@ def social_login end def oauth_providers - auth_methods = Spree::AuthenticationMethod.active_authentication_methods.pluck(:provider) - auth_methods.map! do |provider| + auth_methods = Spree::AuthenticationMethod.active_authentication_methods + auth_methods.map! do |auth_method| oauth_provider = SpreeSocial::OAUTH_PROVIDERS.detect {|p| p[1] == provider} { name: oauth_provider[0], - provider: provider, + provider: auth_method.provider, + api_key: auth_method.api_key, signup_support: oauth_provider[2] } end From 2770f94845a627c697105021247a056ac7803f3e Mon Sep 17 00:00:00 2001 From: Fabian Oudhaarlem Date: Wed, 14 Aug 2019 11:46:50 +0200 Subject: [PATCH 6/6] Generate api key for user after social registration --- app/controllers/spree/api/v1/users_controller_decorator.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/spree/api/v1/users_controller_decorator.rb b/app/controllers/spree/api/v1/users_controller_decorator.rb index dd366da4..94f66c7f 100644 --- a/app/controllers/spree/api/v1/users_controller_decorator.rb +++ b/app/controllers/spree/api/v1/users_controller_decorator.rb @@ -18,6 +18,9 @@ def social_login else user = Spree::User.find_by_email(params[:email]) || Spree::User.new user.apply_omniauth(omniauth_hash) + + user.generate_spree_api_key! if user.spree_api_key.blank? + if user.save! render_user_login(user) end