bfc2c68cc3b08fc11f9753a8fb788e6631a8bd84
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / LoginRefresh.js
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 /**
15 * Task that periodically checks if a blocking event in the backend occurred and
16 * displays a proper dialog to the user.
17 */
18 define('TYPO3/CMS/Backend/LoginRefresh', ['jquery'], function($) {
19 var LoginRefresh = {
20 identifier: {
21 loginrefresh: 't3-modal-loginrefresh',
22 lockedModal: 't3-modal-backendlocked',
23 loginFormModal: 't3-modal-backendloginform'
24 },
25 options: {
26 modalConfig: {
27 backdrop: 'static'
28 }
29 },
30 webNotification: null,
31 intervalId: null,
32 backendIsLocked: false,
33 isTimingOut: false,
34 $timeoutModal: '',
35 $backendLockedModal: '',
36 $loginForm: ''
37 };
38
39 /**
40 * Starts the session check task (if not running already)
41 */
42 LoginRefresh.startTask = function() {
43 if (LoginRefresh.intervalId !== null) {
44 return;
45 }
46
47 // set interval to 60 seconds
48 var interval = 1000 * 60;
49 LoginRefresh.intervalId = setInterval(LoginRefresh.checkActiveSession, interval);
50 };
51
52 /**
53 * Stops the session check task
54 */
55 LoginRefresh.stopTask = function() {
56 clearInterval(LoginRefresh.intervalId);
57 LoginRefresh.intervalId = null;
58 };
59
60 /**
61 * Generates a modal dialog as template.
62 */
63 LoginRefresh.generateModal = function(identifier) {
64 return TYPO3.jQuery('<div />', {id: identifier, class: 't3-modal t3-blr-modal ' + identifier + ' modal fade'}).append(
65 $('<div />', {class: 'modal-dialog'}).append(
66 $('<div />', {class: 'modal-content'}).append(
67 $('<div />', {class: 'modal-header'}).append(
68 $('<h4 />', {class: 'modal-title'})
69 ),
70 $('<div />', {class: 'modal-body'}),
71 $('<div />', {class: 'modal-footer'})
72 )
73 )
74 );
75 };
76
77 /**
78 * Generates the modal displayed on near session time outs
79 */
80 LoginRefresh.initializeTimeoutModal = function() {
81 LoginRefresh.$timeoutModal = LoginRefresh.generateModal(LoginRefresh.identifier.loginrefresh);
82 LoginRefresh.$timeoutModal.find('.modal-header h4').text(TYPO3.LLL.core.login_about_to_expire_title);
83 LoginRefresh.$timeoutModal.find('.modal-body').append(
84 $('<p />').text(TYPO3.LLL.core.login_about_to_expire),
85 $('<div />', {class: 'progress'}).append(
86 $('<div />', {
87 class: 'progress-bar progress-bar-warning progress-bar-striped active',
88 role: 'progressbar',
89 'aria-valuemin': '0',
90 'aria-valuemax': '100'
91 }).append(
92 $('<span />', {class: 'sr-only'})
93 )
94 )
95 );
96 LoginRefresh.$timeoutModal.find('.modal-footer').append(
97 $('<button />', {class: 'btn btn-default', 'data-action': 'refreshSession'}).text(TYPO3.LLL.core.refresh_login_refresh_button).on('click', function() {
98 $.ajax({
99 url: TYPO3.settings.ajaxUrls['BackendLogin::isTimedOut'],
100 method: 'GET',
101 success: function() {
102 LoginRefresh.hideTimeoutModal();
103 }
104 });
105 }),
106 $('<button />', {class: 'btn btn-default', 'data-action': 'logout'}).text(TYPO3.LLL.core.refresh_direct_logout_button).on('click', function() {
107 top.location.href = TYPO3.configuration.siteUrl + TYPO3.configuration.TYPO3_mainDir + 'logout.php';
108 })
109 );
110
111 LoginRefresh.registerDefaultModalEvents(LoginRefresh.$timeoutModal);
112
113 $('body').append(LoginRefresh.$timeoutModal);
114 };
115
116 /**
117 * Shows the timeout dialog. If the backend is not focused, a Web Notification
118 * is displayed, too.
119 */
120 LoginRefresh.showTimeoutModal = function() {
121 LoginRefresh.isTimingOut = true;
122 LoginRefresh.$timeoutModal.modal(LoginRefresh.options.modalConfig);
123 LoginRefresh.fillProgressbar(LoginRefresh.$timeoutModal);
124
125 if (typeof Notification !== 'undefined' && Notification.permission === 'granted' && !LoginRefresh.isPageActive()) {
126 LoginRefresh.webNotification = new Notification(TYPO3.LLL.core.login_about_to_expire_title, {
127 body: TYPO3.LLL.core.login_about_to_expire,
128 icon: '/typo3/sysext/backend/Resources/Public/Images/Logo.png'
129 });
130 }
131 };
132
133 /**
134 * Hides the timeout dialog. If a Web Notification is displayed, close it too.
135 */
136 LoginRefresh.hideTimeoutModal = function() {
137 LoginRefresh.isTimingOut = false;
138 LoginRefresh.$timeoutModal.modal('hide');
139
140 if (typeof Notification !== 'undefined' && LoginRefresh.webNotification !== null) {
141 LoginRefresh.webNotification.close();
142 }
143 };
144
145 /**
146 * Generates the modal displayed if the backend is locked.
147 */
148 LoginRefresh.initializeBackendLockedModal = function() {
149 LoginRefresh.$backendLockedModal = LoginRefresh.generateModal(LoginRefresh.identifier.lockedModal);
150 LoginRefresh.$backendLockedModal.find('.modal-header h4').text(TYPO3.LLL.core.please_wait);
151 LoginRefresh.$backendLockedModal.find('.modal-body').append(
152 $('<p />').text(TYPO3.LLL.core.be_locked)
153 );
154 LoginRefresh.$backendLockedModal.find('.modal-footer').remove();
155
156 $('body').append(LoginRefresh.$backendLockedModal);
157 };
158
159 /**
160 * Shows the "backend locked" dialog.
161 */
162 LoginRefresh.showBackendLockedModal = function() {
163 LoginRefresh.$backendLockedModal.modal(LoginRefresh.options.modalConfig);
164 };
165
166 /**
167 * Hides the "backend locked" dialog.
168 */
169 LoginRefresh.hideBackendLockedModal = function() {
170 LoginRefresh.$backendLockedModal.modal('hide');
171 };
172
173 /**
174 * Generates the login form displayed if the session has timed out.
175 */
176 LoginRefresh.initializeLoginForm = function() {
177 if (TYPO3.configuration.showRefreshLoginPopup) {
178 // dialog is not required if "showRefreshLoginPopup" is enabled
179 return;
180 }
181
182 LoginRefresh.$loginForm = LoginRefresh.generateModal(LoginRefresh.identifier.loginFormModal);
183 LoginRefresh.$loginForm.find('.modal-header h4').text(TYPO3.LLL.core.refresh_login_title);
184 LoginRefresh.$loginForm.find('.modal-body').append(
185 $('<p />').text(TYPO3.LLL.core.login_expired),
186 $('<form />', {id: 'beLoginRefresh', method: 'POST', action: TYPO3.settings.ajaxUrls['BackendLogin::login']}).append(
187 $('<div />', {class: 'form-group'}).append(
188 $('<input />', {type: 'password', name: 'p_field', autofocus: 'autofocus', class: 'form-control', placeholder: TYPO3.LLL.core.refresh_login_password})
189 ),
190 $('<input />', {type: 'hidden', name: 'username', value: TYPO3.configuration.username}),
191 $('<input />', {type: 'hidden', name: 'userident'}),
192 $('<input />', {type: 'hidden', name: 'challenge'})
193 )
194 );
195 LoginRefresh.$loginForm.find('.modal-footer').append(
196 $('<button />', {type: 'submit', form: 'beLoginRefresh', class: 'btn btn-default', 'data-action': 'refreshSession'}).text(TYPO3.LLL.core.refresh_login_button),
197 $('<button />', {class: 'btn btn-default', 'data-action': 'logout'}).text(TYPO3.LLL.core.refresh_direct_logout_button).on('click', function() {
198 top.location.href = TYPO3.configuration.siteUrl + TYPO3.configuration.TYPO3_mainDir + 'logout.php';
199 })
200 );
201
202 LoginRefresh.registerDefaultModalEvents(LoginRefresh.$loginForm).on('submit', LoginRefresh.triggerSubmitForm);
203
204 $('body').append(LoginRefresh.$loginForm);
205 };
206
207 /**
208 * Shows the login form.
209 */
210 LoginRefresh.showLoginForm = function() {
211 // log off for sure
212 $.ajax({
213 url: TYPO3.settings.ajaxUrls['BackendLogin::logout'],
214 method: 'GET',
215 success: function() {
216 if (TYPO3.configuration.showRefreshLoginPopup) {
217 LoginRefresh.showLoginPopup();
218 } else {
219 LoginRefresh.$loginForm.modal(LoginRefresh.options.modalConfig);
220 }
221 },
222 failure: function() {
223 alert('something went wrong');
224 }
225 });
226 };
227
228 /**
229 * Opens the login form in a new window.
230 */
231 LoginRefresh.showLoginPopup = function() {
232 var vHWin = window.open('login_frameset.php', 'relogin_' + TYPO3.configuration.uniqueID, 'height=450,width=700,status=0,menubar=0,location=1');
233 vHWin.focus();
234 };
235
236 /**
237 * Hides the login form.
238 */
239 LoginRefresh.hideLoginForm = function() {
240 LoginRefresh.$loginForm.modal('hide');
241 };
242
243 /**
244 * Fills the progressbar attached to the given modal.
245 */
246 LoginRefresh.fillProgressbar = function($activeModal) {
247 if (!LoginRefresh.isTimingOut) {
248 return;
249 }
250
251 var max = 100,
252 current = 0,
253 $progressBar = $activeModal.find('.progress-bar'),
254 $srText = $progressBar.children('.sr-only');
255
256 var progress = setInterval(function() {
257 var isOverdue = (current >= max);
258
259 if (!LoginRefresh.isTimingOut || isOverdue) {
260 clearInterval(progress);
261
262 if (isOverdue) {
263 // show login form
264 LoginRefresh.hideTimeoutModal();
265 LoginRefresh.showLoginForm();
266 }
267
268 // reset current
269 current = 0;
270 } else {
271 current += 1;
272 }
273
274 var percentText = (current) + '%';
275 $progressBar.css('width', percentText);
276 $srText.text(percentText);
277 }, 300);
278 };
279
280 /**
281 * Triggers the form submit based on the security level.
282 */
283 LoginRefresh.triggerSubmitForm = function(e) {
284 e.preventDefault();
285
286 switch (TYPO3.configuration.securityLevel) {
287 case 'superchallenged':
288 case 'challenged':
289 $.ajax({
290 url: TYPO3.settings.ajaxUrls['BackendLogin::getChallenge'],
291 method: 'GET',
292 data: {
293 skipSessionUpdate: 1
294 },
295 success: function(response) {
296 if (response.challenge) {
297 LoginRefresh.$loginForm.find('input[name=challenge]').val(response.challenge);
298 LoginRefresh.submitForm();
299 }
300 }
301 });
302 break;
303 case 'rsa':
304 $.ajax({
305 url: TYPO3.settings.ajaxUrls['BackendLogin::getRsaPublicKey'],
306 method: 'GET',
307 data: {
308 skipSessionUpdate: 1
309 },
310 success: function(response) {
311 if (response.publicKeyModulus && response.exponent) {
312 LoginRefresh.submitForm(response);
313 }
314 }
315 });
316 break;
317 default:
318 LoginRefresh.submitForm();
319 }
320 };
321
322 /**
323 * Creates additional data based on the security level and "submits" the form
324 * via an AJAX request.
325 */
326 LoginRefresh.submitForm = function(parameters) {
327 var $form = LoginRefresh.$loginForm.find('form'),
328 $usernameField = $form.find('input[name=username]'),
329 $passwordField = $form.find('input[name=p_field]'),
330 $challengeField = $form.find('input[name=challenge]'),
331 $useridentField = $form.find('input[name=userident]'),
332 passwordFieldValue = $passwordField.val();
333
334 if (passwordFieldValue === '') {
335 top.TYPO3.Flashmessage.display(TYPO3.Severity.error, TYPO3.LLL.core.refresh_login_failed, TYPO3.LLL.core.refresh_login_emptyPassword);
336 $passwordField.focus();
337 return;
338 }
339
340 if (TYPO3.configuration.securityLevel === 'superchallenged') {
341 $passwordField.val(MD5(passwordFieldValue));
342 }
343
344 if (TYPO3.configuration.securityLevel === 'superchallenged' || TYPO3.configuration.securityLevel === 'challenged') {
345 $challengeField.val(parameters.challenge);
346 $useridentField.val(MD5($usernameField.val() + ':' + passwordFieldValue + ':' + parameters.challenge));
347 } else if (TYPO3.configuration.securityLevel === 'rsa') {
348 var rsa = new RSAKey();
349 rsa.setPublic(parameters.publicKeyModulus, parameters.exponent);
350 var encryptedPassword = rsa.encrypt(passwordFieldValue);
351 $useridentField.val('rsa:' + hex2b64(encryptedPassword));
352 } else {
353 $useridentField.val(passwordFieldValue);
354 }
355 $passwordField.val('');
356
357 var postData = {
358 login_status: 'login'
359 };
360 $.each($form.serializeArray(), function(i, field) {
361 postData[field.name] = field.value;
362 });
363 $.ajax({
364 url: $form.attr('action'),
365 method: 'POST',
366 data: postData,
367 success: function(response) {
368 var result = response.login;
369 if (result.success) {
370 // User is logged in
371 LoginRefresh.hideLoginForm();
372 } else {
373 // TODO: add failure to notification system instead of alert
374 top.TYPO3.Flashmessage.display(TYPO3.Severity.error, TYPO3.LLL.core.refresh_login_failed, TYPO3.LLL.core.refresh_login_failed_message);
375 $passwordField.focus();
376 }
377 }
378 });
379 };
380
381 /**
382 * Registers the (shown|hidden).bs.modal events.
383 * If a modal is shown, the interval check is stopped. If the modal hides,
384 * the interval check starts again.
385 * This method is not invoked for the backend locked modal, because we still
386 * need to check if the backend gets unlocked again.
387 */
388 LoginRefresh.registerDefaultModalEvents = function($modal) {
389 $modal.on('hidden.bs.modal', function() {
390 LoginRefresh.startTask();
391 }).on('shown.bs.modal', function() {
392 LoginRefresh.stopTask();
393 });
394
395 return $modal;
396 };
397
398 /**
399 * Checks if the user is in focus of the backend.
400 * Thanks to http://stackoverflow.com/a/19519701
401 */
402 LoginRefresh.isPageActive = function() {
403 var stateKey, eventKey, keys = {
404 hidden: 'visibilitychange',
405 webkitHidden: 'webkitvisibilitychange',
406 mozHidden: 'mozvisibilitychange',
407 msHidden: 'msvisibilitychange'
408 };
409
410 for (stateKey in keys) {
411 if (stateKey in document) {
412 eventKey = keys[stateKey];
413 break;
414 }
415 }
416 return function(c) {
417 if (c) {
418 document.addEventListener(eventKey, c);
419 }
420 return !document[stateKey];
421 }();
422 };
423
424 /**
425 * Periodically called task that checks if
426 *
427 * - the user's backend session is about to expire
428 * - the user's backend session has expired
429 * - the backend got locked
430 *
431 * and opens a dialog.
432 */
433 LoginRefresh.checkActiveSession = function() {
434 $.ajax({
435 url: TYPO3.settings.ajaxUrls['BackendLogin::isTimedOut'],
436 data: {
437 skipSessionUpdate: 1
438 },
439 success: function(response) {
440 if (response.login.locked) {
441 if (!LoginRefresh.backendIsLocked) {
442 LoginRefresh.backendIsLocked = true;
443 LoginRefresh.showBackendLockedModal();
444 }
445 } else {
446 if (LoginRefresh.backendIsLocked) {
447 LoginRefresh.backendIsLocked = false;
448 LoginRefresh.hideBackendLockedModal();
449 }
450 }
451
452 if (!LoginRefresh.backendIsLocked) {
453 if (response.login.timed_out || response.login.will_time_out) {
454 if (response.login.timed_out) {
455 LoginRefresh.showLoginForm();
456 } else {
457 LoginRefresh.showTimeoutModal();
458 }
459 }
460 }
461 }
462 });
463 };
464
465 // initialize and return the LoginRefresh object
466 return function() {
467 $(document).ready(function() {
468 LoginRefresh.initializeTimeoutModal();
469 LoginRefresh.initializeBackendLockedModal();
470 LoginRefresh.initializeLoginForm();
471
472 LoginRefresh.startTask();
473
474 if (typeof Notification !== 'undefined' && Notification.permission !== 'granted') {
475 Notification.requestPermission();
476 }
477 });
478
479 TYPO3.LoginRefresh = LoginRefresh;
480 return LoginRefresh;
481 }();
482 });