Forem passport (cont) (#14759)

* Initial forem omniauth strategy setup work

* Finish basic raw proof of concept

* Some playing around

* use OAuth payloads + PASSPORT_OAUTH_URL for local dev

* Use FeatureFlag for Forem Passport Auth

* Working on tests

* Fix tests 🤞🏼 & some cleanup

* Use correct namespace within lib directory (match class namespace)

* Test to ensure Forem Passport auth is restricted by FeatureFlag

* Add broadcast + work on tests

* Update spec/lib/data_update_scripts/insert_forem_connect_broadcast_message_spec.rb

Co-authored-by: Michael Kohl <citizen428@forem.com>

* Hash format

* Schema cleanup + inline comments

* Use temprorary Heroku domain

* More cleanup

* Missed one

* Back to passport.forem.com

* Require correct path in lib

* Apply suggestions from code review

Co-authored-by: Michael Kohl <citizen428@forem.com>

* Use with_indifferent_access for symbol hash access in Forem strategy

Co-authored-by: benhalpern <bendhalpern@gmail.com>
Co-authored-by: Michael Kohl <citizen428@forem.com>
This commit is contained in:
Fernando Valverde 2021-09-30 06:47:45 -06:00 committed by GitHub
parent 58e3d6efd5
commit c74121b0c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 568 additions and 17 deletions

View file

@ -0,0 +1,22 @@
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 200.000000 200.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
-1000z m1507 684 c91 -55 130 -189 84 -290 -22 -48 -95 -114 -128 -114 -19 0
-25 9 -39 57 l-17 57 26 20 c14 12 31 34 37 50 15 40 -14 98 -55 110 -52 15
-58 21 -72 70 -11 38 -12 51 -1 63 20 24 107 12 165 -23z m-247 -25 c6 -11 12
-39 13 -62 2 -41 2 -42 -45 -57 -68 -22 -70 -26 -52 -100 22 -88 28 -94 88
-76 26 8 54 12 61 10 15 -6 40 -97 32 -118 -3 -8 -31 -21 -61 -30 -31 -9 -56
-20 -56 -24 0 -4 18 -72 40 -152 22 -80 40 -153 40 -162 0 -21 -106 -56 -121
-41 -6 6 -25 68 -44 139 -43 167 -155 586 -160 600 -7 24 16 37 111 65 120 34
140 35 154 8z m-485 -185 c67 -33 136 -105 155 -163 16 -48 13 -53 -52 -74
l-48 -16 -17 26 c-10 15 -35 43 -55 64 -67 68 -175 71 -252 7 -48 -39 -67 -77
-71 -144 -1 -30 -5 -57 -7 -59 -10 -10 -97 -28 -110 -22 -38 14 -29 147 17
237 25 49 94 119 144 144 84 44 206 44 296 0z m791 -730 c12 -48 -2 -147 -30
-201 -37 -75 -76 -115 -148 -154 -58 -31 -70 -34 -148 -34 -73 0 -93 4 -143
29 -67 32 -128 92 -157 152 -25 51 -19 68 29 83 64 19 78 15 103 -30 33 -58
94 -92 168 -92 106 0 178 71 189 186 l6 61 40 12 c65 20 84 17 91 -12z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -17,6 +17,10 @@
--brand-facebook-color: #fff;
--brand-facebook-bg-hover: #476fbf;
--brand-forem-bg: #0a3e4a;
--brand-forem-color: #fff;
--brand-forem-bg-hover: #05313b;
--brand-email-bg: #24292e;
--brand-email-color: #fff;
--brand-email-bg-hover: #000;
@ -291,6 +295,17 @@
--color-inverted-hover: var(--brand-facebook-color);
}
.crayons-btn--brand-forem {
--bg: var(--brand-forem-bg);
--bg-hover: var(--brand-forem-bg-hover);
--color: var(--brand-forem-color);
--color-hover: var(--brand-forem-color);
--bg-inverted: var(--brand-forem-bg);
--bg-inverted-hover: var(--brand-forem-bg-hover);
--color-inverted: var(--brand-forem-color);
--color-inverted-hover: var(--brand-forem-color);
}
.crayons-btn--brand-email {
--bg: var(--brand-email-bg);
--bg-hover: var(--brand-email-bg-hover);

View file

@ -39,6 +39,14 @@ module Constants
"The \"App Secret\" portion of the Basic Settings section of the App page on the Facebook Developer Portal",
placeholder: ""
},
forem_key: {
description: "Forem key",
placeholder: ""
},
forem_secret: {
description: "Forem secret",
placeholder: ""
},
github_key: {
description: "The \"Client ID\" portion of the GitHub Oauth Apps portal",
placeholder: ""

View file

@ -14,6 +14,8 @@ module Settings
setting :display_email_domain_allow_list_publicly, type: :boolean, default: false
setting :facebook_key, type: :string
setting :facebook_secret, type: :string
setting :forem_key, type: :string
setting :forem_secret, type: :string
setting :github_key, type: :string, default: ApplicationConfig["GITHUB_KEY"]
setting :github_secret, type: :string, default: ApplicationConfig["GITHUB_SECRET"]
setting :invite_only_mode, type: :boolean, default: false

View file

@ -0,0 +1,51 @@
module Authentication
module Providers
class Forem < Provider
OFFICIAL_NAME = "Forem".freeze
DOMAIN_URL = ApplicationConfig["PASSPORT_OAUTH_URL"] || "https://passport.forem.com".freeze
SETTINGS_URL = "#{DOMAIN_URL}/oauth/authorized_applications".freeze
def new_user_data
{
email: info.email,
name: info.name,
remote_profile_image_url: raw_info.remote_profile_image_url,
forem_username: info.user_nickname
}
end
def existing_user_data
{
email: info.email,
name: info.name,
forem_username: info.user_nickname
}
end
delegate :user_nickname, to: :info
def self.official_name
OFFICIAL_NAME
end
def self.settings_url
SETTINGS_URL
end
def self.sign_in_path(**kwargs)
::Authentication::Paths.sign_in_path(
provider_name,
**kwargs,
)
end
protected
# Remove sensible data from the payload: None in this case so return as-is
# For more details see Authentication::Providers::Provider#cleanup_payload
def cleanup_payload(auth_payload)
auth_payload
end
end
end
end

View file

@ -161,6 +161,7 @@
readonly: true %>
</div>
<% authentication_available_providers.each do |provider| %>
<% next if provider.provider_name == :forem && !FeatureFlag.enabled?(:forem_passport) %>
<section class="config-authentication__item mb-0">
<figure class="config-authentication__item--icon">
<%= inline_svg_tag("#{provider.provider_name}.svg", class: "crayons-icon", aria: true, title: "#{provider.official_name} logo") %>

View file

@ -1,6 +1,7 @@
<div class="registration__actions-providers">
<% authentication_enabled_providers.each do |provider| %>
<% next unless display_social_login? %>
<% next if provider.provider_name == :forem && !FeatureFlag.enabled?(:forem_passport) %>
<%= form_with url: provider.sign_in_path(state: "navbar_basic"), class: "flex w-100", local: true do |f| %>
<%= f.button type: :submit, class: "crayons-btn crayons-btn--l crayons-btn--brand-#{provider.provider_name} crayons-btn--icon-left grow-1 whitespace-nowrap" do %>
<%= inline_svg_tag("#{provider.provider_name}.svg", aria_hidden: true, class: "crayons-icon", title: provider.provider_name) %>

View file

@ -1,3 +1,12 @@
Dir.glob(Rails.root.join("lib/omni_auth/strategies/*.rb")).each do |filename|
require_dependency filename
end
FOREM_OMNIAUTH_SETUP = lambda do |env|
env["omniauth.strategy"].options[:client_id] = Settings::Authentication.forem_key
env["omniauth.strategy"].options[:client_secret] = Settings::Authentication.forem_secret
end
TWITTER_OMNIAUTH_SETUP = lambda do |env|
env["omniauth.strategy"].options[:consumer_key] = Settings::Authentication.twitter_key
env["omniauth.strategy"].options[:consumer_secret] = Settings::Authentication.twitter_secret
@ -312,8 +321,9 @@ Devise.setup do |config|
# Fun fact, unless Twitter is last, it doesn't work for some reason.
config.omniauth :facebook, setup: FACEBOOK_OMNIAUTH_SETUP
config.omniauth :github, setup: GITHUB_OMNIUATH_SETUP
config.omniauth :twitter, setup: TWITTER_OMNIAUTH_SETUP
config.omniauth :apple, setup: APPLE_OMNIAUTH_SETUP
config.omniauth :forem, setup: FOREM_OMNIAUTH_SETUP, strategy_class: OmniAuth::Strategies::Forem
config.omniauth :twitter, setup: TWITTER_OMNIAUTH_SETUP
# ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or

View file

@ -0,0 +1,5 @@
class AddForemUsernameToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :forem_username, :string
end
end

View file

@ -1296,6 +1296,7 @@ ActiveRecord::Schema.define(version: 2021_08_30_062627) do
t.integer "following_orgs_count", default: 0, null: false
t.integer "following_tags_count", default: 0, null: false
t.integer "following_users_count", default: 0, null: false
t.string "forem_username"
t.datetime "github_repos_updated_at", default: "2017-01-01 05:00:00"
t.string "github_username"
t.datetime "invitation_accepted_at"

View file

@ -0,0 +1,17 @@
module DataUpdateScripts
class InsertForemConnectBroadcastMessage
def run
return if Broadcast.find_by(title: "Welcome Notification: forem_connect")
message = "You're on a roll! 🎉 Do you have a Forem account? " \
"Consider <a href='/settings'>connecting it</a>."
Broadcast.create!(
title: "Welcome Notification: forem_connect",
processed_html: message,
type_of: "Welcome",
active: true,
)
end
end
end

View file

@ -0,0 +1,36 @@
require "omniauth-oauth2"
module OmniAuth
module Strategies
class Forem < OmniAuth::Strategies::OAuth2
option :name, :forem
option :client_options, {
site: ApplicationConfig["PASSPORT_OAUTH_URL"] || "https://passport.forem.com".freeze,
authorize_url: "/oauth/authorize"
}
uid { raw_info[:id] }
info do
{
email: raw_info[:email],
name: raw_info[:name],
user_nickname: raw_info[:username]
}
end
extra do
skip_info? ? {} : { raw_info: raw_info }
end
def raw_info
@raw_info ||= access_token.get("/api/v0/me").parsed.with_indifferent_access
end
def callback_url
"#{full_host}#{script_name}#{callback_path}"
end
end
end
end

View file

@ -48,6 +48,15 @@ FactoryBot.define do
end
end
factory :forem_connect_broadcast do
title { "Welcome Notification: forem_connect" }
type_of { "Welcome" }
processed_html do
"You're on a roll! 🎉 Do you have a Forem account? Consider " \
"<a href='/settings'>connecting it</a>."
end
end
factory :twitter_connect_broadcast do
title { "Welcome Notification: twitter_connect" }
type_of { "Welcome" }

View file

@ -0,0 +1,19 @@
require "rails_helper"
require Rails.root.join(
"lib/data_update_scripts/20210921235050_insert_forem_connect_broadcast_message.rb",
)
describe DataUpdateScripts::InsertForemConnectBroadcastMessage do
it "works without a Broadcast" do
expect do
described_class.new.run
end.to change(Broadcast, :count).by(1)
end
it "works when a Broadcast already exists" do
create(:forem_connect_broadcast)
expect do
described_class.new.run
end.not_to change(Broadcast, :count)
end
end

View file

@ -124,6 +124,38 @@ RSpec.describe Identity, type: :model do
end
end
context "with Forem payload" do
let(:auth_payload) { OmniAuth.config.mock_auth[:forem] }
let(:provider) { Authentication::Providers::Forem.new(auth_payload) }
it "initializes a new identity from the auth payload" do
identity = described_class.build_from_omniauth(provider)
expect(identity.new_record?).to be(true)
expect(identity.provider).to eq("forem")
expect(identity.uid).to eq(auth_payload.uid)
expect(identity.token).to eq(auth_payload.credentials.token)
expect(identity.secret).to eq(auth_payload.credentials.secret)
expect(identity.auth_data_dump).to eq(provider.payload)
end
it "finds an existing identity" do
payload = provider.payload
existing_identity = described_class.create!(
user: user,
provider: payload.provider,
uid: payload.uid,
token: payload.credentials.token,
secret: payload.credentials.secret,
auth_data_dump: payload,
)
identity = described_class.build_from_omniauth(provider)
expect(identity).to eq(existing_identity)
end
end
context "with Twitter payload" do
let(:auth_payload) { OmniAuth.config.mock_auth[:twitter] }
let(:provider) { Authentication::Providers::Twitter.new(auth_payload) }

View file

@ -11,8 +11,11 @@ RSpec.describe User, type: :model do
end
def mock_username(provider_name, username)
if provider_name == :apple
case provider_name
when :apple
OmniAuth.config.mock_auth[provider_name].info.first_name = username
when :forem
OmniAuth.config.mock_auth[provider_name].info.user_nickname = username
else
OmniAuth.config.mock_auth[provider_name].info.nickname = username
end
@ -422,7 +425,10 @@ RSpec.describe User, type: :model do
describe "user registration", vcr: { cassette_name: "fastly_sloan" } do
let(:user) { create(:user) }
before { omniauth_mock_providers_payload }
before do
allow(FeatureFlag).to receive(:enabled?).with(:forem_passport).and_return(true)
omniauth_mock_providers_payload
end
Authentication::Providers.available.each do |provider_name|
it "finds user by email and assigns identity to that if exists for #{provider_name}" do

View file

@ -15,7 +15,8 @@ RSpec.describe Authentication::Providers::Facebook, type: :service do
describe ".sign_in_path" do
let(:expected_path) do
"/users/auth/facebook?callback_url=http%3A%2F%2Flocalhost%3A3000%2Fusers%2Fauth%2Ffacebook%2Fcallback"
expected_callback_url = CGI.escape(URL.url("/users/auth/facebook/callback"))
"/users/auth/facebook?callback_url=#{expected_callback_url}"
end
it "returns the correct sign in path" do

View file

@ -0,0 +1,38 @@
require "rails_helper"
RSpec.describe Authentication::Providers::Forem, type: :service do
before { allow(FeatureFlag).to receive(:enabled?).with(:forem_passport).and_return(true) }
describe ".authentication_path" do
it "returns the correct authentication path" do
expected_path = Rails.application.routes.url_helpers.user_forem_omniauth_authorize_path
expect(described_class.authentication_path).to eq(expected_path)
end
it "supports additional parameters" do
path = described_class.authentication_path(state: "state")
expect(path).to include("state=state")
end
end
describe ".sign_in_path" do
let(:expected_path) do
expected_callback_url = CGI.escape(URL.url("/users/auth/forem/callback"))
"/users/auth/forem?callback_url=#{expected_callback_url}"
end
it "returns the correct sign in path" do
expect(described_class.sign_in_path).to eq(expected_path)
end
it "supports additional parameters" do
path = described_class.sign_in_path(state: "state")
expect(path).to include("state=state")
end
it "does not override the callback_url parameter" do
path = described_class.sign_in_path(callback_url: "https://example.com/callback")
expect(path).to eq(expected_path)
end
end
end

View file

@ -15,7 +15,8 @@ RSpec.describe Authentication::Providers::Github, type: :service do
describe ".sign_in_path" do
let(:expected_path) do
"/users/auth/github?callback_url=http%3A%2F%2Flocalhost%3A3000%2Fusers%2Fauth%2Fgithub%2Fcallback"
expected_callback_url = CGI.escape(URL.url("/users/auth/github/callback"))
"/users/auth/github?callback_url=#{expected_callback_url}"
end
it "returns the correct sign in path" do

View file

@ -15,8 +15,8 @@ RSpec.describe Authentication::Providers::Twitter, type: :service do
describe ".sign_in_path" do
let(:expected_path) do
"/users/auth/twitter?callback_url=" \
"http%3A%2F%2Flocalhost%3A3000%2Fusers%2Fauth%2Ftwitter%2Fcallback&secure_image_url=true"
expected_callback_url = CGI.escape(URL.url("/users/auth/twitter/callback"))
"/users/auth/twitter?callback_url=#{expected_callback_url}&secure_image_url=true"
end
it "returns the correct sign in path" do

View file

@ -28,7 +28,7 @@ RSpec.describe Authentication::Providers, type: :service do
describe ".available" do
it "lists the available providers" do
available_providers = %i[apple facebook github twitter]
available_providers = %i[apple facebook forem github twitter]
expect(described_class.available).to eq(available_providers)
end
end

View file

@ -10,6 +10,7 @@ RSpec.describe Broadcasts::WelcomeNotification::Generator, type: :service do
let!(:github_connect_broadcast) { create(:github_connect_broadcast) }
let!(:facebook_connect_broadcast) { create(:facebook_connect_broadcast) }
let!(:apple_connect_broadcast) { create(:apple_connect_broadcast) }
let!(:forem_connect_broadcast) { create(:forem_connect_broadcast) }
let!(:customize_feed_broadcast) { create(:customize_feed_broadcast) }
let!(:discuss_and_ask_broadcast) { create(:discuss_and_ask_broadcast) }
let!(:customize_ux_broadcast) { create(:customize_ux_broadcast) }
@ -66,6 +67,7 @@ RSpec.describe Broadcasts::WelcomeNotification::Generator, type: :service do
facebook_connect_broadcast,
twitter_connect_broadcast,
apple_connect_broadcast,
forem_connect_broadcast,
].include?(user.notifications.last.notifiable)
expect(not_github).to be(true)

