diff --git a/dpc-portal-test.sh b/dpc-portal-test.sh index 280d38c542..2c0a253cea 100755 --- a/dpc-portal-test.sh +++ b/dpc-portal-test.sh @@ -19,6 +19,7 @@ make portal # Prepare the environment docker compose -p start-v1-portals -f docker-compose.yml -f docker-compose.portals.yml up db --wait docker compose -p start-v1-portals -f docker-compose.yml -f docker-compose.portals.yml run --entrypoint "bundle exec rails db:create db:migrate RAILS_ENV=test" dpc_portal +docker compose -p start-v1-portals -f docker-compose.yml -f docker-compose.portals.yml run --entrypoint "bundle exec rails assets:clobber" dpc_portal # Run the tests echo "┌─────────────────────────------─┐" @@ -28,6 +29,7 @@ echo "│ │" echo "└────────────────────────-----───┘" docker compose -p start-v1-portals -f docker-compose.yml -f docker-compose.portals.yml run --entrypoint "bundle exec rubocop" dpc_portal +docker compose -p start-v1-portals -f docker-compose.yml -f docker-compose.portals.yml run --entrypoint "rails assets:clobber tmp:clear" dpc_portal docker compose -p start-v1-portals -f docker-compose.yml -f docker-compose.portals.yml run --entrypoint "bundle exec rspec" dpc_portal docker compose -p start-v1-portals -f docker-compose.yml -f docker-compose.portals.yml run --entrypoint docker/system-tests.sh dpc_portal echo "┌────────────────────────────────┐" diff --git a/dpc-portal/app/assets/stylesheets/login.scss b/dpc-portal/app/assets/stylesheets/login.scss index 3746a6cad6..6580550e53 100644 --- a/dpc-portal/app/assets/stylesheets/login.scss +++ b/dpc-portal/app/assets/stylesheets/login.scss @@ -34,3 +34,29 @@ line-height: 1.8; } } + +.last-used-login-wrapper { + display: block; + text-align: center; + padding: 0.5rem; + background-color: #e1f3f8; + border: 1px solid #a8ddec; + border-radius: 4px; + box-sizing: border-box; + width: 100%; + + // Make the button stretch across the full with. + .usa-button { + width: 100%; + margin-bottom: 0.5rem; + } + + .last-used-login-wrapper__badge { + font-size: 0.875rem; + font-weight: bold; + color: #005ea2; + letter-spacing: 0.5px; + margin: 0; + line-height: 1; + } +} diff --git a/dpc-portal/app/components/page/session/login_component.html.erb b/dpc-portal/app/components/page/session/login_component.html.erb index 746f3532bf..35856c15dd 100644 --- a/dpc-portal/app/components/page/session/login_component.html.erb +++ b/dpc-portal/app/components/page/session/login_component.html.erb @@ -7,15 +7,25 @@

DPC Portal sign in

Sign in with your DPC Portal Login.gov account

- <%= button_to @login_path, class: 'usa-button width-full margin-bottom-1', data: { turbo: false } do %> - - <% end %> - <%= button_to @login_path, class: 'usa-button width-full margin-bottom-1', data: { turbo: false } do %> - - <% end %> - <%= button_to @login_path, class: 'usa-button width-full margin-bottom-1', data: { turbo: false } do %> - + + <%# Build a login button for each CSP %> + <% + csps = [ + { id: :clear, logo_class: 'clear-login-button__logo', text: 'CLEAR' }, + { id: :id_me, logo_class: 'idme-login-button__logo', text: 'ID.me' }, + { id: :login_dot_gov, logo_class: 'lg-login-button__logo', text: 'Login.gov' } + ] + %> + <% csps.each do |csp| %> + <% is_last_used = (@last_used_csp == csp[:id]) %> +
+ <%= button_to @login_path, class: 'usa-button width-full margin-bottom-1', data: { turbo: false } do %> + <%= csp[:text] %> + <% end %> + <%= content_tag(:span, 'LAST USED', class: 'last-used-login-wrapper__badge') if is_last_used %> +
<% end %> + <%= render(Core::Navigation::SystemUseAgreementLinkComponent.new) %>

