Skip to content

Commit

Permalink
Merge branch 'main' into task/remove-api-contact
Browse files Browse the repository at this point in the history
  • Loading branch information
jzbahrai committed May 13, 2024
2 parents 3d30824 + 5fde768 commit 1eff9e5
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 7 deletions.
20 changes: 20 additions & 0 deletions app/assets/javascripts/sessionRedirect.js
@@ -0,0 +1,20 @@
/**
* Redirects the user after a specified period of time.
*/
(function () {
const REDIRECT_LOCATION = "/sign-in?timeout=true";
const SESSION_TIMEOUT_MS = 7 * 60 * 60 * 1000 + 55 * 60 * 1000; // 7 hours 55 minutes

redirectCountdown(REDIRECT_LOCATION, SESSION_TIMEOUT_MS); // 7 hours 55 minutes

/**
* Redirects to the specified location after a given period of time.
* @param {string} redirectLocation - The URL to redirect to.
* @param {number} period - The period of time (in milliseconds) before redirecting.
*/
function redirectCountdown(redirectLocation, period) {
setTimeout(function () {
window.location.href = redirectLocation;
}, period);
}
})();
1 change: 1 addition & 0 deletions app/assets/javascripts/sessionRedirect.min.js
@@ -0,0 +1 @@
setTimeout((function(){window.location.href="/sign-in?timeout=true"}),285e5);
7 changes: 6 additions & 1 deletion app/main/views/sign_in.py
@@ -1,6 +1,6 @@
from flask import abort, flash, redirect, render_template, request, session, url_for
from flask_babel import _
from flask_login import current_user
from flask_login import current_user, logout_user

from app import login_manager
from app.main import main
Expand All @@ -11,6 +11,10 @@

@main.route("/sign-in", methods=(["GET", "POST"]))
def sign_in():
if request.args.get("timeout"):
session.clear()
logout_user()

if current_user and current_user.is_authenticated:
return redirect(url_for("main.show_accounts_or_dashboard"))

Expand Down Expand Up @@ -62,6 +66,7 @@ def sign_in():
form=form,
again=bool(request.args.get("next")),
other_device=other_device,
timeout=bool(request.args.get("timeout")),
)


Expand Down
6 changes: 4 additions & 2 deletions app/main/views/sign_out.py
@@ -1,4 +1,5 @@
from flask import current_app, redirect, session, url_for
from flask import current_app, flash, redirect, session, url_for
from flask_babel import _
from flask_login import logout_user

from app import get_current_locale
Expand All @@ -12,4 +13,5 @@ def sign_out():
logout_user()
session["userlang"] = currentlang

return redirect(url_for("main.index"))
flash(_("You have been signed out."), "default_with_tick")
return redirect(url_for("main.sign_in"))
5 changes: 5 additions & 0 deletions app/templates/main_template.html
Expand Up @@ -169,6 +169,11 @@
{% block page_script %}
<script nonce="{{ request_nonce }}" type="text/javascript" src="{{ asset_url('javascripts/main.min.js') }}"></script>
<script nonce="{{ request_nonce }}" type="text/javascript" src="{{ asset_url('javascripts/all.min.js') }}"></script>

{% if current_user.is_authenticated %}
<script nonce="{{ request_nonce }}" src="{{ asset_url('javascripts/sessionRedirect.min.js') }}"></script>
{% endif %}

{% endblock %}
</body>
</html>
5 changes: 3 additions & 2 deletions app/templates/views/signin.html
Expand Up @@ -11,8 +11,7 @@

<div class="grid-row contain-floats">
<div class="md:w-2/3 float-left py-0 px-0 px-gutterHalf box-border">

{% if again %}
{% if again or timeout %}
<h1 class="heading-large">{{ _('You need to sign in again') }}</h1>
{% if other_device %}
<p>
Expand All @@ -35,8 +34,10 @@ <h1 class="heading-large">{{ _("Sign in") }}</h1>
{% call form_wrapper(autocomplete=True) %}
{{ textbox(form.email_address, width='w-2/3', autocomplete='username') }}
{{ textbox(form.password, width='w-2/3', autocomplete='current-password') }}
<p data-testid="session_timeout_info">{{ _('Your session ends after 8 hours of inactivity') }}</p>
{{ page_footer(btn, secondary_link=url_for('.forgot_password'), secondary_link_text=forgot) }}
{% endcall %}

