Skip to content
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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ gem 'name_of_person', '~> 1.1'
gem 'omniauth', '~> 2.1'
gem 'omniauth-google-oauth2', '~> 1.1'
gem 'omniauth-rails_csrf_protection', '~> 1.0'
gem 'google-id-token', '~> 1.4'
gem 'pay', '~> 5.0'
gem 'pg', '~> 1.1'
gem 'possessive', '~> 1.0'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ GEM
csv (>= 3.0.0)
globalid (1.2.1)
activesupport (>= 6.1)
google-id-token (1.4.2)
jwt (>= 1)
grover (1.1.7)
combine_pdf (~> 1.0)
nokogiri (~> 1.0)
Expand Down Expand Up @@ -604,6 +606,7 @@ DEPENDENCIES
faker (~> 2.19)
friendly_id (~> 5.4, >= 5.4.2)
geocoder
google-id-token (~> 1.4)
grover (~> 1.1)
health_check (~> 3.1)
honeypot-captcha (~> 1.0)
Expand Down
62 changes: 62 additions & 0 deletions app/controllers/google_one_tap_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

class GoogleOneTapController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:callback]

def callback
validator = GoogleIDToken::Validator.new
payload = validator.check(params[:credential], Rails.configuration.google_oauth[:client_id])

if payload.nil?
redirect_to new_account_session_path, alert: 'Google authentication failed.'
return
end

account = find_or_create_account(payload)

if account.present? && account.persisted?
sign_in(account)
redirect_to after_sign_in_path, notice: 'Signed in with Google.'
else
redirect_to new_account_session_path, alert: 'Could not create account.'
end
rescue GoogleIDToken::ValidationError => e
Rails.logger.error "Google One Tap validation error: #{e.message}"
redirect_to new_account_session_path, alert: 'Google authentication failed.'
end

private

def find_or_create_account(payload)
email = payload['email']
return nil unless payload['email_verified']

account = Account.find_by(email: email)
return account if account.present?

# Create new account
Account.create(
email: email,
name: payload['name'],
password: Devise.friendly_token[0, 20]
).tap do |new_account|
if new_account.persisted?
attach_photo_from_google(new_account, payload['picture'])
new_account.enrich
SubscribeToContraptionGhostJob.perform_later(new_account.email, new_account.name)
end
end
end

def attach_photo_from_google(account, picture_url)
return if picture_url.blank?

account.attach_photo_from_url(picture_url)
rescue StandardError => e
Rails.logger.error "Failed to attach Google photo: #{e.message}"
end

def after_sign_in_path
stored_location_for(:account) || page_path(current_account)
end
end
13 changes: 13 additions & 0 deletions app/views/marketing_pages/_google_one_tap.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<% if !current_account && Rails.configuration.google_oauth[:client_id].present? %>
<script src="https://accounts.google.com/gsi/client" async defer></script>

<div id="g_id_onload"
data-client_id="<%= Rails.configuration.google_oauth[:client_id] %>"
data-login_uri="<%= "#{request.protocol}#{Rails.configuration.base_host}/auth/google_one_tap/callback" %>"
data-auto_prompt="true"
data-context="signup"
data-ux_mode="popup"
data-auto_select="true"
data-itp_support="true">
</div>
<% end %>
2 changes: 2 additions & 0 deletions app/views/marketing_pages/homepage.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
<%= render 'cta' %>
</div>
<%= render 'footer' %>

<%= render 'google_one_tap' if Rails.configuration.multiuser_mode %>
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
sessions: 'accounts/sessions'
}

post '/auth/google_one_tap/callback', to: 'google_one_tap#callback', :constraints => { :host => Rails.configuration.base_host }

get '/' => 'marketing_pages#homepage', :constraints => { :host => Rails.configuration.base_host }
get '/alternative/:slug' => 'marketing_pages#alternative', :constraints => { :host => Rails.configuration.base_host }
get '/discover' => redirect('/'), :constraints => { :host => Rails.configuration.base_host }
Expand Down