Braintree logs out user - ruby-on-rails-3

I am trying out the Braintree Payment gateway in a Rails app. After processing a credit card transaction, my application automatically logs out the logged out user. It only happens after I do a Braintree related transaction. Any ideas why this is happening?

The problem was not directly related to Braintree's Payment Gateway. It had more to do with CSRF and how Ruby on Rails handle HTTP Post. I initially followed the tutorial on Braintree where it used . This caused Rails to loose the session because of security associated with CSRF. To pass Rails's security check, I had to use <%= form_for #myobject, ... } do |f| %>. Lesson learned.

My answer is different. But your answer is informational enough for me.
As i am using ActiveMerchant, i would configure the return_url (usually an action) to skip CSRF checks with an exception for "protect_from_forgery".
class PaymentsController < ApplicationController
protect_from_forgery :except=>[:return]
def return
ret = ActiveMerchant::Billing::Integrations::Ipay88::Notification.new(request.raw_post)
if ret.success?
<code>
else
<code>
end
end

Related

omniauth-devise error: "Validation failed: Email has already been taken"

I am trying to create a rails 4 app with omniauth devise :
signin with facebook
signin with google
signin with linkedin
signin with twitter
Here, I am able to login in with either facebook, linkedin, twitter or google account. But my problem is: my google account email and linkedin email address are same. And login with google and then login with linkedin is giving me this error:
"Validation failed: Email has already been taken"
This is a problem because devise uses :unique => true in migration file for email field.
Can anyone provide me nice idea to handle this error please?
I think that basically, if handling all the cases properly, this can be really complex unless you choose option 1 below (and even then, there are issues to consider which I outline afterwards). Sorry for the length of this answer!
I'm assuming you've done something like this:
https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
so far, which gets you some of the way but doesn't handle the problem you're encountering.
The way I have done this, I have a User who has_many Identities. Identity stores the name of the external service, the user id it tells you and whatever else you want. This means that the same user can log in with multiple identities (twitter, Facebook…). Have you seen:
http://railscasts.com/episodes/235-omniauth-part-1?view=asciicast
http://railscasts.com/episodes/236-omniauth-part-2?view=asciicast
which help with getting going with the User has_many Identities, but still don't deal with your case.
Then to solve your issue, one option is to detect the validation error you're encountering:
if #user.errors.added?(:email, :taken)
# do whatever you want - e.g. one of the 4 options below.
end
and if it occurs, you could either:
just add the identity to identities associated with that existing user who has the same email address and then sign them in.
or
before adding the identity to the existing user, ask for the password for the existing user account (if the account was originally registered via devise on your system), so you'll need to go to some new controller/action and view that handles this flow.
or
Perhaps send an email confirmation (not devise's standard confirmation) to acknowledge that they are linking their new identity to an existing account. This sounds a bit complicated, because you'll have to store the identity temporarily somewhere (probably in a database if you want to cope with them ending their current session before clicking a confirmation link), flagging it as unconfirmed, until they click a confirmation link in an email (which you'll also have to deal with generating).
or
Perhaps force them to authenticate with the other identity that has the same email address. This has the advantage over the previous option that you can just save the new identity info in the session and get them to authenticate using the other service immediately, but obviously there will be some work to handle the flow there.
Option 1 can be less secure because you are trusting that external services have confirmed the user's email address - which perhaps they have - but you want to be sure otherwise someone could sign up with linkedin using their email address and then sign in to your site and then an attacker could sign up with another service but using the same email address. They could then access the person's account on your site if you didn't confirm they really owned the email address somehow (e.g. by using option 2, 3 or 4). If the external services can confirm they've verified the email addresses, then this option should be ok and is by far the simplest - for example, Facebook have a field that tells you the account has been verified (but see my comments below about services that don't need email addresses). If they're merging with an account registered with you directly (doesn't sound like your situation), you should have confirmed the email address they registered using devise's standard Confirmable feature.
Option 2 sounds like it doesn't apply in your case, because you don't mention that a user can register with you directly via devise; only sign in using external services. This means that they have no password on your site that they know. (You've probably added a dummy one to get the devise validation to pass, unless you've disabled that, but they won't know the password unless you've told them somehow, and it could be confusing to them to do that).
Option 3 sounds do-able, though I haven't tried it. It's a bit more laborious for the user.
Option 4 also sounds do-able, though I haven't tried it either.
So far, I've done option 2 because users can only register with my site either directly using devise or via 1 external service. I'll be adding other services soon, so I plan to use option 4 (perhaps only if external service says they haven't confirmed the email address, and option 1 otherwise).
Options 2, 3 and 4 are a fair bit more work than option 1, so it depends if you can confirm that the external services have verified the email addresses and if not, how paranoid you are about attackers being able to access user accounts on your site. Personally, I err on the side of paranoia!
This might also give you some more useful info:
https://github.com/intridea/omniauth/wiki/Managing-Multiple-Providers
but because omniauth itself doesn't concern itself with model issues, it mostly sidesteps it, though it says for your case that it is "probably sufficiently prudent to assume that they are, in fact, the same person who also created the previous user" but you have to be able to trust the external services as I mentioned above.
There are also other things to consider, such as the case where someone has the same email address registered with Facebook and linked in and has signed in with both on your site (so single user account once you've dealt with your issue) and then changes the email associated with their Facebook account but not linkedin. If you always overwrite the email stored in the user table with the one from the external service, then it'll keep changing if they log in with linkedin and then Facebook (but maybe this doesn't matter to you). Alternatively, they may have different email addresses registered with Facebook and linked in and have logged in with both on your site (so 2 different users on your site) and then they change their linked in email address to be the same as the Facebook one. If you update the email address for a user every time they log in via an external service, you'll have your "Email already taken" error, but in this case you have 2 existing users to merge which could be interesting depending on what else in your database is associated with a user...
Also, I don't think twitter returns an email address, so if the same person has logged in with twitter and linkedin, you won't detect this. Furthermore, I think email is optional with Facebook (you can use a mobile phone number), so the same thing can happen with Facebook. My ideal solution would allow the user to merge arbitrary accounts, obviously having to enter whatever credentials are required to confirm they own the accounts they are merging! I haven't done this yet, but it's on my wish list!
I followed these steps and working fine for me:
1. Gemfile
gem 'omniauth-facebook', '1.4.0'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2'
2. config/route.rb
devise_for :users, controllers: { omniauth_callbacks: "omniauth_callbacks" }
3. Links
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
<%= link_to "Sign in with twitter", user_omniauth_authorize_path(:twitter) %>
<%= link_to "Sign in with google", user_omniauth_authorize_path(:google_oauth2) %>
4. controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_filter :authenticate_user!
def all
user = User.from_omniauth(request.env["omniauth.auth"], current_user)
if user.persisted?
flash[:notice] = "you are successfully logged in!!"
sign_in_and_redirect(user)
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
def failure
super
end
alias_method :facebook, :all
alias_method :twitter, :all
alias_method :google_oauth2, :all
end
5. add required fields and model
rails g migration add_social_network_info_columns_to_users name image_url locations
# generate new model Authorization
rails g model Authorization user_id:integer provider uid token secret username
6. models/User.rb
class User < ActiveRecord::Base
require 'securerandom'
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable
has_many :authorizations
# omniauth facebook provider
def self.from_omniauth(auth, current_user)
# check for existing authorization
# Find or create Authorization with: provider, uid, token and secret
authorization = Authorization.where(
:provider => auth.provider,
:uid => auth.uid.to_s,
:token => auth.credentials.token,
:secret => auth.credentials.secret
).first_or_initialize
if authorization.user.blank?
user = current_user.nil? ? User.where('email = ?', auth["info"]["email"]).first : current_user
# save user related data in user table
if user.blank?
User.new(
:email => auth.info.email,
:password => Devise.friendly_token[0,10],
:name => auth.info.name,
:locations => auth.info.location,
:image_url => auth.info.image
)
# since twitter don't provide email,
# so you need to skip validation for twitter.
auth.provider == "twitter" ? user.save!(:validate => false) : user.save!
end
# store authorization related data in authorization table
authorization.username = auth.info.nickname
authorization.user_id = user.id
authorization.save!
end
authorization.user
end
end
6. model/Authorization.rb
class Authorization < ActiveRecord::Base
belongs_to :user
end
source:
https://github.com/mohitjain/social-login-in-rails

Fetch Google plus contacts in Rails app

I have google login feature implemented in my Rails app and now the scenario is to fetch google plus contacts in my app.
There is one Google Plus gem available but i am not sure whether i can fullfill my requirements using that gem.
Whats the best possible solution to this.
Regards,
Karan
You can use the Google APIs Ruby Client and do something like:
client = Google::APIClient.new
plus = client.discovered_api('plus')
# Code to authorize the client.
...
result = client.execute(plus.people.list,
:collection => 'visible',
:userId => 'me')
Where the code that you need to authorize the client depends on the flow that you are using to implement the login.
I've encounter the same situation, nowhere was clear answer.
What gem you are using for Google Authorization? If you are using omniauth-google-oauth2 here is the solution:
I've found post of a guy who encounter different problem AFTER part of application you are asking for was done, you may find it here - http://blog.baugues.com/google-calendar-api-oauth2-and-ruby-on-rails
Down to the code, this how you callback function in a controller (after logging in) should looks like:
def create #lets say it is session#new controller
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
initial_session(omniauth) unless current_user
client = Google::APIClient.new()
client.authorization.access_token = omniauth["credentials"]["token"]
plus = client.discovered_api('plus')
contacts = client.execute(plus.people.list, :collection => 'visible',
:userId => 'me')
raise contacts.inspect.to_s

Rails route namespaced in omniauth

I have this in routes.rb:
namespace :api do
namespace :v1 do
...
devise_for :users, constraints: { format: :json },
:controllers => { :omniauth_callbacks => "auths" }
...
end
end
And produces among others, these routes:
new_api_v1_user_confirmation GET /api/v1/users/confirmation/new(.:format) api/v1/confirmations#new {:format=>:json}
GET /api/v1/users/confirmation(.:format) api/v1/confirmations#show {:format=>:json}
api_v1_user_omniauth_authorize /users/auth/:provider(.:format) auths#passthru {:provider=>/facebook|twitter|linkedin/, :format=>:json}
api_v1_user_omniauth_callback /users/auth/:action/callback(.:format) auths#(?-mix:facebook|twitter|linkedin) {:format=>:json}
How could a get last two routes namespaced, something like:
/api/v1/auth/:provider(.:format)
/api/v1/auth/:provider/callback(.:format)
Guess I should convert my comments into an answer:
For our app we are doing the pure json api thing, with backbone/marionette. To get oAuth working with devise - I removed it from devise. :) Removed the omniauthable property I had set up and removed the omniauth settings from my initializers/devise.rb. Then reading on the omniauth page I implemented it by itself.
My api lives under "/api/v1"
Created the initializers/omniauth.rb file listing my providers and keys. For each provider I also gave it a :path_prefix=>"/api/v1/auth" property.
Create a callback controller within my api called api/v1/oauth_controller.rb This was properly namespaced with modules and contains my callback path from the services.
Updated my routes to setup the callback route for omni. See here: gist.github.com/DaveSanders/5835642
Within OAuthController.create I consumed the details from the provider and go through the basic flow of "does the social network user exist and have a mapped account?" if so, log them in via devise's user.sign_in? If not, create the user and then sign them in.
Redirect back to my app, which then boots up backbone again, which can then go get the logged in user details and use them as needed.
Your implementation may vary, but the way I handle my oAuth accounts is put them in their own tables (Twitters, Facebooks, etc) and then link them into my devise user. This way I can have multiple accounts associated and the user can log in with any of them.
Also, be sure to set your twitter/facebook callback to something like:
http://127.0.0.1:3000/api/v1/auth/twitter/callback
to match your route in dev.
Hope this helps others. If I forgot a step or you get lost, please ask.

Rails 3 and Devise: authenticating a user while performing a POST action

Using Rails 3.0.6, Omniauth 0.2.0 and Devise 1.2.1, I'm encountering the following situation:
I want to offer users the option to authenticate via Facebook. I have a user system set up using Devise and I can successfully auth using Facebook. I've spent several hours trying to code the behavior I want for one specific situation:
user is not logged in
user has a site account
user authenticates via Facebook
I offer the user 2 choices at this point
create an account (can be a dummy account with no provided info)
link this Facebook authentication with an existing account
I'm having trouble with the latter option. The user has already authenticated but I still need him to log in with his site account. I have an action in my AuthenticationsController that will associate this authentication with a logged in user. Devise doesn't seem to offer a way for me to log the user in while staying in the same action, though. This was my first attempt to do this
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
However, using this method, if the user logs in, they're simply redirected to my site's root page. I know I can change Devise's sign-in redirect, but that will be for all sign-ins. I wanted only this situation to have a separate redirect.
After reading through this mailing list question, I tried to extend SessionsController with my own custom behavior:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
This doesn't work either. I've defined my auth_link_existing_user route to use a POST verb (which seems accurate) and redirects can only be GETs.
So now I do have a solution in mind: copy and paste code from Devise's authenticate_user! helper into a new function which can be called within a controller action without redirecting. This seems less than ideal to me because it's duplication of code and increases coupling--a Devise or Warden update that changes this behavior will break my code as well.
Has anyone else tried something like this and come up with a more elegant solution? Do you see a simpler way for me to offer this or similar behavior to my users?
UPDATE: For anyone who wants to use my dirty solution at the end, this is what I did:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
note that this action MUST be placed in your sessions controller. If not, Warden will give you an "invalid email/password" error. It was an incredibly long debugging process to find the source.
With this in place, I use a login form to submit to this action after the user has authenticated.
I like how clean your solution is, though it goes deeper into the stack.
Here is how I've implemented something similar by following the Devise+Omniauth Facebook example on the Devise wiki and modifying the facebook method to pass on the session information to the Login form, with something like this:
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_session_url
end
Then, in your case, I'd check in Login controller action for session["devise.facebook_data"], submit the uid + token with the form and apply_omniauth if present.

How do I implement basic authentication with sessions in Rails?

Just learning Rails via Michael Hartl's tutorial and one of the things we have to do is implement basic authentication with sessions instead of cookies.
I am trying to find any literature online that discusses it, but can't find anything.
The Rails Guides talk about sessions from a security point of view, so they assume you have your authentication working and everything - and are just interested in securing it.
But I would like to roll my own from scratch - a very simple version, nothing fancy at all.
Can someone explain to me, how a basic authentication system would work/look like in Rails 3 or show me some articles and stuff that explain how to roll my own.
Again, doesn't have to be fancy, I just want to understand how they work.
Also, assume that a User model has been created, and user data is stored in a db. So it's just a matter of confirming that there was a successful sign in, and showing them different content.
Thanks.
I figured it out, basically in my sessions controller I did this:
class SessionsController < ApplicationController
def create
user = User.authenticate(params[:session][:email], params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
render 'new'
else
session[:user_id] = user.id
redirect_to user
end
end
def destroy
session[:user_id] = nil
redirect_to root_path
end
end