diff --git a/Gemfile b/Gemfile index 8533954..5a6a0eb 100644 --- a/Gemfile +++ b/Gemfile @@ -34,6 +34,7 @@ group :test do gem "mocha", "~> 2.5.0" gem "simplecov", require: false gem "simplecov-tailwindcss", require: false + gem "faker" end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index b9b3158..519954b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -141,6 +141,8 @@ GEM erubi (1.13.0) et-orbi (1.2.11) tzinfo + faker (3.5.1) + i18n (>= 1.8.11, < 2) faraday (2.12.1) faraday-net_http (>= 2.0, < 3.5) json @@ -513,6 +515,7 @@ DEPENDENCIES debug dotenv-rails erb_lint + faker friendly_id (~> 5.5.1) kamal (~> 2.3.0) litestream (~> 0.12.0) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4b56c28..6fc7439 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,7 +6,7 @@ class ApplicationController < ActionController::Base helper_method :user_signed_in? def authenticate_user! - redirect_to root_path, alert: "Requires authentication" unless user_signed_in? + redirect_to root_path, alert: "Please sign in first." unless user_signed_in? end def current_user diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8e6a06a..15a98ba 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,11 +1,9 @@ class SessionsController < ApplicationController def create - Rails.logger.info "OmniAuth Auth Hash: #{request.env['omniauth.auth'].inspect}" @user = User.create_from_omniauth(request.env["omniauth.auth"]) if @user.persisted? session[:user_id] = @user.id - redirect_path = request.env["omniauth.origin"] || dashboard_path redirect_to redirect_path, notice: "Logged in as #{@user.name}" else redirect_to root_url, alert: "Failure" @@ -14,12 +12,22 @@ def create def destroy session[:user_id] = nil - redirect_to root_path, notice: "Logged out" + redirect_to root_path, notice: "Signed out" end def failure - Rails.logger.error "OmniAuth Failure: #{request.env['omniauth.error'].inspect}" - Rails.logger.error "OmniAuth Error Type: #{request.env['omniauth.error.type'].inspect}" redirect_to root_path, alert: "Authentication failed" end + + private + + def redirect_path + origin_path = URI(request.env["omniauth.origin"]).path rescue "/" + + if origin_path.present? && origin_path != "/" && origin_path != root_path + request.env["omniauth.origin"] + else + dashboard_path + end + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..b1376ba --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,9 @@ +class UsersController < ApplicationController + before_action :authenticate_user! + + def show + @user = User.friendly.find(params[:id]) + rescue ActiveRecord::RecordNotFound + redirect_to root_path, alert: "User not found" + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/models/element.rb b/app/models/element.rb index 599b3cb..620e488 100644 --- a/app/models/element.rb +++ b/app/models/element.rb @@ -3,7 +3,10 @@ # Table name: elements # # id :integer not null, primary key +# description :text +# image_path :string # label :string not null +# position :integer # variant_type :string # created_at :datetime not null # updated_at :datetime not null @@ -13,6 +16,7 @@ # Indexes # # index_elements_on_label (label) +# index_elements_on_position (position) # index_elements_on_sub_group_id (sub_group_id) # index_elements_on_variant_type_and_variant_id (variant_type,variant_id) # diff --git a/app/models/group.rb b/app/models/group.rb index 91c4f29..6d93029 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -2,11 +2,12 @@ # # Table name: groups # -# id :integer not null, primary key -# title :string not null -# created_at :datetime not null -# updated_at :datetime not null -# page_id :integer not null +# id :integer not null, primary key +# behavior_type :string +# title :string not null +# created_at :datetime not null +# updated_at :datetime not null +# page_id :integer not null # # Indexes # diff --git a/app/models/user.rb b/app/models/user.rb index b465ad9..b4d4226 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,7 +1,28 @@ +# == Schema Information +# +# Table name: users +# +# id :integer not null, primary key +# email :string +# image :string +# name :string +# provider :string +# slug :string +# uid :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_slug (slug) UNIQUE +# class User < ApplicationRecord + extend FriendlyId + friendly_id :name, use: :slugged + validates :provider, presence: true validates :uid, presence: true, uniqueness: true - validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, presence: true, uniqueness: true + validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true def self.create_from_omniauth(omniauth_params) provider = omniauth_params.provider diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb new file mode 100644 index 0000000..cd37ce8 --- /dev/null +++ b/app/views/users/show.html.erb @@ -0,0 +1 @@ +Welcome <%= @user.name %> diff --git a/config/routes.rb b/config/routes.rb index ac2954d..b02acc0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + get "users/show" get "up", to: "rails/health#show", as: :rails_health_check mount MissionControl::Jobs::Engine, at: "/jobs" @@ -19,6 +20,7 @@ get "auth/failure", to: "sessions#failure" delete "sign_out", to: "sessions#destroy" + resources :users, only: [ :show ], path: "" root to: "static#home" end diff --git a/db/migrate/20241121182456_add_slug_to_users.rb b/db/migrate/20241121182456_add_slug_to_users.rb new file mode 100644 index 0000000..7626285 --- /dev/null +++ b/db/migrate/20241121182456_add_slug_to_users.rb @@ -0,0 +1,6 @@ +class AddSlugToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :slug, :string + add_index :users, :slug, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 9482afc..ceb5e78 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2024_11_19_161136) do +ActiveRecord::Schema[8.0].define(version: 2024_11_21_182456) do create_table "_litestream_lock", id: false, force: :cascade do |t| t.integer "id" end @@ -219,6 +219,8 @@ t.string "image" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "slug" + t.index ["slug"], name: "index_users_on_slug", unique: true end add_foreign_key "elements", "sub_groups" diff --git a/test/controllers/dashboard_controller_test.rb b/test/controllers/dashboard_controller_test.rb index 1b41b20..883c8c6 100644 --- a/test/controllers/dashboard_controller_test.rb +++ b/test/controllers/dashboard_controller_test.rb @@ -1,4 +1,10 @@ require "test_helper" class DashboardControllerTest < ActionDispatch::IntegrationTest + test "should get show" do + user = users(:john) + sign_in(user) + get dashboard_path + assert_response :success + end end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000..88a7116 --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,120 @@ +require "test_helper" + +class SessionControllerTest < ActionDispatch::IntegrationTest + setup do + # without this omniauth.origin can be set to the one in previous test (tags_url) resulting in flaky tests + OmniAuth.config.before_callback_phase do |env| + env["omniauth.origin"] = nil + end + end + + test "guest should not be able to access dashboard" do + get dashboard_path + assert_redirected_to root_path + + get dashboard_url + assert_response :redirect + assert_redirected_to root_path + assert_equal "Please sign in first.", flash[:alert] + end + + test "authenticated github user should get dashboard" do + login_with_github + + get dashboard_url + assert_response :success + + delete sign_out_path + assert_response :redirect + assert_redirected_to root_url + assert_equal "Signed out", flash[:notice] + end + + + test "successful github sign in" do + login_with_github + + assert_response :redirect + assert_redirected_to dashboard_url + email = OmniAuth.config.mock_auth[:github][:info][:email] + name = OmniAuth.config.mock_auth[:github][:info][:name] + assert_equal "Logged in as #{name}", flash[:notice] + assert User.pluck(:email).include?(email) + assert_equal controller.current_user.email, email + end + + test "github oauth failure" do + silence_omniauth_logger do + OmniAuth.config.test_mode = true + OmniAuth.config.mock_auth[:github] = :invalid_credentials + Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:github] + get "/auth/github/callback" + follow_redirect! + + assert_response :redirect + assert_redirected_to root_path + assert_equal "Authentication failed", flash[:alert] + assert_nil controller.current_user + end + end + + test "github auth with no email" do + silence_omniauth_logger do + auth_hash = OmniAuth::AuthHash.new({ + provider: "github", + uid: "123545", + info: { + nickname: "test nickname", + name: "test name", + email: nil, + image: "https://avatars.githubusercontent.com/u/123545?v=3" + } + }) + + OmniAuth.config.mock_auth[:github] = auth_hash + Rails.application.env_config["omniauth.auth"] = auth_hash + + get "/auth/github/callback" + + assert_redirected_to dashboard_path + assert_match "test name", flash[:notice] + end + end + + test "redirect to previous page after login" do + user = users(:john) + + + OmniAuth.config.before_callback_phase do |env| + env["omniauth.origin"] = user_path(user) + end + + sign_in(user) + assert_response :redirect + assert_redirected_to user_path(user) + end + + test "github auth fails when user cannot be persisted" do + silence_omniauth_logger do + auth_hash = OmniAuth::AuthHash.new({ + provider: "github", + uid: "", # Invalid uid (blank) will cause persistence to fail + info: { + nickname: "test nickname", + name: "foo", + email: "test@example.com", + image: "https://avatars.githubusercontent.com/u/123545?v=3" + } + }) + + OmniAuth.config.mock_auth[:github] = auth_hash + Rails.application.env_config["omniauth.auth"] = auth_hash + + get "/auth/github/callback" + + assert_redirected_to root_url + assert_equal "Failure", flash[:alert] + assert_nil session[:user_id] + end + end +end diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb new file mode 100644 index 0000000..02926a3 --- /dev/null +++ b/test/controllers/users_controller_test.rb @@ -0,0 +1,30 @@ +require "test_helper" + +class UsersControllerTest < ActionDispatch::IntegrationTest + test "should redierect for unauthenticated user" do + user = users(:john) + get user_path(user) + + assert_response :redirect + assert_redirected_to root_path + assert_equal "Please sign in first.", flash[:alert] + end + + test "should get show for authenticated user" do + user = users(:john) + sign_in(user) + get user_path(user) + + assert_response :success + end + + test "should raise error for non-existent user" do + user = users(:john) + sign_in(user) + get user_path("non-existent-user") + + assert_response :redirect + assert_redirected_to root_path + assert_equal "User not found", flash[:alert] + end +end diff --git a/test/fixtures/elements.yml b/test/fixtures/elements.yml index 0a40c60..77f8041 100644 --- a/test/fixtures/elements.yml +++ b/test/fixtures/elements.yml @@ -5,7 +5,10 @@ # Table name: elements # # id :integer not null, primary key +# description :text +# image_path :string # label :string not null +# position :integer # variant_type :string # created_at :datetime not null # updated_at :datetime not null @@ -15,6 +18,7 @@ # Indexes # # index_elements_on_label (label) +# index_elements_on_position (position) # index_elements_on_sub_group_id (sub_group_id) # index_elements_on_variant_type_and_variant_id (variant_type,variant_id) # diff --git a/test/fixtures/groups.yml b/test/fixtures/groups.yml index 96bbf19..8243e14 100644 --- a/test/fixtures/groups.yml +++ b/test/fixtures/groups.yml @@ -4,11 +4,12 @@ # # Table name: groups # -# id :integer not null, primary key -# title :string not null -# created_at :datetime not null -# updated_at :datetime not null -# page_id :integer not null +# id :integer not null, primary key +# behavior_type :string +# title :string not null +# created_at :datetime not null +# updated_at :datetime not null +# page_id :integer not null # # Indexes # @@ -30,4 +31,4 @@ dev_env: essentials: title: Essentials - page: basic_setup \ No newline at end of file + page: basic_setup diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 54c906c..9ffa2e6 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -1,15 +1,40 @@ # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -one: - provider: MyString - uid: MyString - name: MyString - email: MyString - image: MyString +# == Schema Information +# +# Table name: users +# +# id :integer not null, primary key +# email :string +# image :string +# name :string +# provider :string +# slug :string +# uid :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_slug (slug) UNIQUE +# -two: - provider: MyString - uid: MyString - name: MyString - email: MyString - image: MyString +john: + name: John Doe + email: john@example.com + image: https://github.com/images/john.jpg + provider: github + uid: '123456' + slug: john-doe + created_at: <%= Time.current %> + updated_at: <%= Time.current %> + +jane: + name: Jane Smith + email: jane@example.com + image: https://github.com/images/jane.jpg + provider: github + uid: '789012' + slug: jane-smith + created_at: <%= Time.current %> + updated_at: <%= Time.current %> diff --git a/test/models/element_test.rb b/test/models/element_test.rb index 9134b82..f738add 100644 --- a/test/models/element_test.rb +++ b/test/models/element_test.rb @@ -3,7 +3,10 @@ # Table name: elements # # id :integer not null, primary key +# description :text +# image_path :string # label :string not null +# position :integer # variant_type :string # created_at :datetime not null # updated_at :datetime not null @@ -13,6 +16,7 @@ # Indexes # # index_elements_on_label (label) +# index_elements_on_position (position) # index_elements_on_sub_group_id (sub_group_id) # index_elements_on_variant_type_and_variant_id (variant_type,variant_id) # diff --git a/test/models/group_test.rb b/test/models/group_test.rb index d984875..943ceb9 100644 --- a/test/models/group_test.rb +++ b/test/models/group_test.rb @@ -2,11 +2,12 @@ # # Table name: groups # -# id :integer not null, primary key -# title :string not null -# created_at :datetime not null -# updated_at :datetime not null -# page_id :integer not null +# id :integer not null, primary key +# behavior_type :string +# title :string not null +# created_at :datetime not null +# updated_at :datetime not null +# page_id :integer not null # # Indexes # diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 5c07f49..f9b4d87 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -1,7 +1,22 @@ +# == Schema Information +# +# Table name: users +# +# id :integer not null, primary key +# email :string +# image :string +# name :string +# provider :string +# slug :string +# uid :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_slug (slug) UNIQUE +# require "test_helper" class UserTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 6f9ca06..1fc756e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -43,9 +43,37 @@ class TestCase end end - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all - - # Add more helper methods to be used by all tests here... end end + +def sign_in(user) + OmniAuth.config.test_mode = true + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new( + provider: user.provider, + uid: user.uid, + info: { + email: user.email + } + ) + Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:github] + post "/auth/github" + follow_redirect! +end + +# random github user +def login_with_github + OmniAuth.config.test_mode = true + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(Faker::Omniauth.github) + Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:github] + post "/auth/github" + follow_redirect! +end + +def silence_omniauth_logger + original_logger = OmniAuth.config.logger + OmniAuth.config.logger = Logger.new("/dev/null") + yield +ensure + OmniAuth.config.logger = original_logger +end