Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHubログイン機能を実装した #67

Merged
merged 11 commits into from
Apr 14, 2024
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
6 changes: 2 additions & 4 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ inherit_gem:
- "config/rails.yml"

RSpec/ExampleLength:
Exclude:
- spec/system/*
Enabled: false

RSpec/MultipleExpectations:
Exclude:
- spec/system/*
Enabled: false
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ gem 'bootsnap', require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

gem 'omniauth'
gem 'omniauth-github'
gem 'omniauth-rails_csrf_protection'
gem 'rails-i18n', '~> 7.0.0'
gem 'slim-rails'

Expand Down
41 changes: 41 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,13 @@ GEM
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
faraday (2.9.0)
faraday-net_http (>= 2.0, < 3.2)
faraday-net_http (3.1.0)
net-http
globalid (1.2.1)
activesupport (>= 6.1)
hashie (5.0.0)
hpricot (0.8.6)
html2slim-ruby3 (0.2.1)
hpricot
Expand All @@ -128,6 +133,8 @@ GEM
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.7.1)
jwt (2.8.1)
base64
language_server-protocol (3.17.0.3)
loofah (2.22.0)
crass (~> 1.0.2)
Expand All @@ -142,7 +149,10 @@ GEM
mini_mime (1.1.5)
minitest (5.22.2)
msgpack (1.7.2)
multi_xml (0.6.0)
mutex_m (0.2.0)
net-http (0.4.1)
uri
net-imap (0.4.10)
date
net-protocol
Expand All @@ -165,6 +175,26 @@ GEM
racc (~> 1.4)
nokogiri (1.16.2-x86_64-linux)
racc (~> 1.4)
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-github (2.0.1)
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.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
Expand All @@ -177,6 +207,9 @@ GEM
nio4r (~> 2.0)
racc (1.7.3)
rack (3.0.9.1)
rack-protection (4.0.0)
base64 (>= 0.1.0)
rack (>= 3.0.0, < 4)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.1.0)
Expand Down Expand Up @@ -295,6 +328,9 @@ GEM
slim_lint (0.26.0)
rubocop (>= 1.0, < 2.0)
slim (>= 3.0, < 6.0)
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
sprockets (4.2.1)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
Expand Down Expand Up @@ -328,6 +364,8 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
uri (0.13.0)
version_gem (1.1.4)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -358,6 +396,9 @@ DEPENDENCIES
html2slim-ruby3
importmap-rails
jbuilder
omniauth
omniauth-github
omniauth-rails_csrf_protection
pg (~> 1.1)
puma (>= 5.0)
rails (~> 7.1.2)
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# frozen_string_literal: true

class ApplicationController < ActionController::Base
helper_method :logged_in?

private

def logged_in?
!!session[:user_id]
end
end
18 changes: 18 additions & 0 deletions app/controllers/user_sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

class UserSessionsController < ApplicationController
def create
user = User.find_or_create_from_auth_hash!(request.env['omniauth.auth'])
session[:user_id] = user.id
redirect_to new_group_path, notice: 'ログインしました'
end

def destroy
reset_session
redirect_to root_path, notice: 'ログアウトしました'
end

def failure
redirect_to root_path, notice: 'ログインをキャンセルしました'
end
end
20 changes: 20 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

class User < ApplicationRecord
validates :provider, presence: true
validates :uid, presence: true, uniqueness: { scope: :provider }
validates :name, presence: true, uniqueness: true
validates :image_url, presence: true, uniqueness: true
Comment on lines +4 to +7
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

認証方法はGitHub認証だけの予定。そのためnameimage_urlにもユニーク制約を追加
(GitHub認証だけならproviderもいらないかも)


def self.find_or_create_from_auth_hash!(auth_hash)
provider = auth_hash[:provider]
uid = auth_hash[:uid]
nickname = auth_hash[:info][:nickname]
image_url = auth_hash[:info][:image]

User.find_or_create_by!(provider:, uid:) do |user|
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user.name = nickname
user.image_url = image_url
end
end
end
8 changes: 8 additions & 0 deletions app/views/groups/index.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ p.mb-4
.mb-4
= link_to '2次会グループを作成', new_group_path

.mb-4
- if logged_in?
= button_to 'ログアウト', logout_path, method: :delete
- else
= button_to 'サインアップ / ログインをして2次会グループを作成', '/auth/github', data: { turbo: false }
Copy link
Owner Author

span.text-xs
| GitHubアカウントが必要です

p.text-xl.font-bold.mb-4
| 2次会グループ一覧

Expand Down
2 changes: 2 additions & 0 deletions app/views/groups/new.html.slim
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
p style="color: green" = notice

h1.text-2xl.font-bold.mb-4
| 2次会グループを作成

Expand Down
2 changes: 1 addition & 1 deletion config/credentials.yml.enc
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
JiytSi4MiwjEUWC995pbQqk0uR3EezofyHGzZxyM3RhxiS2NkVSKx/B0ULh6XVYJM5mwVgZ7Gcxh09PxmX78QQOx1D9/aVJ5fMF6O/3XLhVZ7FPkLQ5g9BPIZqJipqhb3QIaDGv3YCap+HtEuFtu0xv8Eqelz6p/Reabwxr+ZfIvJQZMWRsqnDDgbwkSBgvzgga1rR7C7F47chloAZSNYaF0oyGuH5SoULZB2+cKzjc2GD4M23NVhA0y2CfN0pZRcXLzvka1vyHTdgtkYW2DTWOs60gEuVslovBOQAb2/RuSM6dkG4tIG6zXOfu9Nk1rLQzSvcZFj9J8h5a85PJF1jC/fmXf8w6Bsk9JZkqfadQNbFuOd/vap1oPfuXSe68Neq45rG/Wp9XP+xUXdSUWCgIAv+UT--osFtXK0CWL5Y0STB--DusPBnj2G7mby6v8SvTDgw==
WrNv9PmTDANqa1e1FWXw17TafFTDdNV17QaORIVYhjUKQ2vOEPpD2KOiFVEat2tewzJhP9F5akrJS8IOuEOuteFRcP7XPfiwX8tG8mo49Z7WBtLYlRKLNxwFffhzi23NCH6Rk1BiYrr7deVfhmaSvMacp9OYGxsKymlSOxURwRaAfn0uSIL8PmqtEigVTSan1RRqwcdEyTJEcegCiLZItZdjJzDATOTIF5Zapgt1BnttB+b7ylI6xdu8lX0dQKyxFxo77+vLIIjcwWZp/jNKOou3vv7iTGm8gqQ6Mg82RFmPF+q5HnLxWJVDLv7PRUXrJCg1RfEk1Zid6aQz87LnK+K16Vj3xrgTkJf3prYSZX5FLOGSjkkFRdtFC0cMQRtfjawjLTtp9pEdQhCJGgqR+L/Vn59ENaT+DTuHkG5ZjvuwN9f/m/abMpYy7RBBr02q5RyIya6giisznvixdZ5EzYIklan02hRBaAzoQ6d82qTV5hIxUFIJSZDUrURrbMdq6UMm3j+12GX2ETi/jHAxrsIcrJwUwLQUkYiY3KA=--xU62kZ2t3TKIjDjl--szzWlOmAxIdcsh91yZYfYQ==
9 changes: 9 additions & 0 deletions config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Rails.application.config.middleware.use OmniAuth::Builder do
provider :github,
Rails.application.credentials.github[:client_id],
Rails.application.credentials.github[:client_secret]

OmniAuth.config.on_failure = Proc.new { |env|
OmniAuth::FailureEndpoint.new(env).redirect_to_failure
}
end
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@

# Defines the root path route ("/")
root "groups#index"
get "auth/:provider/callback" => "user_sessions#create"
get "auth/failure" => "user_sessions#failure"
delete "/logout" => "user_sessions#destroy"
end
14 changes: 14 additions & 0 deletions db/migrate/20240328001932_create_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :provider, null: false
t.string :uid, null: false
t.string :name, null: false
t.string :image_url, null: false

t.timestamps
end

add_index :users, %i[provider uid], unique: true
end
end
6 changes: 6 additions & 0 deletions db/migrate/20240403055814_add_index_to_users.rb
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddIndexToUsers < ActiveRecord::Migration[7.1]
def change
add_index :users, :name, unique: true
add_index :users, :image_url, unique: true
end
end
14 changes: 13 additions & 1 deletion db/schema.rb

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

10 changes: 10 additions & 0 deletions spec/factories/users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

FactoryBot.define do
factory :user do
provider { 'github' }
uid { '0001' }
name { 'tester1' }
image_url { 'example.com/1' }
end
end
115 changes: 115 additions & 0 deletions spec/models/user_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe User, type: :model do
let(:user) { FactoryBot.create(:user) }

it 'is valid with all attributes' do
expect(user).to be_valid
end

describe 'validations' do
it 'is invalid without a provider' do
user.provider = nil
user.valid?
expect(user.errors[:provider]).to include('を入力してください')
end

it 'is invalid without a uid' do
user.uid = nil
user.valid?
expect(user.errors[:uid]).to include('を入力してください')
end

it 'is invalid without a name' do
user.name = nil
user.valid?
expect(user.errors[:name]).to include('を入力してください')
end

it 'is invalid without an image_url' do
user.image_url = nil
user.valid?
expect(user.errors[:image_url]).to include('を入力してください')
end

it 'is invalid with a duplicate name' do
FactoryBot.create(:user)
user2 = FactoryBot.build(:user, image_url: 'example.com/2', provider: 'twitter', uid: '0002')
user2.valid?
expect(user2.errors[:name]).to include('はすでに存在します')
end

it 'is invalid with a duplicate image_url' do
FactoryBot.create(:user)
user2 = FactoryBot.build(:user, name: 'tester2', provider: 'twitter', uid: '0002')
user2.valid?
expect(user2.errors[:image_url]).to include('はすでに存在します')
end

it 'is invalid with a duplicate uid and provider pair' do
FactoryBot.create(:user)
user2 = FactoryBot.build(:user, name: 'tester2', image_url: 'example.com/2')
user2.valid?
expect(user2.errors[:uid]).to include('はすでに存在します')
end

it 'allows duplicate uid with different provider' do
FactoryBot.create(:user)
user2 = FactoryBot.build(:user, name: 'tester2', image_url: 'example.com/2', provider: 'twitter')
expect(user2).to be_valid
end
end

describe '.find_or_create_from_auth_hash!' do
let(:auth_hash) do
{
provider: 'github',
uid: '0001',
info: {
nickname: 'testuser',
image: 'https://example.com/testuser.png'
}
}
end

context 'when user does not exist' do
it 'creates a new user' do
expect do
described_class.find_or_create_from_auth_hash!(auth_hash)
end.to change(described_class, :count).by(1)
end

it 'returns the created user' do
user = described_class.find_or_create_from_auth_hash!(auth_hash)
expect(user).to have_attributes(
provider: 'github',
uid: '0001',
name: 'testuser',
image_url: 'https://example.com/testuser.png'
)
end
end

context 'when user already exists' do
before { FactoryBot.create(:user) }

it 'does not create a new user' do
expect do
described_class.find_or_create_from_auth_hash!(auth_hash)
end.not_to change(described_class, :count)
end

it 'returns the existing user' do
user = described_class.find_or_create_from_auth_hash!(auth_hash)
expect(user).to have_attributes(
provider: 'github',
uid: '0001',
name: 'tester1',
image_url: 'example.com/1'
)
end
end
end
end
Loading