Logout automático com Angularjs com base em usuário inativo

É possível determinar se um usuário está inativo e desconectá-lo automaticamente depois de 10 minutos de inatividade usando o angularjs?

Eu estava tentando evitar o uso do jQuery, mas não consigo encontrar nenhum tutorial ou artigo sobre como fazer isso no angularjs. Qualquer ajuda seria apreciada.

Eu escrevi um módulo chamado Ng-Idle que pode ser útil para você nesta situação. Aqui está a página que contém instruções e uma demonstração.

Basicamente, ele tem um serviço que inicia um cronômetro para sua duração inativa que pode ser interrompida pela atividade do usuário (events, como clicar, rolar, digitar). Você também pode interromper manualmente o tempo limite chamando um método no serviço. Se o tempo de espera não for interrompido, ele fará uma contagem regressiva onde você poderá alertar o usuário de que ele será desconectado. Se eles não responderem após a contagem regressiva do aviso atingir 0, um evento será transmitido para o qual seu aplicativo pode responder. No seu caso, poderia emitir um pedido para matar sua session e redirect para uma página de login.

Além disso, ele possui um serviço keep-alive que pode pingar alguma URL em um intervalo. Isso pode ser usado pelo seu aplicativo para manter a session do usuário ativa enquanto ela estiver ativa. O serviço ocioso, por padrão, integra-se ao serviço keep-alive, suspendendo o ping se ficar ocioso e retomando-o quando retornarem.

Todas as informações que você precisa para começar estão no site com mais detalhes no wiki . No entanto, aqui está um snippet de configuração mostrando como assiná-los quando eles terminam.

 angular.module('demo', ['ngIdle']) // omitted for brevity .config(function(IdleProvider, KeepaliveProvider) { IdleProvider.idle(10*60); // 10 minutes idle IdleProvider.timeout(30); // after 30 seconds idle, time the user out KeepaliveProvider.interval(5*60); // 5 minute keep-alive ping }) .run(function($rootScope) { $rootScope.$on('IdleTimeout', function() { // end their session and redirect to login }); }); 

Veja a demonstração que está usando angularjs e veja o log do seu navegador

          

Abaixo do código é pura versão javascript

       

ATUALIZAR

Atualizei minha solução como @Tom sugestão.

          

Clique aqui para ver no Plunker para a versão atualizada

