Session end implementation [SCI-7027]

This commit is contained in:
Andrej 2022-08-08 12:24:50 +02:00
parent 9478779218
commit ac7c873385
9 changed files with 216 additions and 0 deletions

View file

@ -0,0 +1,68 @@
/* globals I18n */
(function() {
'use strict';
var originalTitle = '';
var expireIn;
var expireLimit = 900; // 15min
var timeoutID;
var expirationUrl = $('meta[name=\'expiration-url\']').attr('content');
var pad = function(i) {
var s = ('0' + Math.floor(i));
return s.substring(s.length - 2);
};
var newTimerStr = function(expirationTime) {
var m = (expirationTime / 60) % 60;
var s = (expirationTime % 60);
return [m, s].map(pad).join(':');
};
function getSessionEnd() {
if (expirationUrl) {
$.get(expirationUrl, function(data) {
if (data <= 0) {
$('#session-finished').modal();
} else if (data <= expireLimit + 1) {
expireIn = data;
originalTitle = document.title;
// eslint-disable-next-line no-use-before-define
timeoutID = setTimeout(expirationInTime, 1000);
} else {
timeoutID = setTimeout(getSessionEnd, (data - expireLimit) * 1000);
}
});
}
}
function expirationInTime() {
if (expireIn > 0) {
document.title = newTimerStr(expireIn) + ' ' + originalTitle;
$('.expiring').text(I18n.t('devise.sessions.expire_modal.session_end_in.header',
{ time: newTimerStr(expireIn) }));
expireIn -= 1;
if (!$('#session-expire').hasClass('in')) {
$('#session-expire').modal().off('hide.bs.modal').on('hide.bs.modal', function() {
if (expireIn > 0) {
$.post($('meta[name=\'revive-url\']').attr('content'));
document.title = originalTitle;
clearTimeout(timeoutID);
timeoutID = setTimeout(getSessionEnd, 1000);
}
});
}
timeoutID = setTimeout(expirationInTime, 1000);
} else {
document.title = originalTitle;
$('#session-expire').modal('hide');
$('#session-finished').modal();
}
}
timeoutID = setTimeout(getSessionEnd, 1000);
$(document).on('click', '.session-login', function() {
window.location.reload();
});
}());

View file

@ -0,0 +1,39 @@
// scss-lint:disable NestingDepth ImportantRule
.session-modal {
.modal-body {
padding-bottom: 0px;
}
ul {
list-style-type: none;
padding-left: 0;
li {
padding: .25em 0;
}
}
a:hover {
text-decoration: none;
}
.instruction-session-collapse {
cursor: pointer;
margin-bottom: .5em;
margin-top: 20px;
.fa-angle-up {
margin-left: .5em;
}
&.collapsed {
.fa-angle-up {
@include rotate(-180deg);
}
}
}
}

View file

@ -4,6 +4,7 @@ class Users::SessionsController < Devise::SessionsController
layout :session_layout
after_action :after_sign_in, only: %i(create authenticate_with_two_factor)
before_action :remove_authenticate_mesasge_if_root_path, only: :new
prepend_before_action :skip_timeout, only: :expire_in
rescue_from ActionController::InvalidAuthenticityToken do
redirect_to new_user_session_path
@ -34,6 +35,16 @@ class Users::SessionsController < Devise::SessionsController
generate_templates_project
end
def expire_in
if current_user.remember_created_at.nil?
render plain: Devise.timeout_in.to_i - (Time.now.to_i - user_session['last_request_at']).round
else
render plain: Devise.remember_for - (Time.now.to_i - current_user.remember_created_at.to_i).round
end
end
def revive_session; end
def two_factor_recovery
unless session[:otp_user_id]
redirect_to new_user_session_path
@ -92,6 +103,10 @@ class Users::SessionsController < Devise::SessionsController
private
def skip_timeout
request.env['devise.skip_trackable'] = true
end
def remove_authenticate_mesasge_if_root_path
if session[:user_return_to] == root_path && flash[:alert] == I18n.t('devise.failure.unauthenticated')
flash[:alert] = nil

View file

@ -7,9 +7,14 @@
<meta name="max-file-size" content="<%= Rails.configuration.x.file_max_size_mb %>">
<meta name="tiny-mce-assets-url" content="<%= tiny_mce_assets_path %>">
<meta name="highlightjs-url" content="<%= asset_path('highlightjs-github-theme.css') %>">
<% if user_signed_in? %>
<meta name="expiration-url" content="<%= users_expire_in_path %>">
<meta name="revive-url" content="<%= users_revive_session_path %>">
<% end %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= javascript_pack_tag 'application' %>
<%= javascript_include_tag 'session_end' %>
@ -57,6 +62,8 @@
<%= render "shared/file_edit_modal.html.erb" %>
<%= render "shared/marvinjs_modal.html.erb" %>
<%= render "shared/comments/comments_sidebar.html.erb" %>
<%= render "users/sessions/session_expire_modal.html.erb" %>
<%= render "users/sessions/session_end_modal.html.erb" %>
<% end %>
<% if user_signed_in? && flash[:system_notification_modal] && current_user.show_login_system_notification? %>

