Skip to content

Commit

Permalink
1286 - Adopter Authentication: Add Oauth with google (#1309)
Browse files Browse the repository at this point in the history
* Google Login implementation

* lint fix and removed unwanted file

* Comment for setting oauth and nesting modules

* Remove password fields from  settings for Google oauth users

* Lint fix

* Add production client ID and secret

---------

Co-authored-by: Ben <robinsonbena@gmail.com>
  • Loading branch information
sarvaiyanidhi and kasugaijin authored Jan 6, 2025
1 parent fe539e7 commit 437fe9d
Show file tree
Hide file tree
Showing 18 changed files with 283 additions and 24 deletions.
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ gem "strong_migrations", "~> 2.1"
# Track errors in prod
gem "bugsnag", "~> 6.27"

# Google OAuth
gem "omniauth"
gem "omniauth-google-oauth2"
gem "omniauth-rails_csrf_protection"

group :development, :test, :staging do
gem "faker"
end
Expand Down
38 changes: 38 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ GEM
guard (~> 2.8)
guard-compat (~> 1.0)
multi_json (~> 1.8)
hashie (5.0.0)
http_parser.rb (0.8.0)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
Expand All @@ -286,6 +287,8 @@ GEM
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.9.0)
jwt (2.10.1)
base64
kamal (2.4.0)
activesupport (>= 7.0)
base64 (~> 0.2)
Expand Down Expand Up @@ -332,6 +335,8 @@ GEM
ruby2_keywords (>= 0.0.5)
msgpack (1.7.5)
multi_json (1.15.0)
multi_xml (0.7.1)
bigdecimal (~> 3.1)
multipart-post (2.4.1)
nenv (0.3.0)
net-http-persistent (4.0.5)
Expand Down Expand Up @@ -363,6 +368,28 @@ GEM
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
oauth2 (2.0.9)
faraday (>= 0.17.3, < 3.0)
jwt (>= 1.0, < 3.0)
multi_xml (~> 0.5)
rack (>= 1.2, < 4)
snaky_hash (~> 2.0)
version_gem (~> 1.1)
omniauth (2.1.2)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-google-oauth2 (1.2.0)
jwt (>= 2.9)
oauth2 (~> 2.0)
omniauth (~> 2.0)
omniauth-oauth2 (~> 1.8)
omniauth-oauth2 (1.8.0)
oauth2 (>= 1.4, < 3)
omniauth (~> 2.0)
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
orm_adapter (0.5.0)
ostruct (0.6.1)
pagy (9.3.3)
Expand Down Expand Up @@ -390,6 +417,10 @@ GEM
raabro (1.4.0)
racc (1.8.1)
rack (3.1.8)
rack-protection (4.1.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.2.0)
Expand Down Expand Up @@ -495,6 +526,9 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
solid_cable (3.0.5)
actioncable (>= 7.2)
activejob (>= 7.2)
Expand Down Expand Up @@ -544,6 +578,7 @@ GEM
unicode-emoji (4.0.4)
uri (1.0.2)
useragent (0.16.11)
version_gem (1.1.4)
view_component (3.21.0)
activesupport (>= 5.2.0, < 8.1)
concurrent-ruby (~> 1.0)
Expand Down Expand Up @@ -604,6 +639,9 @@ DEPENDENCIES
kamal
letter_opener_web (~> 3.0)
mocha
omniauth
omniauth-google-oauth2
omniauth-rails_csrf_protection
pagy
pg (~> 1.5)
phonelib
Expand Down
44 changes: 44 additions & 0 deletions app/assets/images/google.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions app/controllers/registrations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ def create
end
end

def update_resource(resource, params)
if resource.google_oauth_user?
params.delete("current_password")
resource.update_without_password(params)
else
resource.update_with_password(params)
end
end

private

def set_layout
Expand Down
20 changes: 20 additions & 0 deletions app/controllers/users/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Users
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include OrganizationScopable

def google_oauth2
@user = User.from_omniauth(request.env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: "Google") if is_navigational_format?
else
session["devise.google_data"] = request.env["omniauth.auth"].except(:extra)
redirect_to new_user_registration_url, alert: @user.errors.full_messages.join("\n")
end
end

def failure
redirect_to root_path
end
end
end
27 changes: 27 additions & 0 deletions app/models/concerns/omniauthable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Omniauthable
extend ActiveSupport::Concern

