Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[34] Timeout Warning #115

Merged
merged 17 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ use nix
mkdir -p .nix-bundler
export BUNDLE_PATH=./.nix-bundler

export SESSION_TIMEOUT_IN_MINUTES=15
stepchud marked this conversation as resolved.
Show resolved Hide resolved

cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
# Login Env Vars
source .env_login
10 changes: 6 additions & 4 deletions app/assets/uswds/_uswds-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ in the form $setting: value,
----------------------------------------
*/

@use "uswds-core" with (
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
$theme-image-path: "images",
$theme-font-path: "fonts"
)
//
// @use "uswds-core" with (
// $setting: value,
// $setting: value
// );
//
17 changes: 17 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
class ApplicationController < ActionController::Base
helper_method :current_user, :logged_in?

before_action :check_session_expiration

def current_user
return unless session[:userinfo]

Expand All @@ -18,12 +20,27 @@ def sign_in(login_userinfo)
user = User.user_from_userinfo(login_userinfo)

@current_user = user
renew_session
session[:userinfo] = login_userinfo
end

def sign_out
@current_user = nil
session.delete(:userinfo)
session.delete(:session_timeout_at)
end

def renew_session
session[:session_timeout_at] = Time.current + ENV.fetch("SESSION_TIMEOUT_IN_MINUTES", 15).to_i.minutes
stepchud marked this conversation as resolved.
Show resolved Hide resolved
end

def check_session_expiration
if session[:session_timeout_at].present? && session[:session_timeout_at] < Time.current
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
sign_out
redirect_to dashboard_path, alert: I18n.t("session_expired_alert")
else
renew_session
end
end

def redirect_if_logged_in(path = "/dashboard")
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def result
redirect_to dashboard_path
end

def renew
renew_session
end

private

def check_error_result
Expand Down
36 changes: 36 additions & 0 deletions app/javascript/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
document.addEventListener("DOMContentLoaded", function () {
var sessionTimeoutMinutes = window.appConfig.sessionTimeoutMinutes;
var sessionTimeoutMs = sessionTimeoutMinutes * 60 * 1000;
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
var warningTimeoutMs = (sessionTimeoutMinutes - 2) * 60 * 1000;
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved

function showTimeoutWarning() {
document.getElementById("renew-modal-button").click();
}

document
.getElementById("extend-session-button")
.addEventListener("click", function () {
renewSession();
});

function renewSession() {
fetch("/sessions/renew", {
method: "POST",
headers: {
"X-CSRF-Token": document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content"),
},
}).then(function (response) {
if (response.ok) {
document.getElementById("renew-modal").style.display = "none";
clearTimeout(timeoutWarning);
timeoutWarning = setTimeout(showTimeoutWarning, warningTimeoutMs);
}
});
}

var timeoutWarning = setTimeout(() => {
showTimeoutWarning();
}, warningTimeoutMs);
});
10 changes: 10 additions & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,20 @@
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag 'uswds', async: true %>
<%= javascript_include_tag 'uswds-init', async: true %>

<script>
window.appConfig = {
sessionTimeoutMinutes: <%= ENV.fetch('SESSION_TIMEOUT_IN_MINUTES', 15).to_i.minutes %>
}
</script>
stepchud marked this conversation as resolved.
Show resolved Hide resolved
</head>

<body>
<%= render "layouts/header" %>
<%= render "shared/flash" %>

<%= yield %>

<%= render "modals/renew_session" %>
</body>
</html>
25 changes: 25 additions & 0 deletions app/views/modals/_renew_session.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<a href="#renew-modal" style="display:none;" id="renew-modal-button" class="usa-button" aria-controls="renew-modal" data-open-modal></a>

<div
stepchud marked this conversation as resolved.
Show resolved Hide resolved
class="usa-modal"
id="renew-modal"
aria-labelledby="modal-1-heading"
aria-describedby="modal-1-description"
>
<div class="usa-modal__content">
<div class="usa-modal__main">
<h2 class="usa-modal__heading" id="modal-1-heading">
Session expire
</h2>
<div class="usa-prose">
<p id="modal-1-description">
Your session will expire in <span id="countdown"></span><br>
cpreisinger marked this conversation as resolved.
Show resolved Hide resolved
Please click below if you would like to continue.
</p>
</div>
<div class="usa-modal__footer">
<button class="usa-button modal-btn" id="extend-session-button" data-close-modal type="button">Renew Session</button>
</div>
</div>
</div>
</div>
14 changes: 14 additions & 0 deletions app/views/shared/_flash.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<% flash.each do |key, value| %>
<% alert_class = case key.to_sym
when :notice then "usa-alert--success"
when :alert then "usa-alert--error"
when :error then "usa-alert--error"
else "usa-alert--info"
end %>

<div class="usa-alert <%= alert_class %> usa-alert--slim">
<div class="usa-alert__body">
<p class="usa-alert__text"><%= value %></p>
</div>
</div>
<% end %>
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ en:
please_try_again: "Please try again."
login_error: "There was an issue with logging in. Please try again."
already_logged_in_notice: "You are already logged in."
session_expired_alert: "Your session has expired. Please log in again."
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
get 'auth/result', to: 'sessions#result'
resource 'session', only: [:new, :create, :destroy]
post 'sessions/renew', to: 'sessions#renew'

get '/', to: "dashboard#index"
get '/dashboard', to: "dashboard#index"
Expand Down
22 changes: 22 additions & 0 deletions spec/requests/sessions_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,26 @@
expect(response).to have_http_status(:redirect)
expect(response).to redirect_to("/dashboard")
end

it "times out the session" do
session_timeout_in_minutes = ENV.fetch("SESSION_TIMEOUT_IN_MINUTES", 15)

code = "ABC123"
login_gov = instance_double(LoginGov)
allow(LoginGov).to receive(:new).and_return(login_gov)
allow(login_gov).to receive(:exchange_token_from_auth_result).with(code).and_return(
[{ email: "[email protected]", sub: "sub" }]
)
get "/auth/result", params: { code: }

expect(session[:userinfo]).not_to be_nil
expect(session[:session_timeout_at]).not_to be_nil

travel_to (session_timeout_in_minutes.to_i + 1).minutes.from_now do
get dashboard_path

expect(session[:userinfo]).to be_nil
expect(session[:session_timeout_at]).to be_nil
end
end
end
Loading