-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #115 from GSA/34/timeout-warning
[34] Timeout Warning
- Loading branch information
Showing
12 changed files
with
255 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
ruby 3.2.4 | ||
ruby 3.2.4 | ||
nodejs 20.15.1 | ||
yarn 1.22.22 | ||
yarn 1.22.22 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { formatMilliseconds } from "./time_helpers"; | ||
|
||
document.addEventListener("DOMContentLoaded", function () { | ||
var sessionStartTime = Date.now(); | ||
|
||
const sessionTimeoutMinutes = window.appConfig.sessionTimeoutMinutes; | ||
const warningTimeoutMinutes = 2; | ||
const sessionTimeoutMs = sessionTimeoutMinutes * 60 * 1000; | ||
const warningTimeoutMs = | ||
(sessionTimeoutMinutes - warningTimeoutMinutes) * 60 * 1000; | ||
// Debug overrides | ||
// const sessionTimeoutMs = 10000; | ||
// const warningTimeoutMs = 5000; | ||
|
||
const renewalModal = document.getElementById("renew-modal"); | ||
const renewalModalOpenButton = document.getElementById( | ||
"renew-modal-open-button" | ||
); | ||
const renewalModalCloseButton = document.getElementById( | ||
"renew-modal-close-button" | ||
); | ||
const countdownDiv = document.querySelector("#renew-modal .countdown"); | ||
countdownDiv.textContent = formatMilliseconds(warningTimeoutMs); | ||
|
||
const activityRenewalInterval = 1000; | ||
var doRenewSession = false; | ||
var ignoreNextActivity = false; | ||
|
||
const showTimeoutWarning = () => { | ||
ignoreNextActivity = true; | ||
renewalModalOpenButton.click(); | ||
ignoreNextActivity = false; | ||
}; | ||
|
||
const updateCountdown = () => { | ||
var timeRemaining = sessionTimeoutMs - (Date.now() - sessionStartTime); | ||
countdownDiv.textContent = formatMilliseconds(timeRemaining); | ||
}; | ||
|
||
const logoutSession = () => { | ||
fetch("/sessions/timeout", { | ||
method: "DELETE", | ||
headers: { | ||
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]') | ||
.content, | ||
"Content-Type": "application/json", | ||
}, | ||
}).then((response) => { | ||
if (response.ok) { | ||
window.location.href = "/"; | ||
} | ||
}); | ||
}; | ||
|
||
const handleUserActivity = (event) => { | ||
// Don't count the modal showing and hiding as user activity | ||
if (ignoreNextActivity) { | ||
return; | ||
} | ||
|
||
ignoreNextActivity = true; | ||
renewalModalCloseButton.click(); | ||
ignoreNextActivity = false; | ||
|
||
doRenewSession = true; | ||
}; | ||
|
||
document.addEventListener("click", handleUserActivity); | ||
document.addEventListener("keydown", handleUserActivity); | ||
document.addEventListener("scroll", handleUserActivity); | ||
|
||
setInterval(() => { | ||
if (doRenewSession) { | ||
renewSession(); | ||
} | ||
}, activityRenewalInterval); | ||
|
||
document | ||
.getElementById("extend-session-button") | ||
.addEventListener("click", () => { | ||
renewSession(); | ||
}); | ||
|
||
var renewSession = () => { | ||
fetch("/sessions/renew", { | ||
method: "POST", | ||
headers: { | ||
"X-CSRF-Token": document | ||
.querySelector('meta[name="csrf-token"]') | ||
.getAttribute("content"), | ||
}, | ||
}).then((response) => { | ||
if (response.ok) { | ||
clearTimeout(timeoutWarning); | ||
clearTimeout(sessionTimeout); | ||
|
||
sessionStartTime = Date.now(); | ||
|
||
timeoutWarning = setTimeout(() => { | ||
showTimeoutWarning(); | ||
}, warningTimeoutMs); | ||
|
||
sessionTimeout = setTimeout(() => { | ||
logoutSession(); | ||
}, sessionTimeoutMs); | ||
|
||
doRenewSession = false; | ||
} | ||
}); | ||
}; | ||
|
||
var timeoutWarning = setTimeout(() => { | ||
showTimeoutWarning(); | ||
}, warningTimeoutMs); | ||
|
||
var sessionTimeout = setTimeout(() => { | ||
logoutSession(); | ||
}, sessionTimeoutMs); | ||
|
||
var countdownInterval; | ||
|
||
var startCountdown = () => { | ||
clearInterval(countdownInterval); | ||
countdownInterval = setInterval(updateCountdown, 1000); | ||
}; | ||
|
||
startCountdown(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export function formatMilliseconds(ms) { | ||
const totalSeconds = Math.round(ms / 1000); | ||
const minutes = Math.floor(totalSeconds / 60); | ||
const seconds = totalSeconds % 60; | ||
return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<a href="#renew-modal" style="display:none;" id="renew-modal-open-button" class="usa-button" aria-controls="renew-modal" data-open-modal></a> | ||
|
||
<div | ||
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 class="countdown"></span><br> | ||
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> | ||
<a href="#renew-modal" style="display:none;" id="renew-modal-close-button" class="usa-button" data-close-modal></a> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,4 +40,31 @@ | |
expect(response).to have_http_status(:redirect) | ||
expect(response).to redirect_to("/dashboard") | ||
end | ||
|
||
it "times out the session" do | ||
session_timeout_in_minutes = SessionsController::SESSION_TIMEOUT_IN_MINUTES | ||
|
||
email = "[email protected]" | ||
token = SecureRandom.uuid | ||
|
||
User.create!({ email:, token: }) | ||
|
||
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:, sub: token }] | ||
) | ||
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 |