diff --git a/Gemfile b/Gemfile index 301a29717..ae9a2f222 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,7 @@ gem 'haml', '~> 4.0.6' gem 'inherited_resources', github: 'activeadmin/inherited_resources' gem 'jbuilder', '~> 1.5.3' gem 'oj', '~> 2.17.5' +gem 'koala', '~> 1.10.1' gem 'puma', '~> 3.0' gem 'rack-cors', '~> 0.4.0' gem 'pg', '~> 0.18.2' diff --git a/Gemfile.lock b/Gemfile.lock index 04ef70984..176b3d982 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -148,6 +148,8 @@ GEM railties (>= 3.0.0) faker (1.4.3) i18n (~> 0.5) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) ffi (1.9.14) fog-aws (0.12.0) fog-core (~> 1.38) @@ -191,6 +193,10 @@ GEM kaminari (0.17.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) + koala (1.10.1) + addressable + faraday + multi_json launchy (2.4.3) addressable (~> 2.3) letter_opener (1.4.1) @@ -212,6 +218,7 @@ GEM mini_portile2 (2.1.0) minitest (5.9.1) multi_json (1.12.1) + multipart-post (2.0.0) nio4r (1.2.1) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) @@ -384,6 +391,7 @@ DEPENDENCIES haml (~> 4.0.6) inherited_resources! jbuilder (~> 1.5.3) + koala (~> 1.10.1) letter_opener (~> 1.4.1) listen (~> 3.0.5) oj (~> 2.17.5) diff --git a/app/controllers/api/v1/sessions_controller.rb b/app/controllers/api/v1/sessions_controller.rb index dec5e413e..6bdf90961 100644 --- a/app/controllers/api/v1/sessions_controller.rb +++ b/app/controllers/api/v1/sessions_controller.rb @@ -5,9 +5,29 @@ module V1 class SessionsController < DeviseTokenAuth::SessionsController protect_from_forgery with: :null_session + def facebook + user_params = FacebookService.new(params[:access_token]).profile + @resource = User.from_social_provider 'facebook', user_params + custom_sign_in + rescue Koala::Facebook::AuthenticationError + render json: { error: 'Not Authorized' }, status: :forbidden + rescue ActiveRecord::RecordNotUnique + render json: { error: 'User already registered with email/password' }, status: :bad_request + end + def resource_params params.require(:user).permit(:email, :password) end + + private + + def custom_sign_in + sign_in(:api_v1_user, @resource) + new_auth_header = @resource.create_new_auth_token + # update response with the header that will be required by the next request + response.headers.merge!(new_auth_header) + render_create_success + end end end end diff --git a/app/models/user.rb b/app/models/user.rb index c4913ba49..f2c607b5c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -40,4 +40,11 @@ def full_name return username unless first_name.present? "#{first_name} #{last_name}" end + + def self.from_social_provider(provider, user_params) + where(provider: provider, uid: user_params['id']).first_or_create do |user| + user.password = Devise.friendly_token[0, 20] + user.assign_attributes user_params.except('id') + end + end end diff --git a/app/services/facebook_service.rb b/app/services/facebook_service.rb index e85db495c..b086b9641 100644 --- a/app/services/facebook_service.rb +++ b/app/services/facebook_service.rb @@ -1,9 +1,13 @@ -class FacebookService < SocialNetworkService +class FacebookService + def initialize(access_token) + @access_token = access_token + end + def profile client.get_object('me?fields=email,first_name,last_name') end def client - Koala::Facebook::API.new(@oauth_token, ENV['FACEBOOK_SECRET']) + Koala::Facebook::API.new(@access_token, ENV['FACEBOOK_SECRET']) end end diff --git a/app/services/social_network_service.rb b/app/services/social_network_service.rb deleted file mode 100644 index e69de29bb..000000000 diff --git a/config/routes.rb b/config/routes.rb index 5d8b5b1fe..d735a8ada 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,8 +9,14 @@ namespace :api do namespace :v1, defaults: { format: :json } do - get :status, to: 'api#status' - resources :users, only: [:show, :update] + devise_scope :user do + get :status, to: 'api#status' + resources :users, only: [:show, :update] do + controller :sessions do + post :facebook, on: :collection + end + end + end end end end diff --git a/spec/controllers/api/v1/sessions_controller_spec.rb b/spec/controllers/api/v1/sessions_controller_spec.rb index 0d7dbc2c6..80490c0d0 100644 --- a/spec/controllers/api/v1/sessions_controller_spec.rb +++ b/spec/controllers/api/v1/sessions_controller_spec.rb @@ -46,6 +46,86 @@ end end + describe 'POST facebook' do + shared_context 'fail to login with facebook' do + it 'does not returns a successful response' do + post :facebook, params: params + expect(response).to_not have_http_status(:success) + end + + it 'does not create an user' do + expect { post :facebook, params: params }.to change(User, :count).by(0) + end + end + context 'with valid params' do + let(:params) do + { + access_token: '123456', + format: 'json' + } + end + + it 'returns a successful response' do + post :facebook, params: params + expect(response).to have_http_status(:success) + end + + it 'creates an user' do + expect { post :facebook, params: params }.to change(User, :count).by(1) + end + + it 'assigns the information properly' do + post :facebook, params: params + user = User.last + expect(user.first_name).to eq 'Test' + expect(user.email).to eq 'test@facebook.com' + expect(user.uid).to eq '1234567890' + expect(user.provider).to eq 'facebook' + expect(user.encrypted_password).to be_present + end + + it 'returns a valid client and access token' do + post :facebook, params: params + token = response.header['access-token'] + client = response.header['client'] + user = User.last + expect(user.reload.valid_token?(token, client)).to be_truthy + end + + context 'with an user having the same email' do + before do + FactoryGirl.create :user, email: 'test@facebook.com' + end + + it_behaves_like 'fail to login with facebook' + end + + context 'without facebook email' do + let(:params) do + { + access_token: 'without_email', + format: 'json' + } + end + + it 'creates an user' do + expect { post :facebook, params: params }.to change(User, :count).by(1) + end + end + end + + context 'with invalid params' do + let(:params) do + { + access_token: 'invalid', + format: 'json' + } + end + + it_behaves_like 'fail to login with facebook' + end + end + describe 'DELETE destroy' do before do auth_request(user) diff --git a/spec/support/mocks/facebook_service.rb b/spec/support/mocks/facebook_service.rb new file mode 100644 index 000000000..dd169d111 --- /dev/null +++ b/spec/support/mocks/facebook_service.rb @@ -0,0 +1,18 @@ +class FacebookService + def initialize(access_token) + @access_token = access_token + end + + def profile + if @access_token.present? && @access_token != 'invalid' + { + 'first_name' => 'Test', + 'last_name' => 'test', + 'email' => @access_token == 'without_email' ? '' : 'test@facebook.com', + 'id' => '1234567890' + } + else + fail Koala::Facebook::AuthenticationError.new 400, 'error' + end + end +end