Deve haver maneiras diferentes de fazê-lo e cada abordagem deve se adequar a um aplicativo específico melhor que outro. Para a maioria dos aplicativos, você pode simplesmente manipular events de chave ou mouse e ativar / desativar um timer de logout de forma adequada. Dito isso, no topo da minha cabeça, uma solução “extravagante” do AngularJS-y está monitorando o loop digest, se nenhum foi acionado pela última [duração especificada], então faça o logout. Algo assim.

 app.run(function($rootScope) { var lastDigestRun = new Date(); $rootScope.$watch(function detectIdle() { var now = new Date(); if (now - lastDigestRun > 10*60*60) { // logout here, like delete cookie, navigate to login ... } lastDigestRun = now; }); }); 

Jogado com a abordagem Boo, no entanto, não gosto do fato de que o usuário foi expulso apenas uma vez que outro resumo é executado, o que significa que o usuário permanece conectado até que ele tente fazer algo dentro da página e, em seguida, imediatamente expulso.

Eu estou tentando forçar o logoff usando o intervalo que verifica cada minuto se o último tempo de ação foi mais de 30 minutos atrás. Eu conectei-o em $ routeChangeStart, mas também podia ser ligado em $ rootScope. $ Watch como no exemplo de Boo.

 app.run(function($rootScope, $location, $interval) { var lastDigestRun = Date.now(); var idleCheck = $interval(function() { var now = Date.now(); if (now - lastDigestRun > 30*60*1000) { // logout } }, 60*1000); $rootScope.$on('$routeChangeStart', function(evt) { lastDigestRun = Date.now(); }); }); 

Você também pode realizar o uso angular-activity-monitor uma maneira mais direta do que injetar vários provedores e usar setInterval() (vs. $interval do angular) para evitar o acionamento manual de um ciclo digest (o que é importante para evitar a manutenção intencional de itens) ).

Por fim, você acabou de assinar alguns events que determinam quando um usuário está inativo ou se aproximando. Portanto, se você quiser fazer logoff de um usuário após 10 minutos de inatividade, poderá usar o seguinte snippet:

 angular.module('myModule', ['ActivityMonitor']); MyController.$inject = ['ActivityMonitor']; function MyController(ActivityMonitor) { // how long (in seconds) until user is considered inactive ActivityMonitor.options.inactive = 600; ActivityMonitor.on('inactive', function() { // user is considered inactive, logout etc. }); ActivityMonitor.on('keepAlive', function() { // items to keep alive in the background while user is active }); ActivityMonitor.on('warning', function() { // alert user when they're nearing inactivity }); } 

Eu experimentei a abordagem de Buu e não consegui acertar devido ao grande número de events que triggersm o digester para executar, incluindo $ interval e $ timeout executando funções. Isso deixa o aplicativo em um estado em que ele nunca fica ocioso, independentemente da input do usuário.

Se você realmente precisa rastrear o tempo ocioso do usuário, não tenho certeza se existe uma boa abordagem angular. Gostaria de sugerir que uma melhor abordagem é representada por Witoldz aqui https://github.com/witoldsz/angular-http-auth . Essa abordagem solicitará que o usuário reautentique quando uma ação for executada, exigindo suas credenciais. Depois que o usuário autenticar a solicitação anterior, ela é reprocessada e o aplicativo continua como se nada tivesse acontecido.

Isso lida com a preocupação que você pode ter de deixar a session do usuário expirar enquanto ela estiver ativa, pois mesmo que a autenticação expire, ela ainda poderá manter o estado do aplicativo e não perder nenhum trabalho.

Se você tiver algum tipo de session no seu cliente (cookies, tokens, etc), poderá assisti-los também e acionar seu processo de logout se eles expirarem.

 app.run(['$interval', function($interval) { $interval(function() { if (/* session still exists */) { } else { // log out of client } }, 1000); }]); 

ATUALIZAÇÃO: Aqui está um pedaço que demonstra a preocupação. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W . O que isso demonstra é que o tempo de execução do digestor é atualizado apenas quando o intervalo é interrompido. Quando o intervalo atingir a contagem máxima, o digestor não será mais executado.

ng-Idle parece o caminho a percorrer, mas eu não conseguia descobrir as modificações de Brian F e queria tempo limite para uma session de dormir também, também eu tinha um caso de uso bastante simples em mente. Eu reduzi o código abaixo. Ele conecta events para redefinir um sinalizador de tempo limite (colocado preguiçosamente em $ rootScope). Ele detecta apenas o tempo limite que ocorreu quando o usuário retorna (e aciona um evento), mas isso é bom o suficiente para mim. Não consegui obter a localização $ do angular para trabalhar aqui, mas novamente, usando o document.location.href conclui o trabalho.

Eu coloquei isso no meu app.js depois que o .config foi executado.

 app.run(function($rootScope,$document) { var d = new Date(); var n = d.getTime(); //n in ms $rootScope.idleEndTime = n+(20*60*1000); //set end time to 20 min from now $document.find('body').on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkAndResetIdle); //monitor events function checkAndResetIdle() //user did something { var d = new Date(); var n = d.getTime(); //n in ms if (n>$rootScope.idleEndTime) { $document.find('body').off('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart'); //un-monitor events //$location.search('IntendedURL',$location.absUrl()).path('/login'); //terminate by sending to login page document.location.href = 'https://whatever.com/myapp/#/login'; alert('Session ended due to inactivity'); } else { $rootScope.idleEndTime = n+(20*60*1000); //reset end time } } }); 

Eu gostaria de expandir as respostas para quem quer que esteja usando isso em um projeto maior, você poderia acidentalmente append vários manipuladores de events e o programa se comportaria de maneira estranha.

Para me livrar disso, usei uma function singleton exposta por uma fábrica, da qual você chamaria inactivityTimeoutFactory.switchTimeoutOn() e inactivityTimeoutFactory.switchTimeoutOff() em seu aplicativo angular para ativar e desativar o logout, respectivamente, devido à funcionalidade de inatividade.

Dessa forma, você verifica se está executando apenas uma única instância dos manipuladores de events, não importa quantas vezes tente ativar o procedimento de tempo limite, facilitando o uso em aplicativos nos quais o usuário pode efetuar login de rotas diferentes.

Aqui está o meu código:

 'use strict'; angular.module('YOURMODULENAME') .factory('inactivityTimeoutFactory', inactivityTimeoutFactory); inactivityTimeoutFactory.$inject = ['$document', '$timeout', '$state']; function inactivityTimeoutFactory($document, $timeout, $state) { function InactivityTimeout () { // singleton if (InactivityTimeout.prototype._singletonInstance) { return InactivityTimeout.prototype._singletonInstance; } InactivityTimeout.prototype._singletonInstance = this; // Timeout timer value const timeToLogoutMs = 15*1000*60; //15 minutes const timeToWarnMs = 13*1000*60; //13 minutes // variables let warningTimer; let timeoutTimer; let isRunning; function switchOn () { if (!isRunning) { switchEventHandlers("on"); startTimeout(); isRunning = true; } } function switchOff() { switchEventHandlers("off"); cancelTimersAndCloseMessages(); isRunning = false; } function resetTimeout() { cancelTimersAndCloseMessages(); // reset timeout threads startTimeout(); } function cancelTimersAndCloseMessages () { // stop any pending timeout $timeout.cancel(timeoutTimer); $timeout.cancel(warningTimer); // remember to close any messages } function startTimeout () { warningTimer = $timeout(processWarning, timeToWarnMs); timeoutTimer = $timeout(processLogout, timeToLogoutMs); } function processWarning() { // show warning using popup modules, toasters etc... } function processLogout() { // go to logout page. The state might differ from project to project $state.go('authentication.logout'); } function switchEventHandlers(toNewStatus) { const body = angular.element($document); const trackedEventsList = [ 'keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus' ]; trackedEventsList.forEach((eventName) => { if (toNewStatus === 'off') { body.off(eventName, resetTimeout); } else if (toNewStatus === 'on') { body.on(eventName, resetTimeout); } }); } // expose switch methods this.switchOff = switchOff; this.switchOn = switchOn; } return { switchTimeoutOn () { (new InactivityTimeout()).switchOn(); }, switchTimeoutOff () { (new InactivityTimeout()).switchOff(); } }; } 

Eu acho que o ciclo digestivo de Buu é genial. Obrigado por compartilhar. Como outros observaram, o $ interval também faz com que o ciclo de digitação seja executado. Poderíamos, com o propósito de registrar automaticamente o usuário, usar setInterval que não causará um loop digest.

 app.run(function($rootScope) { var lastDigestRun = new Date(); setInterval(function () { var now = Date.now(); if (now - lastDigestRun > 10 * 60 * 1000) { //logout } }, 60 * 1000); $rootScope.$watch(function() { lastDigestRun = new Date(); }); }); 

Eu usei ng-idle para isso e adicionei um pouco de logout e token código nulo e está funcionando bem, você pode tentar isso. Obrigado @HackedByChinese por ter feito um módulo tão bom.

No IdleTimeout , acabei de excluir meus dados e token da session.

Aqui está meu código

 $scope.$on('IdleTimeout', function () { closeModals(); delete $window.sessionStorage.token; $state.go("login"); $scope.timedout = $uibModal.open({ templateUrl: 'timedout-dialog.html', windowClass: 'modal-danger' }); });