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.
122 lines
3.8 KiB
Ruby
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
|