View file

@ -0,0 +1,39 @@
<div class="modal session-modal" id="session-finished" tabindex="-1" role="dialog"
aria-labelledby="session_expire" data-role="session_expire-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><%= t('devise.sessions.expire_modal.session_ended.header' ) %></h4>
</div>
<div class="modal-body">
<ul>
<li><%= t('devise.sessions.expire_modal.session_ended.description') %></li>
<li>
<a class="collapse-row">
<div class="row-title">
<div class="instruction-session-collapse collapsed" data-toggle="collapse" href="#InstructionsSectionFinished" aria-expanded="false">
<%= t('devise.sessions.expire_modal.why_see_this') %>
<i class="fas fa-angle-up"></i>
</div>
</div>
</a>
<ul class="collapse" id="InstructionsSectionFinished">
<li><%= t('devise.sessions.expire_modal.session_end_in.paragraph1') %></li>
<li><%= t('devise.sessions.expire_modal.session_end_in.paragraph2') %></li>
</ul>
</li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
<%= t('general.close' ) %>
</button>
<button type="button" class="btn btn-primary session-login">
<%= t('devise.sessions.new.submit' ) %>
</button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,34 @@
<div class="modal session-modal" id="session-expire" tabindex="-1" role="dialog"
aria-labelledby="session-expire" data-role="session-expire-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title expiring"></h4>
</div>
<div class="modal-body">
<ul>
<li><%= t('devise.sessions.expire_modal.session_end_in.description') %></li>
<li>
<a class="collapse-row">
<div class="row-title">
<div class="instruction-session-collapse collapsed" data-toggle="collapse" href="#InstructionsSection" aria-expanded="false">
<%= t('devise.sessions.expire_modal.why_see_this') %>
<i class="fas fa-angle-up"></i>
</div>
</div>
</a>
<ul class="collapse" id="InstructionsSection">
<li><%= t('devise.sessions.expire_modal.session_end_in.paragraph1') %></li>
<li><%= t('devise.sessions.expire_modal.session_end_in.paragraph2') %></li>
</ul>
</li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal"><%= t('devise.sessions.expire_modal.session_end_in.revive_session' ) %></button>
</div>
</div>
</div>
</div>

View file

@ -91,6 +91,7 @@ Rails.application.config.assets.precompile += %w(sidebar_toggle.js)
Rails.application.config.assets.precompile += %w(reports/reports_datatable.js)
Rails.application.config.assets.precompile += %w(reports/save_pdf_to_inventory.js)
Rails.application.config.assets.precompile += %w(reports/content.js)
Rails.application.config.assets.precompile += %w(session_end.js)
# Libraries needed for Handsontable formulas
Rails.application.config.assets.precompile += %w(jquery.js)

View file

@ -49,6 +49,17 @@ en:
not_correct_code: "Not correct recovery code"
create:
team_name: "%{user}'s projects"
expire_modal:
why_see_this: 'Why am I seeing this?'
session_end_in:
header: 'Your session will end in %{time}'
description: 'To keep the data you entered into the fields without clicking the Save button, please revive your session and save them.'
paragraph1: 'To keep your work secure, SciNote times out sessions after 3 hours of inactivity. '
paragraph2: 'To proceed working after the session ends, youll have to log in again, starting a new working session.'
revive_session: 'Revive my session'
session_ended:
header: 'To proceed working, please log in again'
description: 'If you have any unsaved data, please close this message, copy your text, and paste it back after you log in again.'
unlocks:
new:
head_title: "Resend unlock instructions"

View file

@ -703,6 +703,8 @@ Rails.application.routes.draw do
get 'users/sign_up_provider' => 'users/registrations#new_with_provider'
get 'users/two_factor_recovery' => 'users/sessions#two_factor_recovery'
get 'users/two_factor_auth' => 'users/sessions#two_factor_auth'
get 'users/expire_in' => 'users/sessions#expire_in'
post 'users/revive_session' => 'users/sessions#revive_session'
post 'users/authenticate_with_two_factor' => 'users/sessions#authenticate_with_two_factor'
post 'users/authenticate_with_recovery_code' => 'users/sessions#authenticate_with_recovery_code'
post 'users/complete_sign_up_provider' => 'users/registrations#create_with_provider'