diff --git a/Gemfile b/Gemfile index 5a9077b..6c41bf6 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index e68b0b2..df5e0d3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -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) diff --git a/app/controllers/google_one_tap_controller.rb b/app/controllers/google_one_tap_controller.rb new file mode 100644 index 0000000..2c99b09 --- /dev/null +++ b/app/controllers/google_one_tap_controller.rb @@ -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 diff --git a/app/views/marketing_pages/_google_one_tap.html.erb b/app/views/marketing_pages/_google_one_tap.html.erb new file mode 100644 index 0000000..d744cac --- /dev/null +++ b/app/views/marketing_pages/_google_one_tap.html.erb @@ -0,0 +1,13 @@ +<% if !current_account && Rails.configuration.google_oauth[:client_id].present? %> + + +
" + data-auto_prompt="true" + data-context="signup" + data-ux_mode="popup" + data-auto_select="true" + data-itp_support="true"> +
+<% end %> diff --git a/app/views/marketing_pages/homepage.html.erb b/app/views/marketing_pages/homepage.html.erb index 7c45400..86ad50f 100644 --- a/app/views/marketing_pages/homepage.html.erb +++ b/app/views/marketing_pages/homepage.html.erb @@ -10,3 +10,5 @@ <%= render 'cta' %> <%= render 'footer' %> + +<%= render 'google_one_tap' if Rails.configuration.multiuser_mode %> diff --git a/config/routes.rb b/config/routes.rb index 6c1067b..61e33ad 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 }