</div>
</div>

Expand Down
2 changes: 2 additions & 0 deletions app/translations/csv/fr.csv
Expand Up @@ -1868,3 +1868,5 @@
"Alternative text in French","Texte alternatif en français"
"Enter the alternative text in English","Entrez le texte alternatif en anglais"
"Enter the alternative text in French","Entrez le texte alternatif en français"
"You have been signed out.","Déconnexion réussie"
"Your session ends after 8 hours of inactivity","Votre session se termine au bout de 8 heures d’inactivité"
1 change: 1 addition & 0 deletions gulpfile.js
Expand Up @@ -93,6 +93,7 @@ const javascripts = () => {
paths.src + "javascripts/scheduler.min.js",
paths.src + "javascripts/branding_request.min.js",
paths.src + "javascripts/formValidateRequired.min.js",
paths.src + "javascripts/sessionRedirect.min.js",
])
)
.pipe(dest(paths.dist + "javascripts/"));
Expand Down
4 changes: 2 additions & 2 deletions tests/app/main/views/test_sign_out.py
Expand Up @@ -6,7 +6,7 @@
def test_render_sign_out_redirects_to_sign_in(client):
response = client.get(url_for("main.sign_out"))
assert response.status_code == 302
assert response.location == url_for("main.index")
assert response.location == url_for("main.sign_in")


def test_sign_out_user(
Expand Down Expand Up @@ -35,7 +35,7 @@ def test_sign_out_user(
"main.sign_out",
_expected_status=302,
_expected_redirect=url_for(
"main.index",
"main.sign_in",
),
)
with client_request.session_transaction() as session:
Expand Down
46 changes: 46 additions & 0 deletions tests_cypress/cypress/e2e/admin/sign_out/sign_out.js
@@ -0,0 +1,46 @@
import { LoginPage } from "../../../Notify/Admin/Pages/AllPages";

const REDIRECT_LOCATION = '/sign-in?timeout=true';
const SESSION_TIMEOUT_MS = 7 * 60 * 60 * 1000 + 55 * 60 * 1000; // 7 hours 55 minutes
const vistPageAndFastForwardTime = (page = '/') => {
cy.clock();
cy.visit(page);
cy.tick(SESSION_TIMEOUT_MS);
};

describe('Sign out', () => {

it('Does not redirect to session timeout page when logged out', () => {
cy.clearCookie('notify_admin_session');
vistPageAndFastForwardTime();

// asserts
cy.url().should('not.include', REDIRECT_LOCATION);
});

it('Redirects to session timeout page when logged in (multiple pages)', () => {
['/home', '/features'].forEach((page) => {
LoginPage.Login(Cypress.env('NOTIFY_USER'), Cypress.env('NOTIFY_PASSWORD'));
vistPageAndFastForwardTime(page);

// asserts
cy.url().should('include', REDIRECT_LOCATION);
cy.get('h1').should('contain', 'You need to sign in again');
});
});

it('Displays banner on explicit logout', () => {
cy.visit('/sign-out');

// asserts
cy.url().should('include', '/sign-in');
cy.get('.banner-default-with-tick').should('be.visible');
});

it('Displays session timeout info on login page', () => {
cy.visit('/sign-in');

// asserts
cy.getByTestId('session_timeout_info').should('be.visible');
});
});
4 changes: 4 additions & 0 deletions tests_cypress/cypress/support/commands.js
Expand Up @@ -115,3 +115,7 @@ Cypress.Commands.add('a11yScan', (url, options={ a11y: true, htmlValidate: true,
});
}
})

Cypress.Commands.add('getByTestId', (selector, ...args) => {
return cy.get(`[data-testid=${selector}]`, ...args)
});
1 change: 1 addition & 0 deletions webpack.config.js
Expand Up @@ -10,6 +10,7 @@ module.exports = {
main: ["./app/assets/javascripts/index.js", "./app/assets/stylesheets/tailwind/style.css"],
branding_request: ["./app/assets/javascripts/branding_request.js"],
formValidateRequired: ["./app/assets/javascripts/formValidateRequired.js"],
sessionRedirect: ["./app/assets/javascripts/sessionRedirect.js"],
scheduler: {
import: './app/assets/javascripts/scheduler/scheduler.js',
library: {
Expand Down

0 comments on commit 1eff9e5

Please sign in to comment.