Skip to content
Merged
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@

/app/assets/builds/*
!/app/assets/builds/.keep
.DS_Store
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ gem "tailwind_merge", "~> 1.3.1"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
gem "bcrypt", "~> 3.1.20"

gem "omniauth", "~> 2.1"
gem "omniauth-google-oauth2", "~> 1.1"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]

Expand Down
45 changes: 44 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,18 @@ GEM
erubi (1.13.1)
et-orbi (1.2.11)
tzinfo
faraday (2.13.1)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
hashie (5.0.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
importmap-rails (2.1.0)
Expand All @@ -130,6 +137,8 @@ GEM
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.12.0)
jwt (2.10.1)
base64
kamal (2.8.1)
activesupport (>= 7.0)
base64 (~> 0.2)
Expand Down Expand Up @@ -157,6 +166,10 @@ GEM
mini_mime (1.1.5)
minitest (5.25.5)
msgpack (1.8.0)
multi_xml (0.7.2)
bigdecimal (~> 3.1)
net-http (0.6.0)
uri
net-imap (0.5.8)
date
net-protocol
Expand Down Expand Up @@ -186,6 +199,26 @@ GEM
racc (~> 1.4)
nokogiri (1.18.8-x86_64-linux-musl)
racc (~> 1.4)
oauth2 (2.0.10)
faraday (>= 0.17.3, < 4.0)
jwt (>= 1.0, < 4.0)
logger (~> 1.2)
multi_xml (~> 0.5)
rack (>= 1.2, < 4)
snaky_hash (~> 2.0)
version_gem (>= 1.1.8, < 3)
omniauth (2.1.3)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-google-oauth2 (1.2.1)
jwt (>= 2.9.2)
oauth2 (~> 2.0)
omniauth (~> 2.0)
omniauth-oauth2 (~> 1.8)
omniauth-oauth2 (1.8.0)
oauth2 (>= 1.4, < 3)
omniauth (~> 2.0)
ostruct (0.6.1)
parallel (1.27.0)
parser (3.3.8.0)
Expand Down Expand Up @@ -217,6 +250,10 @@ GEM
raabro (1.4.0)
racc (1.8.1)
rack (3.1.15)
rack-protection (4.1.1)
base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
Expand Down Expand Up @@ -302,7 +339,7 @@ GEM
rbs (>= 3, < 5)
ruby-lsp-rails (0.4.8)
ruby-lsp (>= 0.26.0, < 0.27.0)
ruby-lsp-rspec (0.1.27)
ruby-lsp-rspec (0.1.28)
ruby-lsp (~> 0.26.0)
ruby-progressbar (1.13.0)
ruby_ui (1.0.1)
Expand All @@ -315,6 +352,9 @@ GEM
rubyzip (>= 1.2.2, < 4.0)
websocket (~> 1.0)
sin_lru_redux (2.5.2)
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
solid_cable (3.0.8)
actioncable (>= 7.2)
activejob (>= 7.2)
Expand Down Expand Up @@ -370,6 +410,7 @@ GEM
unicode-emoji (4.0.4)
uri (1.0.3)
useragent (0.16.11)
version_gem (1.1.8)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -404,6 +445,8 @@ DEPENDENCIES
importmap-rails (~> 2.1.0)
jbuilder (~> 2.13.0)
kamal (~> 2.8.1)
omniauth (~> 2.1)
omniauth-google-oauth2 (~> 1.1)
pg (~> 1.5.9)
phlex-rails (~> 2.3.1)
propshaft (~> 1.1.0)
Expand Down
18 changes: 18 additions & 0 deletions app/controllers/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class OmniauthCallbacksController < ApplicationController
allow_unauthenticated_access only: [ :google_oauth2 ]

def google_oauth2
auth = request.env["omniauth.auth"]
user = User.find_or_create_from_omniauth(auth)
if user.persisted?
start_new_session_for(user)
redirect_to after_authentication_url, notice: "Signed in with Google!"
else
redirect_to new_session_path, alert: "Could not authenticate via Google."
end
end

def failure
redirect_to new_session_path, alert: "Google authentication failed."
end
end
2 changes: 1 addition & 1 deletion app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class SessionsController < ApplicationController
allow_unauthenticated_access only: %i[ new create ]
allow_unauthenticated_access only: %i[ new create ] # (Google OAuth handled in OmniauthCallbacksController)
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." }

def new
Expand Down
24 changes: 23 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ class User < ApplicationRecord

validates :name, presence: true
validates :email_address, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :phone, format: { with: /\A\d+\z/, message: "must contain only digits" }
validates :phone, format: { with: /\A\d+\z/, message: "must contain only digits" }, allow_blank: true
validates :password, length: { minimum: 8 }, if: -> { password.present? }

def self.find_or_create_from_omniauth(auth)
user = find_by(provider: auth.provider, uid: auth.uid)
if user
user
else
email = auth.info.email
user = find_by(email_address: email)
if user
user.update(provider: auth.provider, uid: auth.uid)
user
else
create(
provider: auth.provider,
uid: auth.uid,
name: auth.info.name || email,
email_address: email,
password: SecureRandom.hex(16)
)
end
end
end
end
8 changes: 8 additions & 0 deletions app/views/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
<div class="found-it-form-actions">
<%= form.submit "Sign in", class: "found-it-button" %>
</div>

<div class="found-it-divider">
<span>or</span>
</div>

<div class="found-it-form-actions">
<%= link_to "Sign in with Google", "/auth/google_oauth2", class: "found-it-button found-it-google-btn", method: :post %>
</div>

<div class="found-it-links">
<%= link_to "Forgot password?", new_password_path, class: "found-it-link" %>
Expand Down
2 changes: 1 addition & 1 deletion config/credentials.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
zkJDCWKx0S9W8W8G5syXjnY718m+2HPwHMFXPIDEwuvQ9HvXMSIWfyZDzfMxNiGApjbhfURPHW2kSQzKeiGMwQLiYXOfkgaAea5u+t4fT0UQquEpZWTJhB9z89G7gQVxmgeb12e9f3krATP04gBgQH6yBa9w8a0x4tYZEUFEfcdSCoSlyqdVNO2YLB2qDOQ+rTPc0yyA6sWkHTM6RiHl/a/c9tGCDe4C/4PWe6oslau1VnX0g36G0pEvqPsIbW5P4DoJI5tTCWUJrq4KGO2+zyCZlzq8Raw88JVPfLGtbHmkR5zLZmY5m4ZlG6YD+AFkI7RY+UERmw9jlM23y/DM027l2LaQUrIthi5HqeG4Kgw+nJ4X1C8f61NmJ0b3zF/1Tn4VcWdTnQfO6P+Vkik939wmvPa8ZuuL4AVUuNfvm1P/aSzd+gidxcA+IkPe0hb/XuE2PEilJZPvBoNw1+TB/QMp9aCtgPEf4jyoaAyuDnNCD4O5WAEOgilT--YhpIBc6U10frzyzV--YExmVS4SOmYzTciqcirv3A==
TaIkiJyOnI7tbFPn2ly4R93NsZ9fH+s6Z9k10kp2roP1HSkzT8SKAR+Ubb89Y3XrhRXQUrE//nSEDSrW7ftu/1q5HCJyQyY3MPtev/YvV2cyRtC6Lfd7V5MiFzkphS/uFi6ee6b3H9m+h2pUl97S1p4iTbyEOZKsYZ99gcyTMzSKj3vq2IVoUlgZReQeIgXkCCb2+k5yAzts2VwcIpWJdChFKjffu/1sW0yucBetW0gFMMKZGgawxXb5jNVNbNZ07JtHrzcx+uq5gVmMMCtK2PxRvbaDdDka6ERG0IT3TEBTHp94bhz7xShdLxGfgNuIiltGknTgJxFt7dnq+IpfNmawLRdThj2rG7nVISflJSqnUTSLGMLFqkdI6SBmrg9S8J9WnDk+C4F+nxhNqYEJaswAtYP40bWKPLuU6qBBPgF7CX0u6VG7mJjmMszcx6Zmz6SrQazEpF6sjXEYFDlws6/BZgF1gEtgg2fhijFPM78lkeW2F1q+5DKOidS0TvF1BX9EPHTDMjlVOd6oj6/xJg2BbIKzCo/eBbR0xMQmh4+WOT1VEfsC27thrWca6fL2nLuzNarW8drQG80R5wkYUC4pJFpVFhGWwBgJGhdP0g2wxB1X2nTPnIhTbW7uSXjRTgyP0ArAl/MmQX0SMarfdXnHRM6nuulx+YA6aLOC5BR/PsG3OcSvPyn31ky1sc3TSATvJA==--LcWYjGffxups9iCj--oC0o+OVsqgFCUWTHBgWgxg==
13 changes: 13 additions & 0 deletions config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2,
Rails.application.credentials.dig(:google_oauth, :client_id),
Rails.application.credentials.dig(:google_oauth, :client_secret),
{
scope: "email,profile",
prompt: "select_account",
access_type: "offline"
}
end

OmniAuth.config.allowed_request_methods = %i[get]
OmniAuth.config.silence_get_warning = true
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Rails.application.routes.draw do
get "/auth/:provider/callback", to: "omniauth_callbacks#google_oauth2"
get "/auth/failure", to: "omniauth_callbacks#failure"
get "items/index"
# Public item info endpoint by UUID
resources :item_views, only: [ :show, :update ] do
Expand Down
7 changes: 7 additions & 0 deletions db/migrate/20250521140000_add_omniauth_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddOmniauthToUsers < ActiveRecord::Migration[8.0]
def change
add_column :users, :provider, :string
add_column :users, :uid, :string
add_index :users, [ :provider, :uid ], unique: true
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ChangePhoneToNullableInUsers < ActiveRecord::Migration[8.0]
def change
change_column_null :users, :phone, true
end
end
7 changes: 5 additions & 2 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions test/models/user_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def setup
assert_not @user.save, "Saved the user without an email"
end

test "should not save user without phone" do
test "should save user without phone" do
@user.phone = nil
assert_not @user.save, "Saved the user without a phone"
assert @user.save, "Saved the user without a phone"
end

test "should not save user with invalid email" do
Expand All @@ -42,9 +42,10 @@ def setup
assert_not second_user.save, "Saved a user with a duplicate email"
end

test "should not save user with invalid phone" do
@user.phone = "invalid_phone"
assert_not @user.save, "Saved the user with an invalid phone"
test "should save user with phone containing non-digits (they get stripped)" do
@user.phone = "123-456-7890"
assert @user.save, "Failed to save user with formatted phone"
assert_equal "1234567890", @user.phone, "Phone should be normalized to digits only"
end

test "should not save user without password" do
Expand Down