Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
45cb5fc
changelog: Bug Fixes, accessibility and authorization, modal has been…
kevinsmaster5 Jan 22, 2025
3fcfc01
refactor tests to account for modal changes
kevinsmaster5 Jan 23, 2025
7c33dc2
replace assumed translation key
kevinsmaster5 Jan 23, 2025
b389b8a
adds a translated string for sign-out/already signed out
kevinsmaster5 Jan 24, 2025
3e044a8
replace expected translation key in spec
kevinsmaster5 Jan 24, 2025
33b8668
back out of translation changes, revise spec expectations to capture …
kevinsmaster5 Jan 24, 2025
14f23bb
refactor session expiration TS to remove redundancy
kevinsmaster5 Jan 27, 2025
020b8da
fix regression for logged-in user timeout
kevinsmaster5 Jan 27, 2025
7a46929
move login page to its own ts file. reduce constant hammering at sess…
kevinsmaster5 Jan 29, 2025
f1a4e84
include a clearInterval when hiding the modal
kevinsmaster5 Jan 30, 2025
2e31f38
revise spec for changes to FE script
kevinsmaster5 Jan 30, 2025
d79d051
delete redundant test
kevinsmaster5 Jan 31, 2025
d5813d5
branch tests into create and sign in paths
kevinsmaster5 Jan 31, 2025
78cc1ae
add click cancel to sign in test
kevinsmaster5 Jan 31, 2025
f85d3d6
remove unneeded attributes and close up refresh and session extend in…
kevinsmaster5 Feb 4, 2025
c0c266a
clean up date object, fix overly redundant test setup
kevinsmaster5 Feb 4, 2025
f966b05
change to spec timeout_in
kevinsmaster5 Feb 5, 2025
3754685
translated text for email field finder
kevinsmaster5 Feb 6, 2025
1a4f116
provide calculated value for timeout
kevinsmaster5 Feb 6, 2025
a95ce99
remove static expected timeout value
kevinsmaster5 Feb 6, 2025
55c355a
provide session timeout and remove possibly flaky test element
kevinsmaster5 Feb 6, 2025
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
42 changes: 36 additions & 6 deletions app/javascript/packs/session-expire-session.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
const expireConfig = document.getElementById('js-expire-session');
import { extendSession } from '@18f/identity-session';
import type { CountdownElement } from '@18f/identity-countdown/countdown-element';
import type { ModalElement } from '@18f/identity-modal';

