diff --git a/Gemfile b/Gemfile index be5ae2e8cd9..a7f7e19b01d 100644 --- a/Gemfile +++ b/Gemfile @@ -45,6 +45,7 @@ gem 'net-smtp', require: false # TODO: Remove it if you use rails 7.0.1 gem 'newspaper' gem 'oauth2' gem 'omniauth', '~> 2.1.1' +gem 'omniauth-discord' gem 'omniauth-github', '~> 2.0.1' gem 'omniauth-rails_csrf_protection' gem 'parser', '3.2.2.4' diff --git a/Gemfile.lock b/Gemfile.lock index badcafdd4c7..eb34d223cf6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -329,6 +329,8 @@ GEM hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection + omniauth-discord (1.2.0) + omniauth-oauth2 (~> 1.6) omniauth-github (2.0.1) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) @@ -638,6 +640,7 @@ DEPENDENCIES newspaper oauth2 omniauth (~> 2.1.1) + omniauth-discord omniauth-github (~> 2.0.1) omniauth-rails_csrf_protection parser (= 3.2.2.4) diff --git a/app/controllers/user_sessions_controller.rb b/app/controllers/user_sessions_controller.rb index 3b408ccc193..adec92fc69b 100644 --- a/app/controllers/user_sessions_controller.rb +++ b/app/controllers/user_sessions_controller.rb @@ -35,32 +35,32 @@ def destroy redirect_to root_url, notice: 'ログアウトしました。' end - # rubocop:disable Metrics/MethodLength def callback auth = request.env['omniauth.auth'] - github_id = auth[:uid] - if current_user.blank? - user = User.find_by(github_id:) - if user.blank? - flash[:alert] = 'ログインに失敗しました。先にアカウントを作成後、GitHub連携を行ってください。' - redirect_to root_url - elsif user.retired_on? - logout - redirect_to retire_path - else - session[:user_id] = user.id - redirect_back_or_to root_url, notice: 'サインインしました。' + authentication = + case params[:provider] + when 'discord' + Authentication::Discord.new(current_user, auth) + when 'github' + Authentication::Github.new(current_user, auth) end - else - github_account = auth[:info][:nickname] - current_user.register_github_account(github_id, github_account) if current_user.github_id.blank? - flash[:notice] = 'GitHubと連携しました。' - redirect_to root_path - end - rescue StandardError => e - logger.warn "[GitHub Login] ログインに失敗しました。:#{e.message}" - flash[:alert] = 'GitHubログインに失敗しました。数回試しても続く場合、管理者に連絡してください。' - redirect_to root_path + result = authentication.authenticate + assign_flash_and_session(result) + + return redirect_back_or_to result[:path] if result[:back] + + redirect_to result[:path] + end + + def failure + redirect_to root_path, alert: 'キャンセルしました' + end + + private + + def assign_flash_and_session(result) + flash[:notice] = result[:notice] if result[:notice] + flash[:alert] = result[:alert] if result[:alert] + session[:user_id] = result[:user_id] if result[:user_id] end - # rubocop:enable Metrics/MethodLength end diff --git a/app/models/authentication/discord.rb b/app/models/authentication/discord.rb new file mode 100644 index 00000000000..6e7f9aa1bd0 --- /dev/null +++ b/app/models/authentication/discord.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class Authentication::Discord + include Rails.application.routes.url_helpers + + def initialize(login_user, auth) + @login_user = login_user + @auth = auth + end + + def authenticate + if link + { path: root_path, notice: 'Discordと連携しました' } + else + { path: root_path, alert: 'Discordの連携に失敗しました' } + end + end + + private + + def link + discord_profile = DiscordProfile.find_or_initialize_by(user: @login_user) + discord_profile.account_name = @auth[:info][:name] + discord_profile.save + end +end diff --git a/app/models/authentication/github.rb b/app/models/authentication/github.rb new file mode 100644 index 00000000000..1ac94028132 --- /dev/null +++ b/app/models/authentication/github.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class Authentication::Github + include Rails.application.routes.url_helpers + + def initialize(user, auth) + @user = user + @auth = auth + end + + def authenticate + if @user.blank? + user = User.find_by(github_id: @auth[:uid]) + if user.blank? + { path: root_path, alert: 'ログインに失敗しました。先にアカウントを作成後、GitHub連携を行ってください。' } + elsif user.retired_on? + { path: retirement_path } + else + { path: root_path, notice: 'サインインしました。', user_id: user.id, back: true } + end + else + link if @user.github_id.blank? + { path: root_path, notice: 'GitHubと連携しました。' } + end + rescue StandardError => e + Rails.logger.warn "[GitHub Login] ログインに失敗しました。:#{e.message}" + { path: root_path, alert: 'GitHubログインに失敗しました。数回試しても続く場合、管理者に連絡してください。' } + end + + private + + def link + @user.github_account = @auth[:info][:nickname] + @user.github_id = @auth[:uid] + @user.save! + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 2d0ca9efb79..ff87a5900da 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -619,12 +619,6 @@ def participating?(event) send(method_name).include?(event) end - def register_github_account(id, account_name) - self.github_account = account_name - self.github_id = id - save! - end - def depressed? reported_reports = reports.order(reported_on: :desc).limit(DEPRESSED_SIZE) reported_reports.size == DEPRESSED_SIZE && reported_reports.all?(&:sad?) diff --git a/app/views/users/form/_company.html.slim b/app/views/users/form/_company.html.slim index 5db2edeff9d..51321ea92b2 100644 --- a/app/views/users/form/_company.html.slim +++ b/app/views/users/form/_company.html.slim @@ -4,18 +4,15 @@ = f.collection_select :company_id, desc_ordered_companies_with_empty, :id, :name, {}, { id: 'js-choices-single-select' } .a-form-help - if user.admin? - p フィヨルドを選択。 + p 株式会社ロッカを選択。 - elsif user.mentor? p - | もし良かったら所属企業を登録してください。 - | 所属企業がない場合は管理者にご連絡をお願いします。 + | 差し支えがなければ所属企業(どこかの企業に所属している場合は)を登録してください。 - elsif user.adviser? p | 所属企業を登録してください。研修でお使いいただいている場合は、 | 所属企業を登録することで研修生の日報、提出物の通知が飛ぶようになります。 - elsif user.graduated_on? p - | もし良かったら所属企業を登録してください。 + | 差し支えがなければ所属企業を登録してください。 | 所属企業の登録は必須ではありません。 - | 所属企業がない場合は管理者にご連絡をお願いします。 - end diff --git a/app/views/users/form/_sns.html.slim b/app/views/users/form/_sns.html.slim index 62767add59d..361843398ab 100644 --- a/app/views/users/form/_sns.html.slim +++ b/app/views/users/form/_sns.html.slim @@ -2,18 +2,20 @@ .form-item label.a-form-label | Discord アカウント - .form-item__mention-input - = discord_profile_fields.text_field :account_name, class: 'a-text-input', placeholder: 'komagata1111' - .a-form-help - p - | Discord で使用しているアカウント名を入力してください。 - p - | Discord のアカウントの調べ方は - label.a-form-help-link.is-danger(for='modal-discord-account') - span.a-form-help-link__label - | こちら - span.a-help - i.fa-solid.fa-question + - if f.object.discord_profile.account_name? + .form-item__mention-input + = discord_profile_fields.text_field :account_name, class: 'a-text-input', disabled: true + .a-form-help + p Discord アカウントは登録されています。 + - else + = link_to '/auth/discord', class: 'a-button is-sm is-primary', method: :post do + p Discord アカウントを登録する + .a-form-help + p + | 今後、Discord とフィヨルドブートキャンプアプリとの + | 連携に Discord アカウントの登録が必要になります。 + | フィヨルドブートキャンプの Discord サーバに入室をしたら、 + | 必ず登録をしてください。 .form-item#times-url = discord_profile_fields.label :times_url, class: 'a-form-label' diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index c0a86baa777..43d9e8ace16 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -2,4 +2,7 @@ Rails.application.config.middleware.use OmniAuth::Builder do provider :github, ENV["GITHUB_KEY"], ENV["GITHUB_SECRET"], scope: 'user:email' + provider :discord, ENV['DISCORD_CLIENT_ID'], ENV['DISCORD_CLIENT_SECRET'] + + OmniAuth.config.on_failure = proc { |_env| [302, {'Location' => '/auth/failure', 'Content-Type'=> 'text/html'}, []] } end diff --git a/config/routes.rb b/config/routes.rb index 92decbf390f..948ee5396d5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -91,7 +91,8 @@ get "pages/tags/:tag", to: "pages#index", as: :pages_tag, tag: /.+/, format: "html" get "questions/tags/:tag", to: "questions#index", as: :questions_tag, tag: /.+/, format: "html" get "login" => "user_sessions#new", as: :login - get "auth/github/callback" => "user_sessions#callback" + get "auth/:provider/callback" => "user_sessions#callback" + get 'auth/failure', to: "user_sessions#failure" post "user_sessions" => "user_sessions#create" get "logout" => "user_sessions#destroy", as: :logout get "thanks", to: "static_pages#thanks" diff --git a/test/models/authentication/discord_test.rb b/test/models/authentication/discord_test.rb new file mode 100644 index 00000000000..933cb4bd37f --- /dev/null +++ b/test/models/authentication/discord_test.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'test_helper' + +class Authentication::DiscordTest < ActiveSupport::TestCase + include Rails.application.routes.url_helpers + + test 'authentication succeeds when arguments are valid' do + user = users(:komagata) + discord_authentication = Authentication::Discord.new(user, { info: { name: 'komagata_discord' } }) + result = discord_authentication.authenticate + + assert_equal result[:notice], 'Discordと連携しました' + assert_equal result[:path], root_path + end + + test 'authentication fails when arguments are invalid' do + discord_authentication = Authentication::Discord.new(nil, { info: { name: 'komagata_discord' } }) + result = discord_authentication.authenticate + + assert_equal result[:alert], 'Discordの連携に失敗しました' + assert_equal result[:path], root_path + end +end diff --git a/test/models/authentication/github_test.rb b/test/models/authentication/github_test.rb new file mode 100644 index 00000000000..15a4ce119d6 --- /dev/null +++ b/test/models/authentication/github_test.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'test_helper' + +class Authentication::GithubTest < ActiveSupport::TestCase + include Rails.application.routes.url_helpers + + test 'authentication fails when github id does not match' do + github_authentication = Authentication::Github.new(nil, { info: { nickname: 'kimura_github' }, uid: 'uid_test_data' }) + result = github_authentication.authenticate + + assert_equal result[:path], root_path + assert_equal result[:alert], 'ログインに失敗しました。先にアカウントを作成後、GitHub連携を行ってください。' + end + + test 'retirement path when github id matches a retired user' do + user = users(:yameo) + user.github_id = 'uid_test_data' + user.save! + + github_authentication = Authentication::Github.new(nil, { info: { nickname: 'yameo_github' }, uid: 'uid_test_data' }) + + assert_equal github_authentication.authenticate[:path], retirement_path + end + + test 'authentication succeeds when github id matches a regular user' do + user = users(:kimura) + user.github_id = 'uid_test_data' + user.save! + + github_authentication = Authentication::Github.new(nil, { info: { name: 'komagata_discord' }, uid: 'uid_test_data' }) + result = github_authentication.authenticate + + assert_equal result[:path], root_path + assert_equal result[:notice], 'サインインしました。' + assert_equal result[:user_id], user.id + assert_equal result[:back], true + end + + test 'github is linked when user is logged in and not linked with Github' do + user = users(:kimura) + github_authentication = Authentication::Github.new(user, { info: { nickname: 'kimura_github' }, uid: 'uid_test_data' }) + result = github_authentication.authenticate + + assert_equal result[:path], root_path + assert_equal user.reload.github_account, 'kimura_github' + assert_equal user.reload.github_id, 'uid_test_data' + end +end diff --git a/test/system/authentication/discord_test.rb b/test/system/authentication/discord_test.rb new file mode 100644 index 00000000000..8b1fde53cff --- /dev/null +++ b/test/system/authentication/discord_test.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'application_system_test_case' + +class Authentication::DiscordSystemTest < ApplicationSystemTestCase + setup do + OmniAuth.config.test_mode = true + OmniAuth.config.add_mock(:discord, { info: { name: 'discord_name' } }) + end + + test 'cannot register discord account already setting user' do + visit_with_auth '/current_user/edit', 'kimura' + + assert_text 'Discord アカウントは登録されています。' + assert_no_text 'Discord アカウントを登録する' + end + + test 'can register discord account not setting user' do + visit_with_auth '/current_user/edit', 'hatsuno' + + click_link 'Discord アカウントを登録する' + assert_text 'Discordと連携しました' + + visit '/current_user/edit' + assert_text 'Discord アカウントは登録されています。' + end +end diff --git a/test/system/sign_in_with_github_test.rb b/test/system/authentication/github_test.rb similarity index 93% rename from test/system/sign_in_with_github_test.rb rename to test/system/authentication/github_test.rb index d45950cba50..80961d05a2b 100644 --- a/test/system/sign_in_with_github_test.rb +++ b/test/system/authentication/github_test.rb @@ -2,7 +2,7 @@ require 'application_system_test_case' -class SignInWithGithubTest < ApplicationSystemTestCase +class Authentication::GithubSystemTest < ApplicationSystemTestCase fixtures :users setup do