included do
devise :omniauthable, omniauth_providers: [:google_oauth2]
end

class_methods do
def from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid, organization_id: Current.organization.id).first_or_create do |user|
user.assign_attributes_from_auth(auth)
user.set_adopter_role
end
end
end

def assign_attributes_from_auth(auth)
self.email = auth.info.email
self.password = Devise.friendly_token[0, 20]
self.first_name = auth.info.first_name if respond_to?(:first_name)
self.last_name = auth.info.last_name if respond_to?(:last_name)
end

def set_adopter_role
add_role("adopter", Current.organization)
end
end
7 changes: 7 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
# invitations_count :integer default(0)
# invited_by_type :string
# last_name :string not null
# provider :string
# remember_created_at :datetime
# reset_password_sent_at :datetime
# reset_password_token :string
# tos_agreement :boolean
# uid :string
# created_at :datetime not null
# updated_at :datetime not null
# invited_by_id :bigint
Expand All @@ -43,6 +45,7 @@ class User < ApplicationRecord
include Avatarable
include Authorizable
include RoleChangeable
include Omniauthable

acts_as_tenant(:organization)
default_scope do
Expand Down Expand Up @@ -146,6 +149,10 @@ def deactivated?
!!deactivated_at
end

def google_oauth_user?
provider == "google_oauth2" && uid.present?
end

private