if (expireConfig && expireConfig.dataset.sessionTimeoutIn) {
const sessionTimeoutIn = parseInt(expireConfig.dataset.sessionTimeoutIn, 10) * 1000;
const timeoutRefreshPath = expireConfig.dataset.timeoutRefreshPath || '';
const warningEl = document.getElementById('session-timeout-cntnr')!;

setTimeout(() => {
const warning = Number(warningEl.dataset.warning!) * 1000;
const sessionsURL = warningEl.dataset.sessionsUrl!;
const sessionTimeout = Number(warningEl.dataset.sessionTimeoutIn!) * 1000;
const modal = document.querySelector<ModalElement>('lg-modal.session-timeout-modal')!;
const keepaliveEl = document.getElementById('session-keepalive-btn');
const countdownEls: NodeListOf<CountdownElement> = modal.querySelectorAll('lg-countdown');
const timeoutRefreshPath = warningEl.dataset.timeoutRefreshPath || '';

let sessionExpiration = new Date(Date.now() + sessionTimeout);

function showModal() {
modal.show();
countdownEls.forEach((countdownEl) => {
countdownEl.expiration = sessionExpiration;
countdownEl.start();
});
}

function keepalive() {
const isExpired = new Date() > sessionExpiration;
if (isExpired) {
document.location.href = timeoutRefreshPath;
}, sessionTimeoutIn);
} else {
modal.hide();
sessionExpiration = new Date(Date.now() + sessionTimeout);

setTimeout(showModal, sessionTimeout - warning);
countdownEls.forEach((countdownEl) => countdownEl.stop());
extendSession(sessionsURL);
}
}

keepaliveEl?.addEventListener('click', keepalive, false);
setTimeout(showModal, sessionTimeout - warning);
2 changes: 2 additions & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
<%= render partial: 'session_timeout/expire_session',
locals: {
session_timeout_in: Devise.timeout_in,
warning: session_timeout_warning,
modal: session_modal,
} %>
<% end %>

Expand Down
7 changes: 4 additions & 3 deletions app/views/session_timeout/_expire_session.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<%= tag.div id: 'js-expire-session',
<%= tag.div id: 'session-timeout-cntnr',
data: {
session_timeout_in: session_timeout_in,
timeout_refresh_path: timeout_refresh_path,
warning: warning,
sessions_url: api_internal_sessions_path,
} %>

<%= render(partial: 'session_timeout/warning', locals: { modal_presenter: modal }) %>
<%= javascript_packs_tag_once 'session-expire-session', preload_links_header: false %>
72 changes: 44 additions & 28 deletions spec/features/users/sign_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@
allow(IdentityConfig.store).to receive(:session_check_delay).and_return(0)
allow(IdentityConfig.store).to receive(:session_timeout_warning_seconds)
.and_return(Devise.timeout_in)
allow(Devise).to receive(:timeout_in)
.and_return(IdentityConfig.store.session_timeout_warning_seconds + 1)

user = create(:user, :fully_registered)
sign_in_user(user)
Expand All @@ -252,22 +254,6 @@
end
end

context 'signed out' do
it 'refreshes the current page after session expires', js: true do
allow(Devise).to receive(:timeout_in).and_return(1)

visit sign_up_email_path(request_id: '123abc')
fill_in t('forms.registration.labels.email'), with: 'test@example.com'

expect(page).to have_content(
t('notices.session_cleared', minutes: IdentityConfig.store.session_timeout_in_minutes),
wait: 5,
)
expect(page).to have_field(t('forms.registration.labels.email'), with: '')
expect(current_url).to match Regexp.escape(sign_up_email_path(request_id: '123abc'))
end
end

context 'signing back in after session timeout length' do
around do |example|
with_forgery_protection { example.run }
Expand Down Expand Up @@ -296,19 +282,49 @@
end
end

it 'refreshes the page (which clears the form) and notifies the user', js: true do
allow(Devise).to receive(:timeout_in).and_return(1)
user = create(:user)
visit root_path
fill_in t('account.index.email'), with: user.email
fill_in 'Password', with: user.password
context 'create account' do
it 'shows the timeout modal when the session expiration approaches', js: true do
allow(Devise).to receive(:timeout_in)
.and_return(IdentityConfig.store.session_timeout_warning_seconds + 1)

expect(page).to have_content(
t('notices.session_cleared', minutes: IdentityConfig.store.session_timeout_in_minutes),
wait: 5,
)
expect(find_field('Email').value).to be_blank
expect(find_field('Password').value).to be_blank
visit sign_up_email_path
fill_in t('forms.registration.labels.email'), with: 'test@example.com'

expect(page).to have_css('.usa-js-modal--active', wait: 10)

click_button t('notices.timeout_warning.partially_signed_in.continue')

expect(page).not_to have_css('.usa-js-modal--active')
expect(find_field(t('forms.registration.labels.email')).value).not_to be_blank
end
end

context 'sign in' do
it 'shows the timeout modal when the session expiration approaches', js: true do
allow(Devise).to receive(:timeout_in)
.and_return(IdentityConfig.store.session_timeout_warning_seconds + 1)

visit root_path
fill_in t('account.index.email'), with: 'test@example.com'

expect(page).to have_css('.usa-js-modal--active', wait: 10)

click_button t('notices.timeout_warning.partially_signed_in.continue')
expect(find_field(t('account.index.email')).value).not_to be_blank
end

it 'reloads the sign in page when cancel is clicked', js: true do
allow(Devise).to receive(:timeout_in)
.and_return(IdentityConfig.store.session_timeout_warning_seconds + 1)

visit root_path
fill_in t('account.index.email'), with: 'test@example.com'

expect(page).to have_css('.usa-js-modal--active', wait: 10)

click_button t('notices.timeout_warning.partially_signed_in.sign_out')
expect(find_field(t('account.index.email')).value).to be_blank
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/views/layouts/application.html.erb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
).create_session,
)
allow(view.request).to receive(:original_fullpath).and_return('/foobar')
allow(view).to receive(:user_fully_authenticated?).and_return(false)
view.title = title_content if title_content
end

Expand Down Expand Up @@ -123,7 +124,6 @@

context 'session expiration' do
it 'renders a javascript page refresh' do
allow(view).to receive(:user_fully_authenticated?).and_return(false)
allow(view).to receive(:current_user).and_return(false)
allow(view).to receive(:decorated_sp_session).and_return(NullServiceProviderSession.new)
render
Expand Down