Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling Twitter Login With External Identity Model #109

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/admin/external_identities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
ActiveAdmin.register ExternalIdentity do

# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
#
# Uncomment all parameters which should be permitted for assignment
#
permit_params :user_id, :provider, :uid, :oauth, :handle, :description, :website, :name, :email, :image
#
# or
#
# permit_params do
# permitted = [:user_id, :provider, :uid, :oauth, :handle, :description, :website, :name, :email, :image]
# permitted << :other if params[:action] == 'create' && current_user.admin?
# permitted
# end

index do
column :id
column :name
column :handle
actions
end


end
2 changes: 1 addition & 1 deletion app/admin/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

index do
column :id
column :twitter_handle
column :name
column :last_sign_in_at
column :created_at
column :sign_in_count
column :letters_count do |user|
user.letters.count
end
column :external_identities
column :likes do |user|
user.likes.count
end
Expand Down
13 changes: 9 additions & 4 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@ def get_twitter_image(user)
image_tag image_for_twitter_handle(user), onerror: 'this.error=null;this.src="https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png"'
end


private


RubyConfIndia2023 = Date.parse('2023-08-26')


def image_for_twitter_handle(user)
if user.updated_at.after?(RubyConfIndia2023) && user.image.present?
if user.updated_at.after?(RubyConfIndia2023) && user.twitter_identity&.image.present?
# this is a quick workaround for being unable to fetch images via Cloudinary for new twitter sign_ins due to Twitter API policy changes.
# one problem with continuing to use this long term would be:
# - if a user changes their profile picture, the url saved on our model will become invalid
# we do have a fallback to default image in place, but it's not ideal
# - As an improvement, we can try to cache it (more like a permanent snapshot until their next login)
# - But a proper solution would be to fetch their current profile picture via a service or to periodically refresh it ourself
user.image
user.twitter_identity&.image
else
cloudinary_image_url(user.twitter_handle)
user_handle = user.twitter_identity&.handle
cloudinary_image_url(user_handle)
end
end


def cloudinary_image_url(twitter_handle)
"https://res.cloudinary.com/#{ENV['CLOUDINARY_HANDLE']}/image/twitter_name/#{twitter_handle}.jpg"
end
end
end
48 changes: 29 additions & 19 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,48 @@ class User < ActiveRecord::Base
devise :registerable, :rememberable, :trackable, :database_authenticatable, :omniauthable,
omniauth_providers: [:twitter]


# Setup accessible (or protected) attributes for your model
# attr_accessible :remember_me, :name, :twitter_handle, :twitter_description, :twitter_description, :twitter_oauth, :website, :image


has_many :letters
has_many :likes
has_many :external_identities
has_one :twitter_identity, -> { where provider: 'twitter' }, class_name: "ExternalIdentity"



class << self
def find_for_twitter_oauth(auth)
user = User.where(uid: auth.uid, provider: auth.provider).first

user = User.joins(:external_identities)
.where(external_identities: { uid: auth.uid, provider: auth.provider })
.first
if user
profile_image_url = auth["info"]["image"]
user.update(image: profile_image_url) if profile_image_url.present?

# TODO: handle exceptions later. For now, continue to sign in the user, regardless of whether image url is saved
user.update(name: auth.info.name)
user.twitter_identity.update(
handle: auth.info.nickname,
description: auth.info.description,
website: auth.info.urls.Website,
oauth: auth.credentials.token,
name: auth.info.name,
image: auth.info.image
)
user
else
User.create(
uid: auth["uid"],
provider: auth["provider"],
name: auth["info"]["name"],
twitter_handle: auth["info"]["nickname"],
twitter_description: auth["info"]["description"],
website: (auth["info"]["urls"]["Website"] rescue nil),
twitter_oauth: auth["credentials"]["token"],
image: auth["info"]["image"]
)
user = User.create(name: auth.info.name)
user.external_identities.create(
uid: auth.uid,
provider: auth.provider,
handle: auth.info.nickname,
name: auth.info.name,
image: auth.info.image,
description: auth.info.description,
website: auth.info.urls.Website,
oauth: auth.credentials.token
)
user
end

end

end

