Skip to content

Commit

Permalink
Feature/eui 1883 ui timeout dev (#503)
Browse files Browse the repository at this point in the history
* Adding Idle Service and ManageSessionServices from xui-common-lib

* Stopped passing in showPermissions and reinviteButton into xuilib-user-details as they do not exist in the underlying component anymore.

* Modal

* Config needs to change dependent on User.

* Working through.

* Working on the node layer.

* Working through setting the idle time dependent on the User.

* Working through user timeout supplied by node layer to ui

* User role check

* Making the session timeouts configurable.

* Working through reactive session timeout modal dependent on User Session from Node layer.

* Working through

* Cleaning up the UI and choosing a time metric.

* Refactor unit tests with properties more appropriate for the redone UI layer.

* Sort user roles so that we can have a prioritise Session Timeout Configuration list.

* Helpful messages if someone does not setup the configuration correctly

* SessionTimeout comes from configuration.

* Copying over changes from app.component.ts on ui-timeout branch.

* Get rid of duplicates

* Cleaning up unit tests, adding missing properties after merge.

* Cleaning comments

* Update Readme.

* Working through getting timeout working.

* When Modal timeout expires the User is now logged out correctly.

* Fixing tests and setting default idle time

* Removing comments

* Regenerated yarn lock file.

* Regenerated lock file after merge from develop branch
  • Loading branch information
ficklephil authored Jun 1, 2020
1 parent 5b9d0c4 commit e819bf8
Show file tree
Hide file tree
Showing 28 changed files with 16,471 additions and 31 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,34 @@ are set within the values.yaml and there should be NO REFERENCE to them within a

The application pulls out the secrets directly using `propertiesVolume.addTo()`

END2
# Session Timeout

The applications Session Timeouts are set via configuration and can be overridden, please @see default.json
and @see .env.defaults.

Example configuration:
```javascript
SESSION_TIMEOUTS=[{"idleModalDisplayTime": 6, "pattern":"pui-", "totalIdleTime": 55},{"idleModalDisplayTime": 3, "pattern":"caseworker-", "totalIdleTime": 30}, {"idleModalDisplayTime": 6, "pattern":".", "totalIdleTime": 60}]
```

Note that the wildcard Reg Ex '.' pattern seen in the following sets the applications default.
```javascript
{"idleModalDisplayTime": 6, "pattern":".", "totalIdleTime": 60
```
Each Session Timeout object accepts a Reg Ex pattern, which sets the Session Timeout for that User group.
Jargon used:
Session Timeout Modal - The modal popup that appears BEFORE the users Total Idle Time is over.
Total Idle Time - The Users total idle time, this includes time in which we show the Session Timeout Modal to the User.
Idle Modal Display Time - The time we display the Session Timeout Modal for.
Session Timeout Configuration - An array that contains the Applications and User Groups session timeout times.
Session Timeout - An object that contains the Idle Modal Display Time, Reg Ex pattern so that we use
the correct Session Timeout for the application / and or User Groups and Total Idle Time.
END
3 changes: 3 additions & 0 deletions api/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ NODE_CONFIG_DIR=../config
ALLOW_CONFIG_MUTATIONS=1
NODE_CONFIG_ENV=development

# Use the following to demo Adjustable Session Timeouts
# SESSION_TIMEOUTS=[{"idleModalDisplayTime": 1, "pattern":"pui-", "totalIdleTime": 2},{"idleModalDisplayTime": 3, "pattern":"caseworker-", "totalIdleTime": 30}, {"idleModalDisplayTime": 6, "pattern":".", "totalIdleTime": 60}]

# PROXY
ROARR_LOG=false # enable this for more verbose logging when going through proxy
MO_HTTP_PROXY=http://172.16.0.7:8080
Expand Down
2 changes: 1 addition & 1 deletion api/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ async function sessionChainCheck(req: Request, res: Response, accessToken: strin
} catch (e) {
console.log(e)
}

req.session.auth = {
email: userDetails.data.email,
orgId: orgIdResponse.data.id,
Expand Down
2 changes: 2 additions & 0 deletions api/configuration/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ export const LINKS_MANAGE_ORG_LINK = 'links.manageOrgLink'
export const REDISCLOUD_URL = 'secrets.rpx.mo-webapp-redis-connection-string'
export const REDIS_TTL = 'redis.ttl'
export const REDIS_KEY_PREFIX = 'redis.prefix'

export const SESSION_TIMEOUTS = 'sessionTimeouts'
18 changes: 14 additions & 4 deletions api/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@ import * as log4jui from '../lib/log4jui'
const logger = log4jui.getLogger('auth')
export const router = express.Router({ mergeParams: true })
import { UserProfileModel } from './user'
import { getUserSessionTimeout } from './userTimeout';
import { getConfigValue } from '../configuration'
import { SESSION_TIMEOUTS } from '../configuration/references'

router.get('/details', handleUserRoute)

function handleUserRoute(req, res) {

const { email, orgId, roles, userId } = req.session.auth

const sessionTimeouts = getConfigValue(SESSION_TIMEOUTS)
const sessionTimeout = getUserSessionTimeout(roles, sessionTimeouts)

const UserDetails: UserProfileModel = {
email: req.session.auth.email,
orgId: req.session.auth.orgId,
roles: req.session.auth.roles,
userId: req.session.auth.userId
email,
orgId,
roles,
sessionTimeout,
userId,
}

try {
const payload = JSON.stringify(UserDetails);
console.log(payload)
Expand Down
7 changes: 4 additions & 3 deletions api/user/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ export class UserModel {
}
}
export class UserProfileModel {
orgId: string;
orgId: string
userId: string
email: string;
roles: string[];
email: string
roles: string[]
sessionTimeout: object
constructor(prop) {
Object.assign(this, prop);
}
Expand Down
264 changes: 264 additions & 0 deletions api/user/userTimeout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import {expect} from 'chai'
import {anyRolesMatch, DEFAULT_SESSION_TIMEOUT, getUserSessionTimeout, isRoleMatch, sortUserRoles} from './userTimeout'

describe('userTimeout', () => {

/**
* Note that I deceided to use a Regular Expression matcher here so that we can set the default session timeout via configuration ie. '.',
* as we require a different default timeout per application, and require this to be easily configurable.
*/
describe('isRoleMatch()', () => {

it('should return true if there is a match of the User\'s role to the Session Timeout regex pattern so' +
'that the App knows that we need to have a specified Session Timeout for that user role.', () => {
expect(isRoleMatch('pui-case-manager', 'case-')).to.be.true
})

it('should return true if there is a partial match of the User\'s role to the Session Timeout regex pattern.', () => {
expect(isRoleMatch('pui-case-manager', 'pui')).to.be.true
})

it('should return false if there is no match of the User\'s role to the Session Timeout regex pattern.', () => {
expect(isRoleMatch('pui-case-manager', 'dwp-')).to.be.false
})

it('should return true for a wildcard regex pattern, note that this pattern acts as our configurable DEFAULT.', () => {
expect(isRoleMatch('pui', '.')).to.be.true
})
})

/**
* Same as isRoleMatch() but testing with multiply roles.
*/
describe('anyRolesMatch()', () => {
it('should return true if any of a Users roles match the regex pattern.', () => {

const roles = [
'pui-organisation-manager',
'pui-user-manager',
'pui-finance-manager',
]

expect(anyRolesMatch(roles, 'user-manager')).to.be.true
})

it('should return true if any of a Users roles match a Regular Expression wildcard.', () => {

const roles = [
'pui-organisation-manager',
'pui-user-manager',
'pui-finance-manager',
]

expect(anyRolesMatch(roles, '.')).to.be.true
})

it('should return false if none of a Users roles match the regex pattern.', () => {

const roles = [
'pui-organisation-manager',
'pui-user-manager',
'pui-finance-manager',
]

expect(anyRolesMatch(roles, 'dwp')).to.be.false
})
})

/**
* The Session Timeouts array is in PRIORITY ORDER ie. The FIRST Session Timeout object will be used
* if the FIRST Session Timeout regex pattern matches a User's role.
*
* If the first pattern is not matched, then the second one is tried, etc. If there are no matches
* then the final wildcard regex pattern is used - the DEFAULT.
*/
describe('getUserSessionTimeout()', () => {

it('should return the FIRST matching Session Timeout, if one of a User\'s Roles matches that Session Timeout\'s pattern.', () => {

const roles = [
'pui-organisation-manager',
'pui-case-manager',
]

const roleGroupSessionTimeouts = [
{
idleModalDisplayTime: 5,
pattern: 'pui-',
totalIdleTime: 50,
},
{
idleModalDisplayTime: 2,
pattern: '.',
totalIdleTime: 12,
},
]

const usersSessionTimeout = getUserSessionTimeout(roles, roleGroupSessionTimeouts);

expect(usersSessionTimeout).to.equal(roleGroupSessionTimeouts[0])
})

it('should return the LAST MATCHING Session Timeout, if there are NO User Role\'s that match the previous reg ex patterns ie.', () => {

const DEFAULT_SESSION_TIMEOUT = [{
idleModalDisplayTime: 2,
pattern: '.',
totalIdleTime: 12,
}]

const roles = [
'pui-organisation-manager',
'pui-case-manager',
'pui-finance-manager',
]

const roleGroupSessionTimeouts = [
{
idleModalDisplayTime: 5,
pattern: 'doesnotmatch',
totalIdleTime: 50,
},
// The last item is the default so that we can easily configure the default as IT will change on Go Live, informed by BA.
...DEFAULT_SESSION_TIMEOUT,
]

expect(getUserSessionTimeout(roles, roleGroupSessionTimeouts)).to.equal(DEFAULT_SESSION_TIMEOUT[0])
})

it('should return the SECOND matching Session Timeout, if the Session Timeout reg ex pattern DOES NOT match' +
'the FIRST Session Timeout pattern.', () => {

const roles = [
'pui-organisation-manager',
]

const roleGroupSessionTimeouts = [
{
idleModalDisplayTime: 5,
pattern: 'doesnotmatch',
totalIdleTime: 50,
},
{
idleModalDisplayTime: 10,
pattern: 'organisation',
totalIdleTime: 80,
},
{
idleModalDisplayTime: 2,
pattern: '.',
totalIdleTime: 12,
},
]

expect(getUserSessionTimeout(roles, roleGroupSessionTimeouts)).to.equal(roleGroupSessionTimeouts[1])
})

it('should return the DEFAULT_SESSION_TIMEOUT if the XUI team accidentally sets an incorrect default reg ex pattern.', () => {

const roles = [
'pui-organisation-manager',
]

const roleGroupSessionTimeouts = [{
idleModalDisplayTime: 6,
pattern: 'a-non-descript-pattern',
totalIdleTime: 60,
}]

expect(getUserSessionTimeout(roles, roleGroupSessionTimeouts)).to.equal(DEFAULT_SESSION_TIMEOUT)
})

it('should return the DEFAULT_SESSION_TIMEOUT if the XUI team accidentally does not set a DEFAULT Session Timeout via the' +
'configuration.', () => {

const roles = [
'pui-organisation-manager',
]

const roleGroupSessionTimeouts = []

expect(getUserSessionTimeout(roles, roleGroupSessionTimeouts)).to.equal(DEFAULT_SESSION_TIMEOUT)
})

/**
* The following should never happen but the production code should be resilient to this edge case.
*/
it('should return the DEFAULT_SESSION_TIMEOUT if there are no User Roles.', () => {

const roles = []

const roleGroupSessionTimeouts = [
{
idleModalDisplayTime: 10,
pattern: 'organisation',
totalIdleTime: 80,
},
]

expect(getUserSessionTimeout(roles, roleGroupSessionTimeouts)).to.equal(DEFAULT_SESSION_TIMEOUT)
})

it('should give preference to Session Timeout patterns in a PRIORITY ORDER. A pattern nearer to the top of the list is' +
'higher priority.', () => {

// Roles are sorted by sortUserRoles() which is a side effect within getUserSessionTimeout(), but we are
// showing the sorted roles here, as it's easier to understand what happens.
const roles = [
'caseworker',
'caseworker-divorce-financialremedy',
'caseworker-divorce-solicitor',
'caseworker-probate',
'caseworker-probate-solicitor',
'pui-user-manager',
'pui-finance-manager',
]

const roleGroupSessionTimeouts = [
{
idleModalDisplayTime: 10,
pattern: 'pui-user-manager',
totalIdleTime: 80,
},
{
idleModalDisplayTime: 20,
pattern: 'caseworker-probate',
totalIdleTime: 200,
},
]

expect(getUserSessionTimeout(roles, roleGroupSessionTimeouts)).to.equal(roleGroupSessionTimeouts[0])
})
})

/**
* Should sort the User's Roles alphabetically. Why? So that a priority order can be given to the Session Timeout +
* configuration list.
*
* Example: If we want a PUI Session Timeout to be given preference over another Session Timeout it would be further
* up the Session Timeout Configuration list.
*/
describe('sortRolesAlphabetically()', () => {
it('should sort the User\'s Roles alphabetically, so that a priority order can be given to the Session Timeout' +
'configuration list.', () => {

const roles = [
'caseworker-divorce-financialremedy',
'pui-user-manager',
'pui-case-manager',
'caseworker-probate-solicitor',
'caseworker',
'caseworker-probate',
'caseworker-divorce-financialremedy-solicitor',
'caseworker-divorce',
'pui-organisation-manager',
'pui-finance-manager',
'caseworker-divorce-solicitor',
]

const sortedRoles = roles.sort()

expect(sortUserRoles(roles)).to.equal(sortedRoles)
})
});
})
Loading

0 comments on commit e819bf8

Please sign in to comment.