From 89e18b2bd6ffc0824f36e96bb316d0ddaafc2274 Mon Sep 17 00:00:00 2001 From: Bell Isabell Date: Sun, 8 Feb 2026 16:57:37 -0800 Subject: [PATCH 1/2] Add Google One Tap sign-in to marketing homepage - Add google-id-token gem for JWT verification - Create GoogleOneTapController to handle callback - Add route for /auth/google_one_tap/callback - Add One Tap script and config to homepage (multiuser mode only) One Tap shows a popup for users already signed into Google, allowing one-click sign-up/sign-in. Falls back gracefully if user dismisses or isn't signed into Google. --- Gemfile | 1 + app/controllers/google_one_tap_controller.rb | 62 +++++++++++++++++++ .../marketing_pages/_google_one_tap.html.erb | 13 ++++ app/views/marketing_pages/homepage.html.erb | 2 + config/routes.rb | 2 + 5 files changed, 80 insertions(+) create mode 100644 app/controllers/google_one_tap_controller.rb create mode 100644 app/views/marketing_pages/_google_one_tap.html.erb 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/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 } From 9061325bc3a13d578185c419ddd393fbdcb3f761 Mon Sep 17 00:00:00 2001 From: Bell Isabell Date: Sun, 8 Feb 2026 18:12:46 -0800 Subject: [PATCH 2/2] Update Gemfile.lock for google-id-token gem (conservative) --- Gemfile.lock | 3 +++ 1 file changed, 3 insertions(+) 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)