Skip to content

Commit

Permalink
ENH Display toast notifications, remove coupling to RememberLoginHash…
Browse files Browse the repository at this point in the history
….logout_across_devices
  • Loading branch information
emteknetnz committed Mar 15, 2021
1 parent 477293b commit b660a0e
Show file tree
Hide file tree
Showing 9 changed files with 1,169 additions and 122 deletions.
13 changes: 0 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,6 @@ It is also compatible with the [Silverstripe MFA module suite](https://github.co

## Configuration

### Logout across devices

This module respects the `SilverStripe\Security\RememberLoginHash.logout_across_devices` config setting, which defaults to `true`. This means that the default behaviour is to revoke _all_ a user’s sessions when they log out.

To change this so that logging out will only revoke the session for that one device, use the following config setting:

```yml
SilverStripe\Security\RememberLoginHash:
logout_across_devices: false
```
**Important:** do not set this value to false if users do not have access to the CMS (or a custom UI where they can revoke sessions). Doing so would make it impossible to a user to revoke a session if they suspect their device has been compromised.
### Session timeout

Non-persisted login sessions (those where the user hasn’t ticked “remember me”) should expire after a period of inactivity, so that they’re removed from the list of active sessions even if the user closes their browser without completing the “log out” action. The length of time before expiry matches the `SilverStripe\Control\Session.timeout` value if one is set, otherwise falling back to a default of one hour. This default can be changed via the following config setting:
Expand Down
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/dist/js/bundle.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/dist/styles/bundle.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions client/src/components/LoginSession/LoginSession.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* global window */
import React, { useState } from 'react';
import confirm from '@silverstripe/reactstrap-confirm';
import { success, error } from 'state/toasts/ToastsActions';
import Config from 'lib/Config'; // eslint-disable-line
import backend from 'lib/Backend';
import Injector from 'lib/Injector';
import moment from 'moment';
import i18n from 'i18n';
import PropTypes from 'prop-types';
Expand Down Expand Up @@ -38,16 +40,23 @@ function LoginSession(props) {
id: props.ID,
SecurityID: Config.get('SecurityID')
})
.then(response => response.json())
.then(output => {
setLoading({ complete: true, failed: !!output.error, submitting: false });
.then(response => {
const failed = !response.success;
setLoading({ complete: true, failed, submitting: false });
// Display toast notification
const { dispatch } = Injector.reducer.store;
if (failed) {
dispatch(error(response.message));
} else {
dispatch(success(response.message));
}
})
.catch(() => {
setLoading({ complete: true, failed: true, submitting: false });
});
}

if (loading.complete || loading.submitting) {
if ((loading.complete || loading.submitting) && !loading.failed) {
return null;
}

Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@
"lint-sass": "sass-lint client/src"
},
"dependencies": {
"@silverstripe/reactstrap-confirm": "^0.0.4",
"babel-polyfill": "6.7.4",
"moment": "^2.24.0",
"prop-types": "^15.7.2",
"react": "^16.8.3",
"react-dom": "^17.0.1",
"@silverstripe/reactstrap-confirm": "^0.0.4",
"moment": "^2.24.0"
"react-dom": "^17.0.1"
},
"devDependencies": {
"@silverstripe/eslint-config": "^0.0.5",
"@silverstripe/webpack-config": "^1.0.0",
"@silverstripe/webpack-config": "^1.7.0",
"babel-jest": "^23.6.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.10.0",
Expand Down
66 changes: 38 additions & 28 deletions src/Control/LoginSessionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@

namespace SilverStripe\SessionManager\Control;

use Exception;
use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\ValidationException;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\Security\SecurityToken;
use SilverStripe\SessionManager\Model\LoginSession;

Expand All @@ -23,11 +20,11 @@ class LoginSessionController extends LeftAndMain
private static $ignore_menuitem = true;

private static $url_handlers = [
'DELETE remove/$ID' => 'removeLoginSession',
'DELETE remove/$ID' => 'remove',
];

private static $allowed_actions = [
'removeLoginSession',
'remove',
];

/**
Expand All @@ -36,36 +33,49 @@ class LoginSessionController extends LeftAndMain
* @param HTTPRequest $request
* @return HTTPResponse
*/
public function removeLoginSession(HTTPRequest $request): HTTPResponse
public function remove(HTTPRequest $request): HTTPResponse
{
// Ensure CSRF protection
if (!SecurityToken::inst()->checkRequest($request)) {
return $this->jsonResponse(
['errors' => 'Request timed out, please try again'],
400
);
}
return $this->removeLoginSession($request);
}

$id = $request->param('ID');
$loginSession = LoginSession::get()->byID($id);
if (!$loginSession) {
return $this->jsonResponse(
['errors' => 'Something went wrong.'],
400
);
}
private function removeLoginSession(HTTPRequest $request): HTTPResponse
{
try {
// Ensure CSRF protection
if (!SecurityToken::inst()->checkRequest($request)) {
return $this->jsonResponse([
'success' => false,
'message' => 'Request timed out, please try again'
]);
}

$id = $request->param('ID');
$loginSession = LoginSession::get()->byID($id);
if (!$loginSession) {
return $this->jsonResponse([
'success' => false,
'message' => 'Something went wrong.'
]);
}

if (!$loginSession->canDelete()) {
return $this->jsonResponse(
['errors' => 'You do not have permission to delete this record.'],
400
);
if (!$loginSession->canDelete()) {
return $this->jsonResponse([
'success' => false,
'message' => 'You do not have permission to delete this record.'
]);
}
} catch (Exception $e) {
return $this->jsonResponse([
'success' => false,
'message' => 'Something went wrong.'
]);
}

$loginSession->delete();

return $this->jsonResponse([
'success' => true,
'message' => 'Device session revoked' // TODO _t() everything here
]);
}

Expand All @@ -76,7 +86,7 @@ public function removeLoginSession(HTTPRequest $request): HTTPResponse
* @param int $code The HTTP response code to set on the response
* @return HTTPResponse
*/
protected function jsonResponse(array $response, int $code = 200): HTTPResponse
private function jsonResponse(array $response, int $code = 200): HTTPResponse
{
return HTTPResponse::create(json_encode($response))
->addHeader('Content-Type', 'application/json')
Expand Down
14 changes: 4 additions & 10 deletions src/Security/LogOutAuthenticationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,10 @@ public function logOut(HTTPRequest $request = null)
$loginHandler = Injector::inst()->get(LogInAuthenticationHandler::class);
$member = Security::getCurrentUser();

if (RememberLoginHash::config()->get('logout_across_devices')) {
foreach ($member->LoginSessions() as $session) {
$session->delete();
}
} else {
$loginSessionID = $request->getSession()->get($loginHandler->getSessionVariable());
$loginSession = LoginSession::get()->byID($loginSessionID);
if ($loginSession && $loginSession->canDelete($member)) {
$loginSession->delete();
}
$loginSessionID = $request->getSession()->get($loginHandler->getSessionVariable());
$loginSession = LoginSession::get()->byID($loginSessionID);
if ($loginSession && $loginSession->canDelete($member)) {
$loginSession->delete();
}

$request->getSession()->clear($loginHandler->getSessionVariable());
Expand Down
Loading

0 comments on commit b660a0e

Please sign in to comment.