diff --git a/dpc-portal/app/components/page/session/login_component.rb b/dpc-portal/app/components/page/session/login_component.rb index 990a6d854f..7dc98f6cce 100644 --- a/dpc-portal/app/components/page/session/login_component.rb +++ b/dpc-portal/app/components/page/session/login_component.rb @@ -4,9 +4,10 @@ module Page module Session # Renders the log in page class LoginComponent < ViewComponent::Base - def initialize(login_path) + def initialize(login_path, last_used_csp: nil) super() @login_path = login_path + @last_used_csp = last_used_csp end end end diff --git a/dpc-portal/app/components/page/session/login_component_preview.rb b/dpc-portal/app/components/page/session/login_component_preview.rb index d9cb33960a..e3df4d3a1b 100644 --- a/dpc-portal/app/components/page/session/login_component_preview.rb +++ b/dpc-portal/app/components/page/session/login_component_preview.rb @@ -4,8 +4,16 @@ module Page module Session # Previews the log in page class LoginComponentPreview < ViewComponent::Preview - def default - render(Page::Session::LoginComponent.new(root_path)) + # The really long @param line fails a Rubocop check, but most of the alternatives I tried + # broke LookBook so I turned the check off. + # rubocop:disable Layout/LineLength + # @param last_used_csp select { choices: { "None": "", "CLEAR": "clear", "ID.me": "id_me", "Login.gov": "login_dot_gov" } } + # rubocop:enable Layout/LineLength + def default(last_used_csp: nil) + # Make sure if the user selects "None" that the value passed is actually nil and not "". + csp_value = last_used_csp.presence + + render(Page::Session::LoginComponent.new(root_path, last_used_csp: csp_value&.to_sym)) end end end diff --git a/dpc-portal/app/controllers/login_dot_gov_controller.rb b/dpc-portal/app/controllers/login_dot_gov_controller.rb index e75c6d8a9f..3e0744b50f 100644 --- a/dpc-portal/app/controllers/login_dot_gov_controller.rb +++ b/dpc-portal/app/controllers/login_dot_gov_controller.rb @@ -56,6 +56,7 @@ def sign_in_and_log(user) sign_in(user) session[:logged_in_at] = Time.now + cookies.permanent[:last_used_csp] = :login_dot_gov Rails.logger.info(['User logged in', { actionContext: LoggingConstants::ActionContext::Authentication, actionType: LoggingConstants::ActionType::UserLoggedIn }]) diff --git a/dpc-portal/app/views/users/sessions/new.html.erb b/dpc-portal/app/views/users/sessions/new.html.erb index 3949df9114..2f34d66230 100644 --- a/dpc-portal/app/views/users/sessions/new.html.erb +++ b/dpc-portal/app/views/users/sessions/new.html.erb @@ -1 +1 @@ -<%= render(Page::Session::LoginComponent.new(omniauth_authorize_path(:login_dot_gov))) %> +<%= render(Page::Session::LoginComponent.new(omniauth_authorize_path(:login_dot_gov), last_used_csp: cookies[:last_used_csp]&.to_sym)) %> diff --git a/dpc-portal/spec/components/page/session/login_component_spec.rb b/dpc-portal/spec/components/page/session/login_component_spec.rb index 4dc50bd711..c70b17a38b 100644 --- a/dpc-portal/spec/components/page/session/login_component_spec.rb +++ b/dpc-portal/spec/components/page/session/login_component_spec.rb @@ -46,4 +46,61 @@ expect(page.find_all('.grid-col-12').size).to eq 2 end end + + describe 'last used csp was CLEAR' do + let(:url) { '/' } + let(:component) { described_class.new(url, last_used_csp: :clear) } + before { render_inline(component) } + + it 'wraps only the CLEAR button' do + # Check that the right button has the "last used" wrapper. + expect(page).to have_css('.last-used-login-wrapper .clear-login-button__logo') + + # Make sure the other two don't. + expect(page).not_to have_css('.last-used-login-wrapper .idme-login-button__logo') + expect(page).not_to have_css('.last-used-login-wrapper .lg-login-button__logo') + end + end + + describe 'last used csp was ID.me' do + let(:url) { '/' } + let(:component) { described_class.new(url, last_used_csp: :id_me) } + before { render_inline(component) } + + it 'wraps only the ID.me button' do + # Check that the right button has the "last used" wrapper. + expect(page).to have_css('.last-used-login-wrapper .idme-login-button__logo') + + # Make sure the other two don't. + expect(page).not_to have_css('.last-used-login-wrapper .clear-login-button__logo') + expect(page).not_to have_css('.last-used-login-wrapper .lg-login-button__logo') + end + end + + describe 'last used csp was Login.gov' do + let(:url) { '/' } + let(:component) { described_class.new(url, last_used_csp: :login_dot_gov) } + before { render_inline(component) } + + it 'wraps only the Login.gov button' do + # Check that the right button has the "last used" wrapper. + expect(page).to have_css('.last-used-login-wrapper .lg-login-button__logo') + + # Make sure the other two don't. + expect(page).not_to have_css('.last-used-login-wrapper .clear-login-button__logo') + expect(page).not_to have_css('.last-used-login-wrapper .idme-login-button__logo') + end + + describe 'no last used csp' do + let(:url) { '/' } + let(:component) { described_class.new(url, last_used_csp: nil) } + before { render_inline(component) } + + it "doesn't wrap any buttons" do + expect(page).not_to have_css('.last-used-login-wrapper .clear-login-button__logo') + expect(page).not_to have_css('.last-used-login-wrapper .idme-login-button__logo') + expect(page).not_to have_css('.last-used-login-wrapper .lg-login-button__logo') + end + end + end end diff --git a/dpc-portal/spec/requests/login_dot_gov_spec.rb b/dpc-portal/spec/requests/login_dot_gov_spec.rb index fc1c13225e..b67b4d6c25 100644 --- a/dpc-portal/spec/requests/login_dot_gov_spec.rb +++ b/dpc-portal/spec/requests/login_dot_gov_spec.rb @@ -31,6 +31,11 @@ post '/auth/login_dot_gov' follow_redirect! end + it 'should write a cookie with the last used csp' do + post '/auth/login_dot_gov' + follow_redirect! + expect(cookies[:last_used_csp]).to eq 'login_dot_gov' + end it 'should not add another user credential' do expect(CspUser.where(uuid:, csp:).count).to eq 1 expect do diff --git a/dpc-portal/spec/requests/users/sessions_spec.rb b/dpc-portal/spec/requests/users/sessions_spec.rb index 09c65ec833..2e2ac71529 100644 --- a/dpc-portal/spec/requests/users/sessions_spec.rb +++ b/dpc-portal/spec/requests/users/sessions_spec.rb @@ -49,4 +49,25 @@ end end end + + describe 'loads last_used_csp from cookies' do + let(:last_used_csp) { :login_dot_gov } + before do + cookies[:last_used_csp] = last_used_csp.to_s + get sign_in_path + end + + # The functionality of which button is wrapped is handled in spec/components/page/session/login_component_spec.rb. + # Here I just wanted to make sure the cookie is read and the value is passed. + it 'should set last_used_csp' do + expect(response.body).to include('last-used-login-wrapper') + end + end + + describe 'handles no last_used_csp cookie set' do + it 'should not wrap a csp button' do + get sign_in_path + expect(response.body).not_to include('last-used-login-wrapper') + end + end end