Files
ubicloud/spec/routes/web/clover_web_spec.rb
Jeremy Evans 7acbf5cd5a Switch to multi phase login
Multi phase login is the practice where you first ask for the login
(email) and not the password, and after you know the related account,
you show the options available for login to that account.

Multi phase login allows social login and OIDC login to be part of the
normal login flow. Once we know the related account, we can show the
authentication options for the account, and not include options that
are not available. If the account does not have a password and is only
connected to one social login provider, then the password field does
not display, and the only button is the button login to the social
login provider.

For accounts that only allow password authentication, multi phase login
results in an extra step during login. However, I think it's better to
support non-password authentication as part of the normal login flow.

A minor improvement I made during this process is if the user enters
their login, but cannot remember their password, if they click the
"Forgot your password?" link, their email address is already filled in.

Ideally, the login field would be have the readonly attribute during
the second login phase.  However, I found that this negatively affects
password managers such as 1password.  So I didn't mark the field as
readonly.  I left the handling of the readonly attribute as boolean,
in case we want to use readonly attributes elsewhere.

This required relatively few spec changes outside of the auth tests,
since almost all tests that test for logged in accounts use the login
helper method.
2025-07-09 04:32:44 +09:00

122 lines
3.8 KiB
Ruby

# frozen_string_literal: true
require_relative "spec_helper"
RSpec.describe Clover do
it "handles CSRF token errors" do
visit "/login"
find(".rodauth input[name=_csrf]", visible: false).set("")
click_button "Sign in"
expect(page.status_code).to eq(400)
expect(page).to have_flash_error("An invalid security token submitted with this request, please try again")
end
it "does not redirect to requested path if path is too long" do
create_account
visit("/a" * 2048)
expect(page.status_code).to eq(200)
expect(page).to have_current_path("/login", ignore_query: true)
fill_in "Email Address", with: TEST_USER_EMAIL
click_button "Sign in"
fill_in "Password", with: TEST_USER_PASSWORD
click_button "Sign in"
expect(page.title).to end_with("Dashboard")
end
if ENV["CLOVER_FREEZE"] != "1"
it "raises error if no_authorization_needed called when not needed or already authorized" do
create_account.create_project_with_default_policy("project-1")
login
visit "/test-no-authorization-needed/once"
expect(page.status_code).to eq(200)
visit "/test-no-authorization-needed/authorization-error"
expect(page.status_code).to eq(403)
multiple_re = /called no_authorization_needed when authorization already not needed: /
missing_re = /no authorization check for /
expect { visit "/test-no-authorization-needed/twice" }.to raise_error(RuntimeError, multiple_re)
expect { visit "/test-no-authorization-needed/after-authorization" }.to raise_error(RuntimeError, multiple_re)
expect { visit "/test-no-authorization-needed/never" }.to raise_error(RuntimeError, missing_re)
expect { visit "/test-no-authorization-needed/runtime-error" }.to raise_error(RuntimeError, missing_re)
end
it "raises original exception and not no authorization check exception when using SHOW_ERRORS" do
show_errors = ENV["SHOW_ERRORS"]
ENV["SHOW_ERRORS"] = "1"
create_account.create_project_with_default_policy("project-1")
login
expect { visit "/test-no-authorization-needed/runtime-error" }.to raise_error(RuntimeError, "foo")
ensure
ENV.delete("SHOW_ERRORS") unless show_errors
end
it "raises error for non-GET request without audit logging" do
expect { post "/webhook/test-no-audit-logging/test" }.to raise_error(RuntimeError, /no audit logging for /)
end
end
it "raises error for unsupported audit log action" do
expect { post "/webhook/test-no-audit-logging/bad" }.to raise_error(RuntimeError, "unsupported audit_log action: bad_action")
end
it "handles expected errors" do
expect(Clog).to receive(:emit).with("route exception").and_call_original
visit "/webhook/test-error"
expect(page.title).to eq("Ubicloud - UnexceptedError")
end
it "raises unexpected errors in test environment" do
expect(Clog).not_to receive(:emit)
expect { visit "/webhook/test-error?message=treat+as+unexpected+error" }.to raise_error(RuntimeError, "treat as unexpected error")
end
it "does not have broken links" do
create_account
login
visited = {"" => true}
failures = []
queue = Queue.new
queue.push([nil, "/"])
pop = lambda do
queue.pop(true)
rescue ThreadError
end
while (tuple = pop.call)
from, path = tuple
next if visited[path]
visited[path] = true
visit path
if page.status_code == 404
failures << [from, path]
end
if page.response_headers["content-type"].include?("text/html")
links = page.all("a").map do |a|
a["href"].sub(/#.*\z/, "")
end
links.reject! do |path|
path.empty? || path.start_with?(%r{https://|mailto:})
end
links.each do |path|
queue.push [page.current_path, path]
end
end
end
expect(failures).to be_empty
end
end