View file

@ -68,6 +68,33 @@ module OmniauthHelpers
},
).freeze
OMNIAUTH_PAYLOAD_FOREM = OmniAuth::AuthHash::InfoHash.new(
{
provider: "forem",
uid: SecureRandom.hex,
info: {
email: "sloan@dev.to",
name: "Sloan",
image: "https://dummyimage.com/400x400.jpg",
user_nickname: "sloan"
},
credentials: {
token: SecureRandom.hex,
refresh_token: SecureRandom.hex,
expires_at: 1_589_475_606,
expires: true
},
extra: {
raw_info: {
email: "slaon@dev.to",
id: "31047",
name: "Sloan",
remote_profile_image_url: "https://dummyimage.com/400x400.jpg"
}
}
},
).freeze
OMNIAUTH_PAYLOAD_APPLE = OmniAuth::AuthHash::InfoHash.new(
{
provider: "apple",
@ -163,6 +190,10 @@ module OmniauthHelpers
OmniAuth.config.mock_auth[:facebook] = OMNIAUTH_PAYLOAD_FACEBOOK.dup
end
def omniauth_mock_forem_payload
OmniAuth.config.mock_auth[:forem] = OMNIAUTH_PAYLOAD_FOREM.dup
end
def omniauth_mock_apple_payload
OmniAuth.config.mock_auth[:apple] = OMNIAUTH_PAYLOAD_APPLE.dup
end

View file

@ -7,6 +7,7 @@ RSpec.describe "Creator config edit", type: :system, js: true do
before do
sign_in admin
allow(ForemInstance).to receive(:private?).and_return(false)
allow(FeatureFlag).to receive(:enabled?).with(:forem_passport).and_return(true)
end
it "presents all available OAuth providers" do

View file

@ -18,6 +18,7 @@ RSpec.describe "Omniauth redirect_uri", type: :system do
it "relies on Settings::General.app_domain to generate correct callbacks_url" do
allow(Settings::Authentication).to receive(:providers).and_return(Authentication::Providers.available)
allow(FeatureFlag).to receive(:enabled?).with(:forem_passport).and_return(true)
visit sign_up_path
Authentication::Providers.available.each do |provider_name|
provider_auth_button = find("button.crayons-btn--brand-#{provider_name}")

View file

@ -220,15 +220,6 @@ RSpec.describe "Authenticating with Facebook" do
expect(page).to have_current_path("/?signin=true")
end
it "renders the facebook icon on the profile" do
sign_in user
visit user_facebook_omniauth_authorize_path
visit user_profile_path(user.username)
expect(page).to have_css("svg.crayons-icon.shrink-0", text: "facebook website")
end
end
end

View file

@ -0,0 +1,250 @@
require "rails_helper"
RSpec.describe "Authenticating with Forem" do
let(:sign_in_link) { "Continue with Forem" }
before do
allow(FeatureFlag).to receive(:enabled?).with(:forem_passport).and_return(true)
omniauth_mock_forem_payload
allow(Settings::Authentication).to receive(:providers).and_return(Authentication::Providers.available)
end
describe "FeatureFlag hides the Forem Passport auth" do
it "shows Forem auth when enabled" do
visit sign_up_path
expect(page).to have_text(sign_in_link)
end
it "doesn't show the Forem auth when disabled" do
allow(FeatureFlag).to receive(:enabled?).with(:forem_passport).and_return(false)
visit sign_up_path
expect(page).not_to have_text(sign_in_link)
end
end
context "when a user is new" do
context "when using valid credentials" do
it "creates a new user" do
expect do
visit sign_up_path
click_on(sign_in_link, match: :first)
end.to change(User, :count).by(1)
end
it "logs in and redirects to the onboarding" do
visit sign_up_path
click_on(sign_in_link, match: :first)
expect(page).to have_current_path("/onboarding", ignore_query: true)
expect(page.html).to include("onboarding-container")
end
it "remembers the user" do
visit sign_up_path
click_on(sign_in_link, match: :first)
user = User.last
expect(user.remember_token).to be_present
expect(user.remember_created_at).to be_present
end
end
context "when using valid credentials but witholding email address" do
before do
OmniAuth.config.mock_auth[:forem][:info].delete(:email)
OmniAuth.config.mock_auth[:forem][:extra][:raw_info].delete(:email)
end
it "creates a new user" do
expect do
visit sign_up_path
click_on(sign_in_link, match: :first)
end.to change(User, :count).by(1)
end
it "logs in and redirects to the onboarding" do
visit sign_up_path
click_on(sign_in_link, match: :first)
expect(page).to have_current_path("/onboarding", ignore_query: true)
expect(page.html).to include("onboarding-container")
end
it "remembers the user" do
visit sign_up_path
click_on(sign_in_link, match: :first)
user = User.last
expect(user.remember_token).to be_present
expect(user.remember_created_at).to be_present
end
end
context "when trying to register with an already existing username" do
it "creates a new user with a temporary username" do
# see Authentication::Providers::Forem#new_user_data
username = OmniAuth.config.mock_auth[:forem].info.name.downcase.sub(" ", "_")
user = create(:user, username: username)
expect do
visit sign_up_path
click_on(sign_in_link, match: :first)
end.to change(User, :count).by(1)
expect(page).to have_current_path("/onboarding", ignore_query: true)
expect(User.last.username).to include(user.username)
end
end
context "when using invalid credentials" do
let(:params) do
'{"callback_url"=>"http://localhost:3000/users/auth/forem/callback", "state"=>"navbar_basic"}'
end
before do
omniauth_setup_invalid_credentials(:forem)
allow(ForemStatsClient).to receive(:increment)
end
after do
OmniAuth.config.on_failure = OmniauthHelpers.const_get("OMNIAUTH_DEFAULT_FAILURE_HANDLER")
end
it "does not create a new user" do
expect do
visit sign_up_path
click_on(sign_in_link, match: :first)
end.not_to change(User, :count)
end
it "does not log in" do
visit sign_up_path
click_on(sign_in_link, match: :first)
expect(page).to have_current_path("/users/sign_in")
expect(page).to have_button(sign_in_link)
end
it "notifies Datadog about a callback error" do
error = OmniAuth::Strategies::OAuth2::CallbackError.new(
"Callback error", "Error reason", "https://example.com/error"
)
omniauth_setup_authentication_error(error, params)
visit sign_up_path
click_on(sign_in_link, match: :first)
args = omniauth_failure_args(error, "forem", params)
expect(ForemStatsClient).to have_received(:increment).with(
"omniauth.failure", *args
)
end
it "notifies Datadog about an OAuth unauthorized error" do
request = double
allow(request).to receive(:code).and_return(401)
allow(request).to receive(:message).and_return("unauthorized")
error = OAuth::Unauthorized.new(request)
omniauth_setup_authentication_error(error, params)
visit sign_up_path
click_on(sign_in_link, match: :first)
args = omniauth_failure_args(error, "forem", params)
expect(ForemStatsClient).to have_received(:increment).with(
"omniauth.failure", *args
)
end
it "notifies Datadog even with no OmniAuth error present" do
error = nil
omniauth_setup_authentication_error(error, params)
visit sign_up_path
click_on(sign_in_link, match: :first)
args = omniauth_failure_args(error, "forem", params)
expect(ForemStatsClient).to have_received(:increment).with(
"omniauth.failure", *args
)
end
end
context "when a validation failure occurrs" do
before do
# A User is invalid if their name is more than 100 chars long
OmniAuth.config.mock_auth[:forem].info.name = "X" * 101
end
it "does not create a new user" do
expect do
visit sign_up_path
click_on(sign_in_link, match: :first)
end.not_to change(User, :count)
end
it "redirects to the registration page" do
visit sign_up_path
click_on(sign_in_link, match: :first)
expect(page).to have_current_path("/users/sign_up")
end
it "logs errors" do
allow(Honeybadger).to receive(:notify)
visit sign_up_path
click_on(sign_in_link, match: :first)
expect(Honeybadger).to have_received(:notify).once
end
end
end
context "when a user already exists" do
let!(:auth_payload) { OmniAuth.config.mock_auth[:forem] }
let(:user) { create(:user, :with_identity, identities: [:forem]) }
before do
auth_payload.info.name = user.name
auth_payload.info.email = user.email
end
after do
sign_out user
end
context "when using valid credentials" do
it "logs in" do
visit sign_up_path
click_on(sign_in_link, match: :first)
expect(page).to have_current_path("/?signin=true")
end
end
context "when already signed in" do
it "redirects to the dashboard" do
sign_in user
visit user_forem_omniauth_authorize_path
expect(page).to have_current_path("/?signin=true")
end
end
end
context "when community is in invite-only mode" do
before do
allow(ForemInstance).to receive(:invitation_only?).and_return(true)
end
it "doesn't present the authentication option" do
visit sign_up_path(state: "new-user")
expect(page).not_to have_text(sign_in_link)
expect(page).to have_text("invite only")
end
end
end