[BUGFIX] Update tslint.json and fix build
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Private / TypeScript / LoginRefresh.ts
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 import * as $ from 'jquery';
15 import Typo3Notification = require('TYPO3/CMS/Backend/Notification');
16
17 enum MarkupIdentifiers {
18 loginrefresh = 't3js-modal-loginrefresh',
19 lockedModal = 't3js-modal-backendlocked',
20 loginFormModal = 't3js-modal-backendloginform',
21 }
22
23 // hack is required, because the Notification definition is wrong
24 declare var Notification: any;
25
26 /**
27 * Module: TYPO3/CMS/Backend/LoginRefresh
28 * @exports TYPO3/CMS/Backend/LoginRefresh
29 */
30 class LoginRefresh {
31 private options: any = {
32 modalConfig: {
33 backdrop: 'static',
34 },
35 };
36 private webNotification: Notification = null;
37 private intervalTime: number = 60;
38 private intervalId: number = null;
39 private backendIsLocked: boolean = false;
40 private isTimingOut: boolean = false;
41 private $timeoutModal: JQuery = null;
42 private $backendLockedModal: JQuery = null;
43 private $loginForm: JQuery = null;
44 private loginFramesetUrl: string = '';
45 private logoutUrl: string = '';
46
47 /**
48 * Initialize login refresh
49 */
50 public initialize(): void {
51 this.initializeTimeoutModal();
52 this.initializeBackendLockedModal();
53 this.initializeLoginForm();
54
55 this.startTask();
56
57 if (document.location.protocol === 'https:' && typeof Notification !== 'undefined' && Notification.permission !== 'granted') {
58 Notification.requestPermission();
59 }
60 }
61
62 /**
63 * Start the task
64 */
65 public startTask(): void {
66 if (this.intervalId !== null) {
67 return;
68 }
69 // set interval to 60 seconds
70 let interval: Number = this.intervalTime * 1000;
71 this.intervalId = setInterval(this.checkActiveSession, interval);
72 }
73
74 /**
75 * Stop the task
76 */
77 public stopTask(): void {
78 clearInterval(this.intervalId);
79 this.intervalId = null;
80 }
81
82 /**
83 * Set interval time
84 *
85 * @param {number} intervalTime
86 */
87 public setIntervalTime(intervalTime: number): void {
88 // To avoid the integer overflow in setInterval, we limit the interval time to be one request per day
89 this.intervalTime = Math.min(intervalTime, 86400);
90 }
91
92 /**
93 * Set the logout URL
94 *
95 * @param {string} logoutUrl
96 */
97 public setLogoutUrl(logoutUrl: string): void {
98 this.logoutUrl = logoutUrl;
99 }
100
101 /**
102 * Set login frameset url
103 */
104 public setLoginFramesetUrl(loginFramesetUrl: string): void {
105 this.loginFramesetUrl = loginFramesetUrl;
106 }
107
108 /**
109 * Shows the timeout dialog. If the backend is not focused, a Web Notification
110 * is displayed, too.
111 */
112 public showTimeoutModal(): void {
113 this.isTimingOut = true;
114 this.$timeoutModal.modal(this.options.modalConfig);
115 this.fillProgressbar(this.$timeoutModal);
116
117 if (document.location.protocol === 'https:' && typeof Notification !== 'undefined'
118 && Notification.permission === 'granted' && document.hidden) {
119 this.webNotification = new Notification(TYPO3.lang['mess.login_about_to_expire_title'], {
120 body: TYPO3.lang['mess.login_about_to_expire'],
121 icon: '/typo3/sysext/backend/Resources/Public/Images/Logo.png',
122 });
123 this.webNotification.onclick = () => {
124 window.focus();
125 };
126 }
127 }
128
129 /**
130 * Hides the timeout dialog. If a Web Notification is displayed, close it too.
131 */
132 public hideTimeoutModal(): void {
133 this.isTimingOut = false;
134 this.$timeoutModal.modal('hide');
135
136 if (typeof Notification !== 'undefined' && this.webNotification !== null) {
137 this.webNotification.close();
138 }
139 }
140
141 /**
142 * Shows the "backend locked" dialog.
143 */
144 public showBackendLockedModal(): void {
145 this.$backendLockedModal.modal(this.options.modalConfig);
146 }
147
148 /**
149 * Hides the "backend locked" dialog.
150 */
151 public hideBackendLockedModal(): void {
152 this.$backendLockedModal.modal('hide');
153 }
154
155 /**
156 * Shows the login form.
157 */
158 public showLoginForm(): void {
159 // log off for sure
160 $.ajax({
161 url: TYPO3.settings.ajaxUrls.logout,
162 method: 'GET',
163 success: () => {
164 TYPO3.configuration.showRefreshLoginPopup
165 ? this.showLoginPopup()
166 : this.$loginForm.modal(this.options.modalConfig);
167 },
168 });
169 }
170
171 /**
172 * Opens the login form in a new window.
173 */
174 public showLoginPopup(): void {
175 const vHWin = window.open(
176 this.loginFramesetUrl,
177 'relogin_' + Math.random().toString(16).slice(2),
178 'height=450,width=700,status=0,menubar=0,location=1',
179 );
180 if (vHWin) {
181 vHWin.focus();
182 }
183 }
184
185 /**
186 * Hides the login form.
187 */
188 public hideLoginForm(): void {
189 this.$loginForm.modal('hide');
190 }
191
192 /**
193 * Generates the modal displayed if the backend is locked.
194 */
195 protected initializeBackendLockedModal(): void {
196 this.$backendLockedModal = this.generateModal(MarkupIdentifiers.lockedModal);
197 this.$backendLockedModal.find('.modal-header h4').text(TYPO3.lang['mess.please_wait']);
198 this.$backendLockedModal.find('.modal-body').append(
199 $('<p />').text(TYPO3.lang['mess.be_locked']),
200 );
201 this.$backendLockedModal.find('.modal-footer').remove();
202
203 $('body').append(this.$backendLockedModal);
204 }
205
206 /**
207 * Generates the modal displayed on near session time outs
208 */
209 protected initializeTimeoutModal(): void {
210 this.$timeoutModal = this.generateModal(MarkupIdentifiers.loginrefresh);
211 this.$timeoutModal.addClass('modal-severity-notice');
212 this.$timeoutModal.find('.modal-header h4').text(TYPO3.lang['mess.login_about_to_expire_title']);
213 this.$timeoutModal.find('.modal-body').append(
214 $('<p />').text(TYPO3.lang['mess.login_about_to_expire']),
215 $('<div />', {class: 'progress'}).append(
216 $('<div />', {
217 class: 'progress-bar progress-bar-warning progress-bar-striped active',
218 role: 'progressbar',
219 'aria-valuemin': '0',
220 'aria-valuemax': '100',
221 }).append(
222 $('<span />', {class: 'sr-only'}),
223 ),
224 ),
225 );
226 this.$timeoutModal.find('.modal-footer').append(
227 $('<button />', {
228 class: 'btn btn-default',
229 'data-action': 'logout',
230 }).text(TYPO3.lang['mess.refresh_login_logout_button']).on('click', () => {
231 top.location.href = this.logoutUrl;
232 }),
233 $('<button />', {
234 class: 'btn btn-primary t3js-active',
235 'data-action': 'refreshSession',
236 }).text(TYPO3.lang['mess.refresh_login_refresh_button']).on('click', () => {
237 $.ajax({
238 url: TYPO3.settings.ajaxUrls.login_timedout,
239 method: 'GET',
240 success: () => {
241 this.hideTimeoutModal();
242 },
243 });
244 }),
245 );
246 this.registerDefaultModalEvents(this.$timeoutModal);
247
248 $('body').append(this.$timeoutModal);
249 }
250
251 /**
252 * Generates the login form displayed if the session has timed out.
253 */
254 protected initializeLoginForm(): void {
255 if (TYPO3.configuration.showRefreshLoginPopup) {
256 // dialog is not required if "showRefreshLoginPopup" is enabled
257 return;
258 }
259
260 this.$loginForm = this.generateModal(MarkupIdentifiers.loginFormModal);
261 this.$loginForm.addClass('modal-notice');
262 let refresh_login_title = String(TYPO3.lang['mess.refresh_login_title']).replace('%s', TYPO3.configuration.username);
263 this.$loginForm.find('.modal-header h4').text(refresh_login_title);
264 this.$loginForm.find('.modal-body').append(
265 $('<p />').text(TYPO3.lang['mess.login_expired']),
266 $('<form />', {
267 id: 'beLoginRefresh',
268 method: 'POST',
269 action: TYPO3.settings.ajaxUrls.login,
270 }).append(
271 $('<div />', {class: 'form-group'}).append(
272 $('<input />', {
273 type: 'password',
274 name: 'p_field',
275 autofocus: 'autofocus',
276 class: 'form-control',
277 placeholder: TYPO3.lang['mess.refresh_login_password'],
278 'data-rsa-encryption': 't3-loginrefres-userident',
279 }),
280 ),
281 $('<input />', {type: 'hidden', name: 'username', value: TYPO3.configuration.username}),
282 $('<input />', {type: 'hidden', name: 'userident', id: 't3-loginrefres-userident'}),
283 ),
284 );
285 this.$loginForm.find('.modal-footer').append(
286 $('<a />', {
287 href: this.logoutUrl,
288 class: 'btn btn-default',
289 }).text(TYPO3.lang['mess.refresh_exit_button']),
290 $('<button />', {type: 'button', class: 'btn btn-primary', 'data-action': 'refreshSession'})
291 .text(TYPO3.lang['mess.refresh_login_button'])
292 .on('click', () => {
293 this.$loginForm.find('form').submit();
294 }),
295 );
296 this.registerDefaultModalEvents(this.$loginForm).on('submit', this.submitForm);
297 $('body').append(this.$loginForm);
298 if (require.specified('TYPO3/CMS/Rsaauth/RsaEncryptionModule')) {
299 require(['TYPO3/CMS/Rsaauth/RsaEncryptionModule'], function(RsaEncryption: any): void {
300 RsaEncryption.registerForm($('#beLoginRefresh').get(0));
301 });
302 }
303 }
304
305 /**
306 * Generates a modal dialog as template.
307 *
308 * @param {string} identifier
309 * @returns {JQuery}
310 */
311 protected generateModal(identifier: string): JQuery {
312 return $('<div />', {
313 id: identifier,
314 class: 't3js-modal ' + identifier + ' modal modal-type-default modal-severity-notice modal-style-light modal-size-small fade',
315 }).append(
316 $('<div />', {class: 'modal-dialog'}).append(
317 $('<div />', {class: 'modal-content'}).append(
318 $('<div />', {class: 'modal-header'}).append(
319 $('<h4 />', {class: 'modal-title'}),
320 ),
321 $('<div />', {class: 'modal-body'}),
322 $('<div />', {class: 'modal-footer'}),
323 ),
324 ),
325 );
326 }
327
328 /**
329 * Fills the progressbar attached to the given modal.
330 */
331 protected fillProgressbar($activeModal: JQuery): void {
332 if (!this.isTimingOut) {
333 return;
334 }
335
336 const max = 100;
337 let current = 0;
338 const $progressBar = $activeModal.find('.progress-bar');
339 const $srText = $progressBar.children('.sr-only');
340
341 const progress = setInterval(() => {
342 const isOverdue = (current >= max);
343 if (!this.isTimingOut || isOverdue) {
344 clearInterval(progress);
345
346 if (isOverdue) {
347 // show login form
348 this.hideTimeoutModal();
349 this.showLoginForm();
350 }
351
352 // reset current
353 current = 0;
354 } else {
355 current += 1;
356 }
357
358 const percentText = (current) + '%';
359 $progressBar.css('width', percentText);
360 $srText.text(percentText);
361 }, 300);
362 }
363
364 /**
365 * Creates additional data based on the security level and "submits" the form
366 * via an AJAX request.
367 *
368 * @param {JQueryEventObject} event
369 */
370 protected submitForm = (event: JQueryEventObject): void => {
371 event.preventDefault();
372
373 const $form = this.$loginForm.find('form');
374 const $passwordField = $form.find('input[name=p_field]');
375 const $useridentField = $form.find('input[name=userident]');
376 const passwordFieldValue = $passwordField.val();
377
378 if (passwordFieldValue === '' && $useridentField.val() === '') {
379 Typo3Notification.error(TYPO3.lang['mess.refresh_login_failed'], TYPO3.lang['mess.refresh_login_emptyPassword']);
380 $passwordField.focus();
381 return;
382 }
383
384 if (passwordFieldValue) {
385 $useridentField.val(passwordFieldValue);
386 $passwordField.val('');
387 }
388
389 const postData: any = {
390 login_status: 'login',
391 };
392 $.each($form.serializeArray(), function(i: number, field: any): void {
393 postData[field.name] = field.value;
394 });
395 $.ajax({
396 url: $form.attr('action'),
397 method: 'POST',
398 data: postData,
399 success: (response) => {
400 if (response.login.success) {
401 // User is logged in
402 this.hideLoginForm();
403 } else {
404 Typo3Notification.error(TYPO3.lang['mess.refresh_login_failed'], TYPO3.lang['mess.refresh_login_failed_message']);
405 $passwordField.focus();
406 }
407 },
408 });
409 }
410
411 /**
412 * Registers the (shown|hidden).bs.modal events.
413 * If a modal is shown, the interval check is stopped. If the modal hides,
414 * the interval check starts again.
415 * This method is not invoked for the backend locked modal, because we still
416 * need to check if the backend gets unlocked again.
417 *
418 * @param {JQuery} $modal
419 * @returns {JQuery}
420 */
421 protected registerDefaultModalEvents($modal: JQuery): JQuery {
422 $modal.on('hidden.bs.modal', () => {
423 this.startTask();
424 }).on('shown.bs.modal', () => {
425 this.stopTask();
426 // focus the button which was configured as active button
427 this.$timeoutModal.find('.modal-footer .t3js-active').first().focus();
428 });
429 return $modal;
430 }
431
432 /**
433 * Periodically called task that checks if
434 *
435 * - the user's backend session is about to expire
436 * - the user's backend session has expired
437 * - the backend got locked
438 *
439 * and opens a dialog.
440 */
441 protected checkActiveSession = (): void => {
442 $.getJSON(TYPO3.settings.ajaxUrls.login_timedout, [], (response) => {
443 if (response.login.locked) {
444 if (!this.backendIsLocked) {
445 this.backendIsLocked = true;
446 this.showBackendLockedModal();
447 }
448 } else {
449 if (this.backendIsLocked) {
450 this.backendIsLocked = false;
451 this.hideBackendLockedModal();
452 }
453 }
454
455 if (!this.backendIsLocked) {
456 if (response.login.timed_out || response.login.will_time_out) {
457 response.login.timed_out
458 ? this.showLoginForm()
459 : this.showTimeoutModal();
460 }
461 }
462 });
463 }
464 }
465
466 let loginRefreshObject;
467 try {
468 // fetch from opening window
469 if (window.opener && window.opener.TYPO3 && window.opener.TYPO3.LoginRefresh) {
470 loginRefreshObject = window.opener.TYPO3.LoginRefresh;
471 }
472
473 // fetch from parent
474 if (parent && parent.window.TYPO3 && parent.window.TYPO3.LoginRefresh) {
475 loginRefreshObject = parent.window.TYPO3.LoginRefresh;
476 }
477
478 // fetch object from outer frame
479 if (top && top.TYPO3 && top.TYPO3.LoginRefresh) {
480 loginRefreshObject = top.TYPO3.LoginRefresh;
481 }
482 } catch (e) {
483 // This only happens if the opener, parent or top is some other url (eg a local file)
484 // which loaded the current window. Then the browser's cross domain policy jumps in
485 // and raises an exception.
486 // For this case we are safe and we can create our global object below.
487 }
488
489 if (!loginRefreshObject) {
490 loginRefreshObject = new LoginRefresh();
491
492 // attach to global frame
493 if (typeof TYPO3 !== 'undefined') {
494 TYPO3.LoginRefresh = loginRefreshObject;
495 }
496 }
497
498 export = loginRefreshObject;