DEV: Full system specs coverage for signup/login (#26977)

- login with username/password
- login with username/password and 2FA
- login with username/password back up code
- login with magic link
- login with magic link and 2FA
- login with magic link and back up code
- login when 2FA is required
- reset password
---
- signup and activate account
- signup with invite code
- signup with invite link
- signup and approve account
- signup and auto approve account
- signup with blocked domain
---
- basic login with Facebook
- basic login with Google
- basic login with Github
- basic login with Twitter
- basic login with Discord
- basic login with Linkedin
This commit is contained in:
Jan Cernik 2024-05-17 11:56:43 -03:00 committed by GitHub
parent e04ac5e2d8
commit 57eff8b760
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 696 additions and 0 deletions

View File

@ -0,0 +1,88 @@
# frozen_string_literal: true
module OmniauthHelpers
FIRST_NAME = "John"
LAST_NAME = "Doe"
FULL_NAME = "John Doe"
USERNAME = "john"
EMAIL = "johndoe@example.com"
def mock_facebook_auth(email: EMAIL, name: FULL_NAME)
OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new(
provider: "facebook",
uid: "12345",
info: OmniAuth::AuthHash::InfoHash.new(email: email, name: name),
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:facebook]
end
def mock_google_auth(email: EMAIL, name: FULL_NAME, verified: true)
OmniAuth.config.mock_auth[:google_oauth2] = OmniAuth::AuthHash.new(
provider: "google_oauth2",
uid: "12345",
info: OmniAuth::AuthHash::InfoHash.new(email: email, name: name),
extra: {
raw_info: {
email_verified: verified,
},
},
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
end
def mock_github_auth(email: EMAIL, nickname: USERNAME, name: FULL_NAME, verified: true)
OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(
provider: "github",
uid: "12345",
info: OmniAuth::AuthHash::InfoHash.new(email: email, nickname: nickname, name: name),
extra: {
all_emails: [{ email: email, primary: true, verified: verified, visibility: "private" }],
},
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:github]
end
def mock_twitter_auth(nickname: USERNAME, name: FULL_NAME, verified: true)
OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new(
provider: "twitter",
uid: "12345",
info: OmniAuth::AuthHash::InfoHash.new(nickname: nickname, name: name),
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
def mock_discord_auth(email: EMAIL, username: USERNAME, name: FULL_NAME)
OmniAuth.config.mock_auth[:discord] = OmniAuth::AuthHash.new(
provider: "discord",
uid: "12345",
info: OmniAuth::AuthHash::InfoHash.new(email: email, name: name),
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:discord]
end
def mock_linkedin_auth(email: EMAIL, first_name: FIRST_NAME, last_name: LAST_NAME)
OmniAuth.config.mock_auth[:linkedin_oidc] = OmniAuth::AuthHash.new(
provider: "linkedin_oidc",
uid: "12345",
info:
OmniAuth::AuthHash::InfoHash.new(
email: email,
first_name: first_name,
last_name: last_name,
),
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:linkedin_oidc]
end
def reset_omniauth_config(provider)
OmniAuth.config.test_mode = false
OmniAuth.config.mock_auth[provider] = nil
Rails.application.env_config["omniauth.auth"] = nil
end
end

159
spec/system/login_spec.rb Normal file
View File

@ -0,0 +1,159 @@
# frozen_string_literal: true
require "rotp"
describe "Login", type: :system do
let(:login_modal) { PageObjects::Modals::Login.new }
fab!(:user) { Fabricate(:user, username: "john", password: "supersecurepassword") }
before { Jobs.run_immediately! }
context "with username and password" do
it "can login" do
EmailToken.confirm(Fabricate(:email_token, user: user).token)
login_modal.open
login_modal.fill(username: "john", password: "supersecurepassword")
login_modal.click_login
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
it "can login and activate account" do
login_modal.open
login_modal.fill(username: "john", password: "supersecurepassword")
login_modal.click_login
find(".activation-controls button.resend").click
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
mail = ActionMailer::Base.deliveries.last
expect(mail.to).to contain_exactly(user.email)
activation_link = mail.body.to_s[%r{/u/activate-account/\S+}]
visit activation_link
find("#activate-account-button").click
visit "/"
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
it "can reset password" do
login_modal.open
login_modal.fill_username("john")
login_modal.forgot_password
find("button.forgot-password-reset").click
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
mail = ActionMailer::Base.deliveries.last
expect(mail.to).to contain_exactly(user.email)
reset_password_link = mail.body.to_s[%r{/u/password-reset/\S+}]
visit reset_password_link
find("#new-account-password").fill_in(with: "newsuperpassword")
find("form .btn-primary").click
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
context "with login link" do
it "can login" do
login_modal.open
login_modal.fill_username("john")
login_modal.email_login_link
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
mail = ActionMailer::Base.deliveries.last
expect(mail.to).to contain_exactly(user.email)
login_link = mail.body.to_s[%r{/session/email-login/\S+}]
visit login_link
find(".email-login-form .btn-primary").click
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
context "with two-factor authentication" do
let!(:user_second_factor) { Fabricate(:user_second_factor_totp, user: user) }
let!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup, user: user) }
fab!(:other_user) { Fabricate(:user, username: "jane", password: "supersecurepassword") }
before do
EmailToken.confirm(Fabricate(:email_token, user: user).token)
EmailToken.confirm(Fabricate(:email_token, user: other_user).token)
end
context "when it is required" do
before { SiteSetting.enforce_second_factor = "all" }
it "requires to set 2FA after login" do
login_modal.open
login_modal.fill(username: "jane", password: "supersecurepassword")
login_modal.click_login
expect(page).to have_css(".header-dropdown-toggle.current-user")
expect(page).to have_content(I18n.t("js.user.second_factor.enforced_notice"))
end
end
it "can login with totp" do
login_modal.open
login_modal.fill(username: "john", password: "supersecurepassword")
login_modal.click_login
expect(page).to have_css(".login-modal-body.second-factor")
totp = ROTP::TOTP.new(user_second_factor.data).now
find("#login-second-factor").fill_in(with: totp)
login_modal.click_login
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
it "can login with backup code" do
login_modal.open
login_modal.fill(username: "john", password: "supersecurepassword")
login_modal.click_login
expect(page).to have_css(".login-modal-body.second-factor")
find(".toggle-second-factor-method").click
find(".second-factor-token-input").fill_in(with: "iAmValidBackupCode")
login_modal.click_login
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
it "can login with login link and totp" do
login_modal.open
login_modal.fill_username("john")
login_modal.email_login_link
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
mail = ActionMailer::Base.deliveries.last
expect(mail.to).to contain_exactly(user.email)
login_link = mail.body.to_s[%r{/session/email-login/\S+}]
visit login_link
totp = ROTP::TOTP.new(user_second_factor.data).now
find(".second-factor-token-input").fill_in(with: totp)
find(".email-login-form .btn-primary").click
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
it "can login with login link and backup code" do
login_modal.open
login_modal.fill_username("john")
login_modal.email_login_link
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
mail = ActionMailer::Base.deliveries.last
expect(mail.to).to contain_exactly(user.email)
login_link = mail.body.to_s[%r{/session/email-login/\S+}]
visit login_link
find(".toggle-second-factor-method").click
find(".second-factor-token-input").fill_in(with: "iAmValidBackupCode")
find(".email-login-form .btn-primary").click
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
end

View File

@ -6,6 +6,75 @@ module PageObjects
def open?
super && has_css?(".login-modal")
end
def closed?
super && has_no_css?(".login-modal")
end
def open
visit("/login")
end
def open_from_header
find(".login-button").click
end
def open_signup
find("#new-account-link").click
end
def click_login
find("#login-button").click
end
def email_login_link
find("#email-login-link").click
end
def forgot_password
find("#forgot-password-link").click
end
def fill_username(username)
find("#login-account-name").fill_in(with: username)
end
def fill_password(password)
find("#login-account-password").fill_in(with: password)
end
def fill(username: nil, password: nil)
fill_username(username) if username
fill_password(password) if password
end
def select_facebook
find(".btn-social.facebook").click
end
def select_google
find(".btn-social.google_oauth2").click
end
def select_github
find(".btn-social.github").click
end
def select_twitter
find(".btn-social.twitter").click
end
def select_discord
find(".btn-social.discord").click
end
def select_linkedin
find(".btn-social.linkedin_oidc").click
end
def select_passkey
find(".btn-social.passkey-login-button").click
end
end
end
end

View File

@ -0,0 +1,101 @@
# frozen_string_literal: true
module PageObjects
module Modals
class Signup < PageObjects::Modals::Base
def open?
super && has_css?(".modal.create-account")
end
def closed?
super && has_no_css?(".modal.create-account")
end
def open
visit("/signup")
end
def open_from_header
find(".sign-up-button").click
end
def open_login
find("#login-link").click
end
def click_create_account
find(".modal.create-account .btn-primary").click
end
def has_password_input?
has_css?("#new-account-password")
end
def has_no_password_input?
has_no_css?("#new-account-password")
end
def fill_email(email)
find("#new-account-email").fill_in(with: email)
end
def fill_username(username)
find("#new-account-username").fill_in(with: username)
end
def fill_name(name)
find("#new-account-name").fill_in(with: name)
end
def fill_password(password)
find("#new-account-password").fill_in(with: password)
end
def fill_code(code)
find("#inviteCode").fill_in(with: code)
end
def has_valid_email?
find(".create-account-email").has_css?("#account-email-validation.good")
end
def has_valid_username?
find(".create-account__username").has_css?("#username-validation.good")
end
def has_valid_password?
find(".create-account__password").has_css?("#password-validation.good")
end
def has_valid_fields?
has_valid_email?
has_valid_username?
has_valid_password?
end
def select_facebook
find(".btn-social.facebook").click
end
def select_google
find(".btn-social.google_oauth2").click
end
def select_github
find(".btn-social.github").click
end
def select_twitter
find(".btn-social.twitter").click
end
def select_discord
find(".btn-social.discord").click
end
def select_linkedin
find(".btn-social.linkedin_oidc").click
end
end
end
end

150
spec/system/signup_spec.rb Normal file
View File

@ -0,0 +1,150 @@
# frozen_string_literal: true
describe "Signup", type: :system do
let(:login_modal) { PageObjects::Modals::Login.new }
let(:signup_modal) { PageObjects::Modals::Signup.new }
context "when anyone can create an account" do
it "can signup and activate account" do
Jobs.run_immediately!
signup_modal.open
signup_modal.fill_email("johndoe@example.com")
signup_modal.fill_username("john")
signup_modal.fill_password("supersecurepassword")
expect(signup_modal).to have_valid_fields
signup_modal.click_create_account
wait_for(timeout: 5) { ActionMailer::Base.deliveries.count != 0 }
mail = ActionMailer::Base.deliveries.last
expect(mail.to).to contain_exactly("johndoe@example.com")
activation_link = mail.body.to_s[%r{/u/activate-account/\S+}]
visit "/"
visit activation_link
find("#activate-account-button").click
visit "/"
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
context "with invite code" do
before { SiteSetting.invite_code = "cupcake" }
it "can signup with valid code" do
signup_modal.open
signup_modal.fill_email("johndoe@example.com")
signup_modal.fill_username("john")
signup_modal.fill_password("supersecurepassword")
signup_modal.fill_code("cupcake")
expect(signup_modal).to have_valid_fields
signup_modal.click_create_account
expect(page).to have_css(".account-created")
end
it "cannot signup with invalid code" do
signup_modal.open
signup_modal.fill_email("johndoe@example.com")
signup_modal.fill_username("john")
signup_modal.fill_password("supersecurepassword")
signup_modal.fill_code("pudding")
expect(signup_modal).to have_valid_fields
signup_modal.click_create_account
expect(signup_modal).to have_content(I18n.t("login.wrong_invite_code"))
expect(signup_modal).to have_no_css(".account-created")
end
end
context "when user requires approval" do
before do
SiteSetting.must_approve_users = true
SiteSetting.auto_approve_email_domains = "awesomeemail.com"
end
it "can signup but cannot login until approval" do
signup_modal.open
signup_modal.fill_email("johndoe@example.com")
signup_modal.fill_username("john")
signup_modal.fill_password("supersecurepassword")
expect(signup_modal).to have_valid_fields
signup_modal.click_create_account
visit "/"
login_modal.open
login_modal.fill_username("john")
login_modal.fill_password("supersecurepassword")
login_modal.click_login
expect(login_modal).to have_content(I18n.t("login.not_approved"))
wait_for(timeout: 5) { User.find_by(username: "john") != nil }
user = User.find_by(username: "john")
user.update!(approved: true)
EmailToken.confirm(Fabricate(:email_token, user: user).token)
login_modal.click_login
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
it "can login directly when using an auto approved email" do
signup_modal.open
signup_modal.fill_email("johndoe@awesomeemail.com")
signup_modal.fill_username("john")
signup_modal.fill_password("supersecurepassword")
expect(signup_modal).to have_valid_fields
signup_modal.click_create_account
wait_for(timeout: 5) { User.find_by(username: "john") != nil }
user = User.find_by(username: "john")
EmailToken.confirm(Fabricate(:email_token, user: user).token)
login_modal.open
login_modal.fill_username("john")
login_modal.fill_password("supersecurepassword")
login_modal.click_login
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
end
context "when the email domain is blocked" do
before { SiteSetting.blocked_email_domains = "example.com" }
it "cannot signup" do
signup_modal.open
signup_modal.fill_email("johndoe@example.com")
signup_modal.fill_username("john")
signup_modal.fill_password("supersecurepassword")
expect(signup_modal).to have_valid_username
expect(signup_modal).to have_valid_password
expect(signup_modal).to have_content(I18n.t("user.email.not_allowed"))
end
end
context "when site is invite only" do
before { SiteSetting.invite_only = true }
it "cannot open the signup modal" do
signup_modal.open
expect(signup_modal).to be_closed
expect(page).to have_no_css(".sign-up-button")
login_modal.open_from_header
expect(login_modal).to have_no_css("#new-account-link")
end
it "can signup with invite link" do
invite = Fabricate(:invite, email: "johndoe@example.com")
visit "/invites/#{invite.invite_key}?t=#{invite.email_token}"
find("#new-account-password").fill_in(with: "supersecurepassword")
find(".username-input").has_css?("#username-validation.good")
find(".create-account__password-tip-validation").has_css?("#password-validation.good")
find(".invitation-cta__accept").click
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
end

View File

@ -0,0 +1,129 @@
# frozen_string_literal: true
describe "Social authentication", type: :system do
include OmniauthHelpers
let(:login_modal) { PageObjects::Modals::Login.new }
let(:signup_modal) { PageObjects::Modals::Signup.new }
before { OmniAuth.config.test_mode = true }
context "for Facebook" do
before { SiteSetting.enable_facebook_logins = true }
after { reset_omniauth_config(:facebook) }
it "works" do
mock_facebook_auth
visit("/")
login_modal.open
login_modal.select_facebook
expect(signup_modal).to be_open
expect(signup_modal).to have_no_password_input
expect(signup_modal).to have_valid_username
expect(signup_modal).to have_valid_email
signup_modal.click_create_account
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
context "for Google" do
before { SiteSetting.enable_google_oauth2_logins = true }
after { reset_omniauth_config(:google_oauth2) }
it "works" do
mock_google_auth
visit("/")
login_modal.open_from_header
login_modal.select_google
expect(signup_modal).to be_open
expect(signup_modal).to have_no_password_input
expect(signup_modal).to have_valid_username
expect(signup_modal).to have_valid_email
signup_modal.click_create_account
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
context "for Github" do
before { SiteSetting.enable_github_logins = true }
after { reset_omniauth_config(:github) }
it "works" do
mock_github_auth
visit("/")
login_modal.open
login_modal.select_github
expect(signup_modal).to be_open
expect(signup_modal).to have_no_password_input
expect(signup_modal).to have_valid_username
expect(signup_modal).to have_valid_email
signup_modal.click_create_account
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
context "for Twitter" do
before { SiteSetting.enable_twitter_logins = true }
after { reset_omniauth_config(:twitter) }
it "works" do
mock_twitter_auth
visit("/")
login_modal.open
login_modal.select_twitter
expect(signup_modal).to be_open
expect(signup_modal).to have_no_password_input
signup_modal.fill_email(OmniauthHelpers::EMAIL)
expect(signup_modal).to have_valid_username
expect(signup_modal).to have_valid_email
signup_modal.click_create_account
expect(page).to have_css(".account-created")
end
end
context "for Discord" do
before { SiteSetting.enable_discord_logins = true }
after { reset_omniauth_config(:discord) }
it "works" do
mock_discord_auth
visit("/")
login_modal.open
login_modal.select_discord
expect(signup_modal).to be_open
expect(signup_modal).to have_no_password_input
expect(signup_modal).to have_valid_username
expect(signup_modal).to have_valid_email
signup_modal.click_create_account
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
context "for Linkedin" do
before do
SiteSetting.linkedin_oidc_client_id = "12345"
SiteSetting.linkedin_oidc_client_secret = "abcde"
SiteSetting.enable_linkedin_oidc_logins = true
end
after { reset_omniauth_config(:linkedin_oidc) }
it "works" do
mock_linkedin_auth
visit("/")
login_modal.open
login_modal.select_linkedin
expect(signup_modal).to be_open
expect(signup_modal).to have_no_password_input
expect(signup_modal).to have_valid_username
expect(signup_modal).to have_valid_email
signup_modal.click_create_account
expect(page).to have_css(".header-dropdown-toggle.current-user")
end
end
end