end
16 changes: 11 additions & 5 deletions app/views/home/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
-else
= link_to 'Submit', '#', class: 'btn primary pull-right disabled', rel: 'popover', data: {title: 'Oops!!', content: 'You need to login first to submit!!'}


%hr
.row
.span8
Expand All @@ -25,13 +26,15 @@
- else
= link_to 'UpVote', '#', class: 'btn', rel: 'popover', data: {content: "You need to login first to Like it. ", title: 'Oops !!'}


.personalInfo.pull-right
.pull-right.names
= letter.user.name
%br
%strong
%a{ href: "https://www.twitter.com/#{letter.user.twitter_handle}", target: "_blank" }
= "@#{letter.user.twitter_handle}"
- twitter_identity = letter.user.twitter_identity
%a{ href: "https://www.twitter.com/#{twitter_identity.handle}", target: "_blank" }
= "@#{twitter_identity.handle}"
%br
%small.timeAgo
= link_to letter_path(letter) do
Expand All @@ -40,6 +43,7 @@
.pull-right.thumbnail.imageWrapper{ style: 'max-height: 50px; max-width: 50px' }
= get_twitter_image(letter.user)


-# TODO refactor this
- unless letter.user_id == 43
.letterDescription
Expand All @@ -50,9 +54,11 @@
%br
%div
%div.pull-left
%a.twitter-share-button{"data-size": "large", "data-hashtags": "whyloveruby",
href: "https://twitter.com/share", class: 'pull-right',
"data-text": "Here is @#{letter.user.twitter_handle}\'s reason to love Ruby. - #{letter_url(letter)}\n Tell us yours at"}
- twitter_identity = letter.user.twitter_identity
%a.twitter-share-button{"data-size": "large", "data-hashtags": "whyloveruby", "data-url": "#{root_url}",
href: "https://twitter.com/share", class: 'pull-right',
"data-text": "Here is @#{twitter_identity.handle}\'s reason to love Ruby. - #{letter_url(letter)}\n Tell us yours at" }


.clr
:javascript
Expand Down
14 changes: 11 additions & 3 deletions app/views/layouts/application.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
%meta{:content => "Why Love Ruby", :property => "og:site_name"}/
%meta{:content => "Why Love Ruby", :itemprop => "name"}


%title= "I Love Ruby because .."
= csrf_meta_tags
/ Le HTML5 shim, for IE6-8 support of HTML elements
Expand All @@ -32,6 +33,8 @@
%script{:src => "//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"}




%body
.navbar.navbar-fixed-top.navbar-inverse
.navbar-inner
Expand All @@ -47,19 +50,21 @@
%li
%span.welcome
Welcome,
= current_user.twitter_handle
= current_user.twitter_identity&.handle
|
= link_to "Logout", destroy_user_session_path, method: 'delete'
- else
%li= link_to 'Login with Twitter' , user_twitter_omniauth_authorize_path
%li= link_to 'Login with Twitter' , user_twitter_omniauth_authorize_path, method: :post
%li=# link_to "Link 1", "/path1"
%li=# link_to "Link 2", "/path2"
%li=# link_to "Link 3", "/path3"


%div{:style => "position:absolute; top:0; right:0;z-index:1040"}
= link_to 'https://github.com/rtdp/myloveruby', target: '_blank' do
%img{:alt => "Beta Version", :border => "0", :src => "/forkme_right_red.png"}


.container
.row
.span12
Expand Down Expand Up @@ -109,13 +114,15 @@
.thumbnail.pull-left{ style: 'max-height: 50px; max-width: 50px' }
= get_twitter_image(l.user)
.info.pull-left
= truncate("#{l.user.name} (#{l.user.twitter_handle})", length: 27)
- twitter_identity = l.user.twitter_identity
= truncate("#{twitter_identity.name} (#{twitter_identity.handle})", length: 27)
%br
%small.timeAgo
= link_to letter_path(l) do
= time_ago_in_words l.created_at
ago


%footer
%hr
%p
Expand All @@ -132,6 +139,7 @@
_gaq.push(['_setAccount', 'UA-10763136-15']);
_gaq.push(['_trackPageview']);


(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
Expand Down
10 changes: 6 additions & 4 deletions app/views/letters/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
= @letter.user.name
%br
%strong
%a{ href: "https://www.twitter.com/#{@letter.user.twitter_handle}", target: "_blank" }
= "@#{@letter.user.twitter_handle}"
- twitter_identity = @letter.user.twitter_identity
%a{ href: "https://www.twitter.com/#{twitter_identity.handle}", target: "_blank" }
= "@#{twitter_identity.handle}"
%br
%small.timeAgo
= link_to letter_path(@letter) do
Expand All @@ -28,9 +29,10 @@
%br
%div
%div.pull-left
- twitter_identity = @letter.user.twitter_identity
%a.twitter-share-button{"data-size": "large", "data-hashtags": "whyloveruby", "data-url": "#{root_url}",
href: "https://twitter.com/share", class: 'pull-right',
"data-text": "Here is @#{@letter.user.twitter_handle}\'s reason to love Ruby. - #{letter_url(@letter)}\n Tell us yours at" }
"data-text": "Here is @#{twitter_identity.handle}\'s reason to love Ruby. - #{letter_url(@letter)}\n Tell us yours at" }
.clr
:javascript
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
Expand All @@ -43,4 +45,4 @@
method: :delete,
data: {confirm: "Are you sure?"}
%br
%hr
%hr
8 changes: 8 additions & 0 deletions spec/factories/external_identity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FactoryBot.define do
factory :external_identity do
association :user
provider { "SomeProvider" }
uid { "SomeUID" }
end
end

18 changes: 12 additions & 6 deletions spec/helpers/application_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,27 @@

context 'get_twitter_image for User who last signed in before RubyIndiaConf 2023' do
it 'should render image tag with the twitter handle' do
user = User.new(twitter_handle: 'my_twitter_handle', updated_at: Date.parse('2022-12-31').end_of_day)
user = User.create(updated_at: Date.parse('2022-12-31').end_of_day)
user.external_identities.create(handle: "my_twitter_handle", provider: "twitter", uid: "252152")
expect(helper.get_twitter_image(user)).to match("cloudinary.*my_twitter_handle\.jpg")
end
end
context 'get_twitter_image for User who last signed in after RubyIndiaConf 2023' do
it 'should render image tag with twitter handle (in the unlikely case, our User model does not contain the image url)' do
user = User.new(twitter_handle: 'my_twitter_handle', updated_at: Date.parse('2024-01-01').end_of_day)
user = User.create(updated_at: Date.parse('2024-01-01').end_of_day)
user.external_identities.create(handle: "my_twitter_handle", provider: "twitter", uid: "252152")
expect(helper.get_twitter_image(user)).to match("cloudinary.*my_twitter_handle\.jpg")
end
it 'should render image tag using the URL saved on the User model' do
user = User.new(
twitter_handle: 'my_twitter_handle',
image: 'https://pbs.twimg.com/profile_images/123456789/my_selfie.jpg',
it 'should render image tag using the URL saved on the ExternalIdentity model' do
user = User.create(
updated_at: Date.parse('2024-01-01').end_of_day
)
user.external_identities.create(
handle: 'my_twitter_handle',
provider: 'twitter',
uid: '123456',
image: 'https://pbs.twimg.com/profile_images/123456789/my_selfie.jpg'
)
image_url = helper.get_twitter_image(user)
expect(image_url).to_not match("my_twitter_handle\.jpg")
expect(image_url).to match("my_selfie\.jpg")
Expand Down
12 changes: 11 additions & 1 deletion spec/models/external_identity_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
require 'rails_helper'

RSpec.describe ExternalIdentity, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
describe 'ActiveRecord associations' do
it { should belong_to(:user) }
end

describe 'ActiveModel validations' do
subject { create(:external_identity) }
it { should validate_presence_of(:provider) }
it { should validate_uniqueness_of(:provider).scoped_to(:user_id).with_message('has already been taken for this user') }

it { should validate_uniqueness_of(:uid).scoped_to(:provider).with_message('has already been taken for this provider') }
end
end
1 change: 1 addition & 0 deletions spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
describe 'ActiveRecord associations' do
it { should have_many(:letters) }
it { should have_many(:likes) }
it { should have_many(:external_identities) }
end
end
Loading