def downcase_email
Expand Down
1 change: 1 addition & 0 deletions app/services/organizations/create_service.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Class to create a new location, organization, user, and staff account with role admin.
# Be sure to add the callback URL for the new org in for Google OAuth dev account
# An email is sent to admin user if all steps are successful.
# Be sure to use the Country and State codes from countries_states.yml
# call with Organizations::CreateService.new.signal(args)
Expand Down
42 changes: 23 additions & 19 deletions app/views/devise/registrations/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@
<div>Currently waiting confirmation for:
<%= resource.unconfirmed_email %></div>
<% end %>
<div class="form-group mb-3">
<%= f.password_field :password, autocomplete: "new-password",
class: 'form-control', label: 'New password' %>
<i>(leave blank if you don't want to change it)</i><br />
<% if @minimum_password_length %>
<em><%= @minimum_password_length %> characters minimum</em>
<% end %>
</div>
<div class="form-group mb-3">
<%= f.password_field :password_confirmation, autocomplete: "new-password",
class: 'form-control', label: 'Confirm new password' %>
</div>
<% unless resource.google_oauth_user? %>
<div class="form-group mb-3">
<%= f.password_field :password, autocomplete: "new-password",
class: 'form-control', label: 'New password' %>
<i>(leave blank if you don't want to change it)</i><br />
<% if @minimum_password_length %>
<em><%= @minimum_password_length %> characters minimum</em>
<% end %>
</div>
<div class="form-group mb-3">
<%= f.password_field :password_confirmation, autocomplete: "new-password",
class: 'form-control', label: 'Confirm new password' %>
</div>
<% end %>
<div class="form-group mb-3">
<%= f.text_field :first_name,
class: 'form-control' %>
Expand All @@ -39,13 +41,15 @@
<%= f.text_field :last_name,
class: 'form-control' %>
</div>
<div class="form-group mb-3">
<%= f.password_field :current_password,
autocomplete: "current-password",
required: true,
class: 'form-control' %>
<i>(we need your current password to confirm your changes)</i><br/>
</div>
<% unless resource.google_oauth_user? %>
<div class="form-group mb-3">
<%= f.password_field :current_password,
autocomplete: "current-password",
required: true,
class: 'form-control' %>
<i>(we need your current password to confirm your changes)</i><br/>
</div>
<% end %>
<% if resource.avatar.id %>
<div class='form-group mt-3'>
<label class="form-label">
Expand Down
20 changes: 19 additions & 1 deletion app/views/devise/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,29 @@
<%= link_to "Forgot your password?", new_password_path(resource_name), class: 'small text-end' %>
</div>
</div>
<div class='mb-5'>
<div class='mb-1'>
<div class="d-grid">
<%= f.submit "Log in", class: 'btn btn-primary' %>

</div>
</div>

<div class="d-grid gap-3 mb-4">
<div class="position-relative mt-4">
<hr class="bg-gray-300">
<p class="position-absolute top-50 start-50 translate-middle bg-white px-3 text-muted small">
or
</p>
</div>
<%= link_to user_google_oauth2_omniauth_authorize_path,
method: :post,
data: { turbo: 'false' },
class: "btn border w-100 d-flex align-items-center justify-content-center gap-2",
onclick: "this.innerHTML='<span class=\"spinner-border spinner-border-sm me-2\" role=\"status\" aria-hidden=\"true\"></span>Signing in...'" do %>
<%= image_tag "google.svg", size: "20x20", alt: "Google" %>
Continue with Google
<% end %>
</div>
<% end %>
</div>
</div>
Expand Down
17 changes: 16 additions & 1 deletion app/views/devise/shared/_links.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@

<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br />
<div class="d-grid gap-3 mb-4">
<div class="position-relative mt-4">
<hr class="bg-gray-300">
<p class="position-absolute top-50 start-50 translate-middle bg-white px-3 text-muted small">
or
</p>
</div>
<%= link_to user_google_oauth2_omniauth_authorize_path,
method: :post,
data: { turbo: 'false' },
class: "btn border w-100 d-flex align-items-center justify-content-center gap-2",
onclick: "this.innerHTML='<span class=\"spinner-border spinner-border-sm me-2\" role=\"status\" aria-hidden=\"true\"></span>Signing in...'" do %>
<%= image_tag "google.svg", size: "20x20", alt: "Google" %>
Continue with Google
<% end %>
</div>
<% end %>
<% end %>
2 changes: 1 addition & 1 deletion config/credentials/production.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4ERfYFtT9kWCK++LezB5gW0pmP8hN+mI1HQ9+Hx9N5TWfR7eIsQFlCoKKFrM3RSe3p5ZSDL5YTdFBKIFscYa23Sx9sgb0dw8hLc1Q0ItD4sfVqnbZPctFrWnUIlLihLthz2O94rhzjk0sk0rer1KMaD3ZPggl7sAcGY8Osy+eRUVDygvRhX0nPDEpgRo1Wh5xzda3iDW5LaIjICBLvUzSjiXr9tp/HFKzprwoJtqqeO0H54L4TVRmocfbtVsyNl8w4CDOftloYH1KfvfnQzzPquEuIFFvw2WuverL3BZ04fnDpvL6xMWM4Vt0jtA+pzmZoYZo29T0LoX+33j8v4UUV2OddkxR7rUXYWdpE7/U3NFc5GDM7ynyRh+Jp7+nlnuYbHdw1wbPW54HwY+mlq9G/Out3hNo/nTcY171xl9xI7utMKD0wvIQIVqId5xoAT+hJosJXcbM24tCvjd4Q3zGeH0xEwvgmgCojcTwcCX7Jn9tqU1Iy3F4KUQpll/kPz32TmruZA3pPGgqWNfr96ogJ1qPaWvI+6R9ZGmbKAdrufLdm4//pZBMUkXlRBk89Sl0dSZliw3LfSyAIOsVjOkQfftU+c63iWZCQ5eUxoIPaDo4p8YkftTbUKZmk+pAgon2kau2CtLVF9FbjA+MFB5xVmPi+JchKDT+knWjPIHE0vg9ePBoU26S4HAu29JQnLnwEAfK1hdJkClPl9HTIdDaZ38Ld1b68MR96s9ZlLk2EARm9Pc8UEjtNXS2o+7t1tLZoVllXd+MrWPHjaoKUWMaSc2TnNzMtVnVfB+Bxi6qYDs0Blbjwk+mwWsMsnlDIF38Fbrkt8YyMfJ9CBeFaAzi1BJw54NJ784UluEo3QUc5T1WRan3H6Nb5dgBSq9VhWGV0QKSJEtc2IvmDrfE3qn8IaR3E7NZbV6UoeczwEwnTI9HuvrZ0WkNdNugx8E1Dk/c8ZVjLmtcsB2XlQtW4mtzWGj+k9ZUuRCXe032xQwJbgHtBHbtVLi4SbRvYyiU5m4JfORuaMMu/V9EZXPjLvAyWdRlVKDptu+CbPgbbG0GPMSOpx/XfV0QgolZ0kgUTemawSTeBDVnkvufwIaOfImfEt5U/QnMUYJyR0ZBpjAXNqaE+bb+qVe+yBOJIWY6SETlPfIhWDfVfCDm+RY--BTHcPE2kQRjxri/C--sSAa87XeJ0VAblR+MOmoBg==
W3w8nvvQhn3a9iVi1l/oSK3ob5pldwaW9cH3neLXfjhHJK5oXVHr+kxgE6PY0L6xiEjhuaB+TB/quXnAucBtaETqL6mO/3jx+NOHZWW8PeJJh3OkAJQHReGSSOH3XAM8HIpLDdgUWEO6mjKJTE+tg88pTA0uMi6KW2/XcfY6+sTiKgygZ0lnjUXEuKsOf2hgPBdHe0Q1xO1MpnDuK9641iljYKpT8NeEbi3hx02DGtNXgJ/U0cWZpP3YNpMGsWwj1zmai5MfX7XnS/kL+TKG1eilRVVccC8rllUEX65QWH37WKhHJjGJkGyXucFsO1Z6FIEDFyQUrweDf/iF9AilpBFObccIOIPicg0b475ZMylCrq4VZ4EwD4W2UTloMFx6OVlvWBckCP8BiY80q7N7deBkPkNs9vpNft1hO4eMM6a9bU09Q+EYZjsn4M77k0KBkKGBAS1Rc3X13Y0gR9CykVBRd6Akq1PJa4X+yTf/zzA/TySKB44t+tbrHBlIxt4aWtHI2brvqE6gE+/oBuTaVjvPZYFd5YccCzdGXgs+5rRPDOSP6VqAfn0mKfPSe2QXPavYLeFOP/5dYZg1JaGCOPtZvyhcrqvx4N8gwV1F7Q75z1tUqdu1djALho1J3kaAurAhDtjgiMHQ6KJuL++B13QsXhfMe2TJ3imqf+bY8Y+cPTNyNvkq/lql7qSAP4St7C1qDOr4YFD34Ye+hb8ePaKFxV/PSEk7Z1LDLnsiWI+cZhKc6vy22nvs262URhQ/MWtSM0KZD9Mh38XjoG+PYkcQ507pIYEyH977oeMLgqBDasBknaTxefeaaN8q2fLzonfhrKo2jqTdCdUP9ZgEqsFxWrQR33LmNv5/boN3HOVU/7wF1AX4QUZw6IRs6w/qzbscBgVevDF3SSDS3eMQ6a5gith3o2pBKfuG+wkRv6X6SnZ1Tj3DjsY44XX8eP9tD3/Nncg25jX1P7u9ZxBH5asWgz10g5cBtaWrESeoaBYjGzOPXtjbJAdv1AdSp64lkoRCRZgRWMpLVOMK3ZCWZCREZUuV8dNVWoBK7930K34o13h6ifRVd7GUviezeOdzsAQRDe1THc0Hvrs09E8qXixXPqdpSWEceozMgXaeK/ZyixRHJ9Xl4u8RzAPc70QJUh6zRY/Mf+QRBsv4czmzjoskaIpMpXohA/SbQzSUfrsYQBEnYEaocxS9S1CI6ikwGWU0trXj77kzB4TUqK640k+KS4DNQe3F/2Lbf0Gu8D+msnrIusi5GnuOfXFjS109RO7faUngVxSpVxuBLcKErzpA96IKsCTMeJq+6N/2WwryW9rzRbtFKLKR6Ip8C0d+RysVA/Qe4RV3nujLhncyUHd1TGmUiCwX--D/R1K1hn1a4Zav38--9R0KJV0yoSyKYDs4fBxyXg==
5 changes: 5 additions & 0 deletions config/initializers/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,9 @@
config.warden do |manager|
manager.failure_app = AuthenticationFailureApp
end

config.omniauth :google_oauth2,
Rails.application.credentials.dig(:google, :client_id),
Rails.application.credentials.dig(:google, :client_secret),
{}
end
1 change: 1 addition & 0 deletions config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OmniAuth.config.allowed_request_methods = [:get, :post]
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
devise_for :users, controllers: {
registrations: "registrations",
sessions: "users/sessions",
invitations: "organizations/staff/invitations"
invitations: "organizations/staff/invitations",
omniauth_callbacks: "users/omniauth_callbacks"
}

# Application Scope
Expand Down
6 changes: 6 additions & 0 deletions db/migrate/20241228004147_add_omniauth_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddOmniauthToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
end
end
Loading

0 comments on commit 437fe9d

Please sign in to comment.