mirror of
https://github.com/discourse/discourse.git
synced 2025-10-03 17:21:20 +08:00
DEV: Add Discourse ID authenticator (#33186)
Adds a Discourse ID authenticator. Not available for use in production just yet, but soon communities will be able to use this service to let users authenticate using a central Discourse ID account. Includes a support for a `/revoke` action, allowing users to log out of multiple client instances from a central auth service. Internal ticket: t/155397 --------- Co-authored-by: Loïc Guitaut <loic@discourse.org>
This commit is contained in:
parent
2362bf740d
commit
d45ebd746c
12 changed files with 572 additions and 0 deletions
21
app/controllers/users/discourse_id_controller.rb
Normal file
21
app/controllers/users/discourse_id_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Users::DiscourseIdController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token, only: [:revoke]
|
||||
|
||||
def revoke
|
||||
RateLimiter.new(nil, "discourse_id_revoke_#{params[:identifier]}", 5, 1.minute).performed!
|
||||
|
||||
DiscourseId::Revoke.call(service_params) do |result|
|
||||
on_success { render json: { success: true } }
|
||||
on_failed_contract do |contract|
|
||||
logger.warn(result.inspect_steps) if SiteSetting.discourse_id_verbose_logging
|
||||
render json: { error: contract.errors.full_messages.join(", ") }, status: 400
|
||||
end
|
||||
on_failure do
|
||||
logger.warn(result.inspect_steps) if SiteSetting.discourse_id_verbose_logging
|
||||
render json: { error: "Invalid request" }, status: 400
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
58
app/services/discourse_id/revoke.rb
Normal file
58
app/services/discourse_id/revoke.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DiscourseId::Revoke
|
||||
include Service::Base
|
||||
|
||||
policy :discourse_id_properly_configured
|
||||
|
||||
params do
|
||||
attribute :identifier, :string
|
||||
attribute :timestamp, :integer
|
||||
attribute :signature, :string
|
||||
|
||||
validates :identifier, :timestamp, :signature, presence: true
|
||||
|
||||
with_options if: -> { identifier.present? && timestamp.present? && signature.present? } do
|
||||
validate :timestamp_expired?
|
||||
validate :proper_signature?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def timestamp_expired?
|
||||
time_diff = (Time.current.to_i - timestamp).abs
|
||||
return if time_diff <= 5.minutes.to_i
|
||||
|
||||
errors.add(:timestamp, "is expired: #{time_diff} seconds old")
|
||||
end
|
||||
|
||||
def proper_signature?
|
||||
expected_signature =
|
||||
OpenSSL::HMAC.hexdigest(
|
||||
"sha256",
|
||||
Digest::SHA256.hexdigest(SiteSetting.discourse_id_client_secret),
|
||||
"#{SiteSetting.discourse_id_client_id}:#{identifier}:#{timestamp}",
|
||||
)
|
||||
return if ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
|
||||
errors.add(:signature, "is invalid for user id #{identifier}")
|
||||
end
|
||||
end
|
||||
|
||||
model :associated_account
|
||||
step :revoke_auth_tokens
|
||||
|
||||
private
|
||||
|
||||
def discourse_id_properly_configured
|
||||
SiteSetting.enable_discourse_id && SiteSetting.discourse_id_client_id.present? &&
|
||||
SiteSetting.discourse_id_client_secret.present?
|
||||
end
|
||||
|
||||
def fetch_associated_account(params:)
|
||||
UserAssociatedAccount.find_by(provider_name: "discourse_id", provider_uid: params.identifier)
|
||||
end
|
||||
|
||||
def revoke_auth_tokens(associated_account:)
|
||||
UserAuthToken.where(user_id: associated_account.user_id).destroy_all
|
||||
end
|
||||
end
|
|
@ -2521,6 +2521,9 @@ en:
|
|||
to_continue: "Please Log In"
|
||||
preferences: "You need to be logged in to change your user preferences."
|
||||
not_approved: "Your account hasn't been approved yet. You will be notified by email when you are ready to log in."
|
||||
discourse_id:
|
||||
name: "Discourse ID"
|
||||
title: "Log in with Discourse ID"
|
||||
google_oauth2:
|
||||
name: "Google"
|
||||
title: "Sign in with Google"
|
||||
|
|
|
@ -1271,6 +1271,7 @@ Discourse::Application.routes.draw do
|
|||
|
||||
match "/auth/failure", to: "users/omniauth_callbacks#failure", via: %i[get post]
|
||||
get "/auth/:provider", to: "users/omniauth_callbacks#confirm_request"
|
||||
post "/auth/discourse_id/revoke" => "users/discourse_id#revoke"
|
||||
match "/auth/:provider/callback", to: "users/omniauth_callbacks#complete", via: %i[get post]
|
||||
get "/associate/:token",
|
||||
to: "users/associate_accounts#connect_info",
|
||||
|
|
|
@ -592,6 +592,22 @@ login:
|
|||
enable_signup_cta:
|
||||
client: true
|
||||
default: true
|
||||
enable_discourse_id:
|
||||
default: false
|
||||
hidden: true
|
||||
discourse_id_client_id:
|
||||
default: ""
|
||||
hidden: true
|
||||
discourse_id_client_secret:
|
||||
default: ""
|
||||
hidden: true
|
||||
secret: true
|
||||
discourse_id_verbose_logging:
|
||||
default: false
|
||||
hidden: true
|
||||
discourse_id_provider_url:
|
||||
default: ""
|
||||
hidden: true
|
||||
enable_google_oauth2_logins:
|
||||
default: false
|
||||
google_oauth2_client_id: ""
|
||||
|
|
|
@ -15,3 +15,4 @@ require "auth/twitter_authenticator"
|
|||
require "auth/linkedin_oidc_authenticator"
|
||||
require "auth/google_oauth2_authenticator"
|
||||
require "auth/discord_authenticator"
|
||||
require "auth/discourse_id_authenticator"
|
||||
|
|
62
lib/auth/discourse_id_authenticator.rb
Normal file
62
lib/auth/discourse_id_authenticator.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Auth::DiscourseIdAuthenticator < Auth::ManagedAuthenticator
|
||||
class DiscourseIdStrategy < ::OmniAuth::Strategies::OAuth2
|
||||
option :name, "discourse_id"
|
||||
|
||||
option :client_options, auth_scheme: :basic_auth
|
||||
|
||||
def authorize_params
|
||||
super.tap { _1[:intent] = "signup" if request.params["signup"] == "true" }
|
||||
end
|
||||
|
||||
def callback_url
|
||||
Discourse.base_url_no_prefix + callback_path
|
||||
end
|
||||
|
||||
uid { access_token.params["info"]["uuid"] }
|
||||
|
||||
info do
|
||||
{
|
||||
nickname: access_token.params["info"]["username"],
|
||||
email: access_token.params["info"]["email"],
|
||||
image: access_token.params["info"]["image"],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def name
|
||||
"discourse_id"
|
||||
end
|
||||
|
||||
def display_name
|
||||
"Discourse ID"
|
||||
end
|
||||
|
||||
def enabled?
|
||||
SiteSetting.enable_discourse_id && SiteSetting.discourse_id_client_id.present? &&
|
||||
SiteSetting.discourse_id_client_secret.present?
|
||||
end
|
||||
|
||||
def site
|
||||
SiteSetting.discourse_id_provider_url.presence || "https://id.discourse.com"
|
||||
end
|
||||
|
||||
def register_middleware(omniauth)
|
||||
omniauth.provider DiscourseIdStrategy,
|
||||
scope: "read",
|
||||
setup: ->(env) do
|
||||
env["omniauth.strategy"].options.merge!(
|
||||
client_id: SiteSetting.discourse_id_client_id,
|
||||
client_secret: SiteSetting.discourse_id_client_secret,
|
||||
client_options: {
|
||||
site:,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def primary_email_verified?(auth_token)
|
||||
true # email will be verified at source
|
||||
end
|
||||
end
|
|
@ -487,6 +487,10 @@ module Discourse
|
|||
end
|
||||
|
||||
BUILTIN_AUTH = [
|
||||
Auth::AuthProvider.new(
|
||||
authenticator: Auth::DiscourseIdAuthenticator.new,
|
||||
icon: "fab-discourse",
|
||||
),
|
||||
Auth::AuthProvider.new(
|
||||
authenticator: Auth::FacebookAuthenticator.new,
|
||||
frame_width: 580,
|
||||
|
|
115
spec/lib/auth/discourse_id_authenticator_spec.rb
Normal file
115
spec/lib/auth/discourse_id_authenticator_spec.rb
Normal file
|
@ -0,0 +1,115 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe Auth::DiscourseIdAuthenticator do
|
||||
let(:authenticator) { described_class.new }
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
context "with default settings" do
|
||||
before do
|
||||
SiteSetting.enable_discourse_id = true
|
||||
SiteSetting.discourse_id_client_id = "client_id"
|
||||
SiteSetting.discourse_id_client_secret = "client_secret"
|
||||
end
|
||||
|
||||
it "has the right name" do
|
||||
expect(authenticator.name).to eq("discourse_id")
|
||||
end
|
||||
|
||||
it "can connect to existing user" do
|
||||
expect(authenticator.can_connect_existing_user?).to eq(true)
|
||||
end
|
||||
|
||||
it "can be revoked" do
|
||||
expect(authenticator.can_revoke?).to eq(true)
|
||||
end
|
||||
|
||||
it "verifies email by default" do
|
||||
expect(authenticator.primary_email_verified?({})).to eq(true)
|
||||
end
|
||||
|
||||
it "does not always update user email" do
|
||||
expect(authenticator.always_update_user_email?).to eq(false)
|
||||
end
|
||||
|
||||
describe "#enabled?" do
|
||||
it "is enabled with proper settings" do
|
||||
expect(authenticator.enabled?).to eq(true)
|
||||
end
|
||||
|
||||
it "is disabled without client id" do
|
||||
SiteSetting.discourse_id_client_id = ""
|
||||
expect(authenticator.enabled?).to eq(false)
|
||||
end
|
||||
|
||||
it "is disabled without client secret" do
|
||||
SiteSetting.discourse_id_client_secret = ""
|
||||
expect(authenticator.enabled?).to eq(false)
|
||||
end
|
||||
|
||||
it "is disabled when `discourse_login_client_enabled` is false" do
|
||||
SiteSetting.enable_discourse_id = false
|
||||
expect(authenticator.enabled?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#site" do
|
||||
it "returns default URL when setting is blank" do
|
||||
SiteSetting.discourse_id_provider_url = ""
|
||||
expect(authenticator.site).to eq("https://id.discourse.com")
|
||||
end
|
||||
|
||||
it "returns configured URL when setting is present" do
|
||||
SiteSetting.discourse_id_provider_url = "https://custom.example.com"
|
||||
expect(authenticator.site).to eq("https://custom.example.com")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "DiscourseIdStrategy" do
|
||||
let(:strategy) { Auth::DiscourseIdAuthenticator::DiscourseIdStrategy.new({}) }
|
||||
|
||||
it "uses 'discourse_id' name" do
|
||||
expect(strategy.options.name).to eq("discourse_id")
|
||||
end
|
||||
|
||||
it "uses basic auth as authentication scheme" do
|
||||
expect(strategy.options.client_options.auth_scheme).to eq(:basic_auth)
|
||||
end
|
||||
|
||||
it "defines callback_url" do
|
||||
expect(strategy.callback_url).to eq("http://test.localhost/auth/discourse_id/callback")
|
||||
end
|
||||
end
|
||||
|
||||
let(:hash) do
|
||||
OmniAuth::AuthHash.new(
|
||||
provider: "discourse_id",
|
||||
info: {
|
||||
"nickname" => "test_user",
|
||||
"email" => user.email,
|
||||
"image" => "http://example.com/avatar.png",
|
||||
"uuid" => "12345",
|
||||
},
|
||||
uid: "99",
|
||||
)
|
||||
end
|
||||
|
||||
describe "after_authenticate" do
|
||||
it "works and syncs username, email, avatar" do
|
||||
result = authenticator.after_authenticate(hash)
|
||||
expect(result.user).to eq(user)
|
||||
expect(result.failed).to eq(false)
|
||||
|
||||
expect(result.username).to eq("test_user")
|
||||
expect(result.email).to eq(user.email)
|
||||
|
||||
associated_record =
|
||||
UserAssociatedAccount.find_by(provider_name: "discourse_id", user_id: user.id)
|
||||
|
||||
expect(associated_record[:info]["image"]).to eq("http://example.com/avatar.png")
|
||||
expect(associated_record[:info]["uuid"]).to eq("12345")
|
||||
end
|
||||
end
|
||||
end
|
135
spec/requests/discourse_id_controller_spec.rb
Normal file
135
spec/requests/discourse_id_controller_spec.rb
Normal file
|
@ -0,0 +1,135 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Users::DiscourseIdController do
|
||||
let(:client_id) { SiteSetting.discourse_id_client_id }
|
||||
let(:hashed_secret) { Digest::SHA256.hexdigest(SiteSetting.discourse_id_client_secret) }
|
||||
let(:identifier) { SecureRandom.hex }
|
||||
let(:provider_name) { "discourse_id" }
|
||||
|
||||
let!(:user) { Fabricate(:user) }
|
||||
let!(:user_associated_account) do
|
||||
Fabricate(:user_associated_account, user:, provider_name:, provider_uid: identifier)
|
||||
end
|
||||
|
||||
before do
|
||||
SiteSetting.enable_discourse_id = true
|
||||
SiteSetting.discourse_id_client_id = SecureRandom.hex
|
||||
SiteSetting.discourse_id_client_secret = SecureRandom.hex
|
||||
end
|
||||
|
||||
describe "#revoke" do
|
||||
context "with valid parameters" do
|
||||
it "revokes all auth tokens for the user" do
|
||||
UserAuthToken.generate!(user_id: user.id)
|
||||
UserAuthToken.generate!(user_id: user.id)
|
||||
|
||||
expect(UserAuthToken.where(user_id: user.id).count).to eq(2)
|
||||
|
||||
timestamp = Time.now.to_i
|
||||
signature =
|
||||
OpenSSL::HMAC.hexdigest(
|
||||
"sha256",
|
||||
hashed_secret,
|
||||
"#{client_id}:#{identifier}:#{timestamp}",
|
||||
)
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["success"]).to eq(true)
|
||||
|
||||
expect(UserAuthToken.where(user_id: user.id).count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid parameters" do
|
||||
it "returns 400 when signature is invalid" do
|
||||
timestamp = Time.now.to_i
|
||||
signature = SecureRandom.hex
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
expect(response.parsed_body["error"]).to match(/Signature is invalid/)
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
end
|
||||
|
||||
it "returns 400 when timestamp is too old" do
|
||||
timestamp = Time.now.to_i - 6.minutes.to_i
|
||||
signature =
|
||||
OpenSSL::HMAC.hexdigest(
|
||||
"sha256",
|
||||
hashed_secret,
|
||||
"#{client_id}:#{identifier}:#{timestamp}",
|
||||
)
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
expect(response.parsed_body["error"]).to match(/Timestamp is expired/)
|
||||
end
|
||||
|
||||
it "returns 400 when user_id is not found" do
|
||||
identifier = "non_existent_user_id"
|
||||
timestamp = Time.now.to_i
|
||||
signature =
|
||||
OpenSSL::HMAC.hexdigest(
|
||||
"sha256",
|
||||
hashed_secret,
|
||||
"#{client_id}:#{identifier}:#{timestamp}",
|
||||
)
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
expect(response.parsed_body["error"]).to eq("Invalid request")
|
||||
end
|
||||
|
||||
it "returns 400 when client_id or client_secret is blank" do
|
||||
SiteSetting.discourse_id_client_id = ""
|
||||
|
||||
timestamp = Time.now.to_i
|
||||
signature =
|
||||
OpenSSL::HMAC.hexdigest(
|
||||
"sha256",
|
||||
hashed_secret,
|
||||
"#{client_id}:#{identifier}:#{timestamp}",
|
||||
)
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
expect(response.parsed_body["error"]).to eq("Invalid request")
|
||||
|
||||
SiteSetting.discourse_id_client_id = SecureRandom.hex
|
||||
SiteSetting.discourse_id_client_secret = ""
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
|
||||
expect(response.status).to eq(400)
|
||||
expect(response.parsed_body["error"]).to eq("Invalid request")
|
||||
end
|
||||
end
|
||||
|
||||
context "with rate limiting" do
|
||||
before { RateLimiter.enable }
|
||||
|
||||
it "rate limits after 5 requests per minute for the same user_id" do
|
||||
identifier = "non_existent_user_id"
|
||||
timestamp = Time.now.to_i
|
||||
signature = SecureRandom.hex
|
||||
|
||||
5.times do
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
expect(response.status).to eq(400)
|
||||
end
|
||||
|
||||
post "/auth/discourse_id/revoke.json", params: { signature:, identifier:, timestamp: }
|
||||
expect(response.status).to eq(429)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
94
spec/services/discourse_id/revoke_spec.rb
Normal file
94
spec/services/discourse_id/revoke_spec.rb
Normal file
|
@ -0,0 +1,94 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseId::Revoke do
|
||||
describe described_class::Contract, type: :model do
|
||||
subject(:contract) { described_class.new(identifier:, timestamp:, signature:) }
|
||||
|
||||
let(:identifier) { SecureRandom.hex }
|
||||
let(:signature) { SecureRandom.hex }
|
||||
let(:timestamp) { Time.current.to_i }
|
||||
|
||||
it { is_expected.to validate_presence_of(:identifier) }
|
||||
it { is_expected.to validate_presence_of(:timestamp) }
|
||||
it { is_expected.to validate_presence_of(:signature) }
|
||||
|
||||
context "when the timestamp is expired" do
|
||||
let(:timestamp) { Time.current.to_i - 3600 }
|
||||
|
||||
it do
|
||||
is_expected.not_to allow_value(timestamp).for(:timestamp).with_message(
|
||||
"is expired: 3600 seconds old",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the signature is not valid" do
|
||||
it do
|
||||
is_expected.not_to allow_value(signature).for(:signature).with_message(
|
||||
"is invalid for user id #{identifier}",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
subject(:result) { described_class.call(params:) }
|
||||
|
||||
fab!(:user)
|
||||
|
||||
let(:params) { { identifier: uaa.provider_uid, timestamp:, signature: } }
|
||||
let(:client_id) { SiteSetting.discourse_id_client_id }
|
||||
let(:hashed_secret) { Digest::SHA256.hexdigest(SiteSetting.discourse_id_client_secret) }
|
||||
let(:identifier) { SecureRandom.hex }
|
||||
let(:provider_name) { "discourse_id" }
|
||||
let(:timestamp) { Time.current.to_i }
|
||||
let(:signature) do
|
||||
OpenSSL::HMAC.hexdigest("sha256", hashed_secret, "#{client_id}:#{identifier}:#{timestamp}")
|
||||
end
|
||||
let!(:uaa) do
|
||||
Fabricate(
|
||||
:user_associated_account,
|
||||
user:,
|
||||
provider_name: "discourse_id",
|
||||
provider_uid: identifier,
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
SiteSetting.enable_discourse_id = true
|
||||
SiteSetting.discourse_id_client_id = SecureRandom.hex
|
||||
SiteSetting.discourse_id_client_secret = SecureRandom.hex
|
||||
end
|
||||
|
||||
context "when discourse id is not properly configured" do
|
||||
before do
|
||||
SiteSetting.discourse_id_client_id = nil
|
||||
SiteSetting.discourse_id_client_secret = nil
|
||||
end
|
||||
|
||||
it { is_expected.to fail_a_policy(:discourse_id_properly_configured) }
|
||||
end
|
||||
|
||||
context "when contract is not valid" do
|
||||
let(:signature) { nil }
|
||||
|
||||
it { is_expected.to fail_a_contract }
|
||||
end
|
||||
|
||||
context "when the associated account is not found" do
|
||||
before { uaa.destroy! }
|
||||
|
||||
it { is_expected.to fail_to_find_a_model(:associated_account) }
|
||||
end
|
||||
|
||||
context "when everything is ok" do
|
||||
before { UserAuthToken.generate!(user_id: user.id) }
|
||||
|
||||
it { is_expected.to run_successfully }
|
||||
|
||||
it "destroys user auth tokens" do
|
||||
expect { result }.to change { UserAuthToken.where(user_id: user.id).count }.by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
62
spec/system/discourse_id_spec.rb
Normal file
62
spec/system/discourse_id_spec.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "discourse login client auth" do
|
||||
include OmniauthHelpers
|
||||
|
||||
before do
|
||||
OmniAuth.config.test_mode = true
|
||||
SiteSetting.enable_discourse_id = true
|
||||
SiteSetting.discourse_id_client_id = "asdasd"
|
||||
SiteSetting.discourse_id_client_secret = "wadayathink"
|
||||
|
||||
OmniAuth.config.mock_auth[:discourse_id] = OmniAuth::AuthHash.new(
|
||||
provider: "discourse_id",
|
||||
uid: OmniauthHelpers::UID,
|
||||
info:
|
||||
OmniAuth::AuthHash::InfoHash.new(
|
||||
email: OmniauthHelpers::EMAIL,
|
||||
username: OmniauthHelpers::USERNAME,
|
||||
),
|
||||
)
|
||||
|
||||
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:github]
|
||||
end
|
||||
|
||||
after { reset_omniauth_config(:discourse_id) }
|
||||
|
||||
let(:signup_form) { PageObjects::Pages::Signup.new }
|
||||
|
||||
context "when user does not exist" do
|
||||
context "when auth_skip_create_confirm is false" do
|
||||
before { SiteSetting.auth_skip_create_confirm = false }
|
||||
|
||||
it "skips the signup form and creates the account directly" do
|
||||
visit("/")
|
||||
signup_form.open.click_social_button("discourse_id")
|
||||
expect(page).to have_css(".login-welcome-header")
|
||||
end
|
||||
end
|
||||
|
||||
context "when auth_skip_create_confirm is true" do
|
||||
before { SiteSetting.auth_skip_create_confirm = true }
|
||||
|
||||
it "skips the signup form and creates the account directly" do
|
||||
visit("/")
|
||||
signup_form.open.click_social_button("discourse_id")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when user exists" do
|
||||
fab!(:user) do
|
||||
Fabricate(:user, email: OmniauthHelpers::EMAIL, username: OmniauthHelpers::USERNAME)
|
||||
end
|
||||
|
||||
it "logs in user" do
|
||||
visit("/")
|
||||
signup_form.open.click_social_button("discourse_id")
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue