Skip to content

Commit

Permalink
Consolidated login (#2547)
Browse files Browse the repository at this point in the history
* Beginning of a consolidated login flow

* Replace Login dropdown with a simple button
* Introduce interstitial page that captures just the user email
* Based on the email, render either the partner or bank login page,
  with email pre-filled
* Introduce a new system spec to cover this feature

The Users::LookupsController does not derive from Devise, mostly because
i couldn't get that to work. Instead it introduces some helper methods
that essentially duck type it to look like a devise controller. This
allows it to directly render devise templates instead of redirecting to
their respective controller.

TODO:
-[ ] doesn't yet handle the case of email not found in either DB
-[ ] needs a system spec scenario for the case of both bank and partner
-[ ] figure out whether/how to distinguish the interstitial from the
     other login pages
-[ ] if login is attempted immediately after logout, the logout flash
     message lingers and shows up unexpectedly on the bank/partner login
     page

* Add spec scenario for consolidated login with both bank/partner accounts

Co-authored-by: patmccler <[email protected]>

* Rename Users::LookupsController -> ConsolidatedLoginsController

Sits at /logins/new, slightly breaking convention by not matching the
controller name. Worth the compromise IMO to have both a meaningful path
and a controller name that helps differentiate it from
*::SessionsController.

* Handle non-existent email in consolidated login flow

* Drop unnecessary, innaccurate render_ prefix from ...Director methods

* Extract devise layout duplication into _devise_shared partial

Sets us up to easily introduce a third devise_consolidated_login
layout without having to duplicate the whole layout again.

* Introduce separate layout for consolidated login pages

* Add system spec for unregistered email on consolidated login

Co-authored-by: patmccler <[email protected]>
  • Loading branch information
solebared and patmccler authored Oct 15, 2021
1 parent 4b04595 commit 25aad90
Show file tree
Hide file tree
Showing 16 changed files with 446 additions and 197 deletions.
8 changes: 8 additions & 0 deletions app/assets/stylesheets/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@
}
}

.login-page--user {
background: linear-gradient(135deg, #3023AE, #C86DD7)
}

.login-page--consolidated {
background: linear-gradient(315deg, #8BC6EC 0%, #9599E2 100%);
}

.login-page--partner {
background-color: grey;
}
Expand Down
59 changes: 59 additions & 0 deletions app/controllers/consolidated_logins_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
class ConsolidatedLoginsController < ApplicationController
skip_before_action :authorize_user
skip_before_action :authenticate_user!

def new
render director.render, layout: director.layout
end

# TODO: flash message from logout is showing up residually on bank/partner login pages
def create
director.lookup params.require(:user).permit(:email, :organization)
render director.render, layout: director.layout
end

private

def director
@director ||= ConsolidatedLoginDirector.new
end

# The methods below essentially ducktype this controller so that it looks
# like a devise controller to devise-y templates, such as shared/links

def resource
@director
end

def resource_name
@director.resource_name
end

def devise_mapping
@devise_mapping ||= DeviseMappingShunt.new
end

helper_method :resource, :resource_name, :devise_mapping

class DeviseMappingShunt
def registerable?
true
end

def recoverable?
true
end

def confirmable?
false
end

def lockable?
false
end

def omniauthable?
false
end
end
end
1 change: 1 addition & 0 deletions app/models/partners/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module Partners
class User < Base
self.table_name = "users"

# If you change any of these options, adjust ConsolidatedLoginsController::DeviseMappingShunt accordingly
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable,
:invitable, :trackable, :password_has_required_content

Expand Down
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
# :invitable is from the devise_invitable gem
# If you change any of these options, adjust ConsolidatedLoginsController::DeviseMappingShunt accordingly
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:timeoutable, :password_has_required_content
Expand Down
74 changes: 74 additions & 0 deletions app/services/consolidated_login_director.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
class ConsolidatedLoginDirector
include ActiveModel::Validations

attr_reader :email, :organization, :organizations, :render, :layout, :resource_name

def initialize
@render = :new
@layout = "devise_consolidated_login"
@resource_name = "user"
end

def lookup(params)
email, selection = params.values_at("email", "organization")

@user = User.find_by(email: email)
@partner_user = Partners::User.find_by(email: email)

selected_login(selection) ||
options ||
bank_login ||
partner_login ||
email_not_found(email)
end

private

def selected_login(selection)
case selection
when "Bank" then bank_login
when "Partner" then partner_login
else false
end
end

def options
if @user && @partner_user
@organization = "Bank"
@organizations = [
[@user.organization.name, "Bank"],
[@partner_user.partner.name, "Partner"]
]

@email = @user.email
@resource_name = "user"
@render = :new
@layout = "devise_consolidated_login"
end
end

def bank_login
if @user
@email = @user.email
@resource_name = "user"
@render = "users/sessions/new"
@layout = "devise"
end
end

def partner_login
if @partner_user
@email = @partner_user.email
@resource_name = "partner_user"
@render = "partner_users/sessions/new"
@layout = "devise_partner_users"
end
end

def email_not_found(email)
fail if @user || @partner_user # this method shouldn't be called if either are present

errors.add :email, "not found"
@email = email
end
end
30 changes: 30 additions & 0 deletions app/views/consolidated_logins/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<%= simple_form_for(resource, as: resource_name, url: logins_path) do |f| %>
<div class="login-box">
<div class="card">
<div class="card-body login-card-body">
<p class="login-box-msg">Let's get started!</p>
<div class="form-inputs">
<%= f.input :email, autofocus: true %>
</div>
<% if resource.organization %>
<div class="form-inputs">
<%= f.input :organization, collection: resource.organizations, include_blank: false %>
</div>
<% end %>
<div class="col-12 text-center">
<%= f.button :submit, "Continue", class: "btn btn-primary btn-block" %>
</div>
</div>
<br>
<div class="row">
<div class="col-12 text-center">
<p class="mb-1">
<%= link_to "Sign up", new_account_request_path %><br />
</p>
</div>
</div>
</div>
</div>
<% end %>


89 changes: 89 additions & 0 deletions app/views/layouts/_devise_shared.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<%# required params: title, body_class, masthead_img_src %>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title><%= title %></title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 -->
<%= csrf_meta_tags %>
<%= javascript_include_tag 'application' %>
<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
<style>
.checkbox label{
padding-left: 0px;
}
</style>
<script>
$(document).ready(function() {
<% if Rails.env.staging? %>
// Prevents users from closing the modal by clicking outside of it or pressing the esc key
$('#warningModal').modal({
backdrop: 'static',
keyboard: false
});
// Adds click event handler on the checkbox
$('#defaultCheck1').click(function(){
// If the checkbox is checked, enable the continue button
if($(this).is(':checked')){
$('.continue-btn').attr("disabled", false);
} else{
$('.continue-btn').attr("disabled", true);
}
});
<% end %>
});
</script>
</head>

<body id="devise" class="hold-transition login-page <%= body_class %> <%= Rails.env.staging? ? 'login-page-test' : '' %>">
<div class="login-box">
<% if masthead_img_src %>
<div class="login-logo text-center">
<img id="MastheadLogo" src=<%= masthead_img_src %> alt="Human Essentials" title="Human Essentials" class="serv_icon"/>
</div>
<% end %>

<!-- /.login-logo -->
<div class="login-box-body">
<div class="form-group has-feedback">
<%= yield %>
</div>
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->

<!-- Modal -->
<div class="modal fade" id="warningModal" tabindex="-1" role="dialog" aria-labelledby="warningModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="warningModalLabel"><b>This site is for TEST purposes only!</b></h3>
</div>
<div class="modal-body">
You're visiting diaperbase.org, a demo/test site for the full site at <a href="https://diaper.app">diaper.app</a>.<br>
It is not safe to upload, enter or save any sensitive data here.<br>
<div class="modal-body-warning-text">
If you meant to login to your live account, go to <a href="https://diaper.app/users/sign_in">diaper.app</a>
</div>
<br>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="defaultCheck1">
<label class="form-check-label" for="defaultCheck1">
I Understand
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning continue-btn" data-dismiss="modal" disabled>Continue To The Test Site</button>
</div>
</div>
</div>
</div>
</body>
</html>
92 changes: 6 additions & 86 deletions app/views/layouts/devise.html.erb
Original file line number Diff line number Diff line change
@@ -1,86 +1,6 @@
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Human Essentials | Bank | Log in</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 -->
<%= csrf_meta_tags %>
<%= javascript_include_tag 'application' %>
<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
<style>
.checkbox label{
padding-left: 0px;
}
</style>
<script>
$(document).ready(function() {
<% if Rails.env.staging? %>
// Prevents users from closing the modal by clicking outside of it or pressing the esc key
$('#warningModal').modal({
backdrop: 'static',
keyboard: false
});
// Adds click event handler on the checkbox
$('#defaultCheck1').click(function(){
// If the checkbox is checked, enable the continue button
if($(this).is(':checked')){
$('.continue-btn').attr("disabled", false);
} else{
$('.continue-btn').attr("disabled", true);
}
});
<% end %>
});
</script>
</head>

<body id="devise" style="background: linear-gradient(135deg, #3023AE, #C86DD7)" class="hold-transition login-page <%= Rails.env.staging? ? 'login-page-test' : '' %>">
<div class="login-box">
<div class="login-logo text-center">
<img id="MastheadLogo" src="/img/essentials.svg" alt="Human Essentials" title="Human Essentials" class="serv_icon"/>
</div>

<!-- /.login-logo -->
<div class="login-box-body">
<div class="form-group has-feedback">
<%= yield %>
</div>
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->

<!-- Modal -->
<div class="modal fade" id="warningModal" tabindex="-1" role="dialog" aria-labelledby="warningModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title" id="warningModalLabel"><b>This site is for TEST purposes only!</b></h3>
</div>
<div class="modal-body">
You're visiting diaperbase.org, a demo/test site for the full site at <a href="https://diaper.app">diaper.app</a>.<br>
It is not safe to upload, enter or save any sensitive data here.<br>
<div class="modal-body-warning-text">
If you meant to login to your live account, go to <a href="https://diaper.app/users/sign_in">diaper.app</a>
</div>
<br>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="defaultCheck1">
<label class="form-check-label" for="defaultCheck1">
I Understand
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning continue-btn" data-dismiss="modal" disabled>Continue To The Test Site</button>
</div>
</div>
</div>
</div>
</body>
</html>
<%= render(
'layouts/devise_shared',
title: 'Human Essentials | Bank | Log in',
body_class: 'login-page--user',
masthead_img_src: "/img/essentials.svg"
)%>
6 changes: 6 additions & 0 deletions app/views/layouts/devise_consolidated_login.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= render(
'layouts/devise_shared',
title: 'Human Essentials | Log in',
body_class: 'login-page--consolidated',
masthead_img_src: nil
)%>
Loading

0 comments on commit 25aad90

Please sign in to comment.