Skip to content

Commit ed81521

Browse files
authored
Merge pull request #714 from jembi/ocrvs-1441-security-component
Ocrvs 1441 secure pages with pins
2 parents 0fd2518 + 32d5c27 commit ed81521

File tree

22 files changed

+399
-177
lines changed

22 files changed

+399
-177
lines changed

packages/e2e/cypress/integration/birth.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ context('Birth Registration Integration Test', () => {
66
})
77
it('Tests from application to registration', () => {
88
cy.login('fieldWorker')
9+
cy.get('#createPinBtn', { timeout: 30000 }).should('be.visible')
10+
cy.get('#createPinBtn').click()
11+
for (let i = 0; i < 8; i++) {
12+
cy.get('#keypad-1').click()
13+
}
914
cy.get('#new_event_declaration', { timeout: 30000 }).should('be.visible')
1015
cy.get('#new_event_declaration').click()
1116
cy.get('#select_vital_event_view').should('be.visible')

packages/e2e/cypress/integration/death.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ context('Death Registration Integration Test', () => {
66
})
77
it('Tests from application to registration', () => {
88
cy.login('fieldWorker')
9+
cy.get('#createPinBtn', { timeout: 30000 }).should('be.visible')
10+
cy.get('#createPinBtn').click()
11+
for (let i = 0; i < 8; i++) {
12+
cy.get('#keypad-1').click()
13+
}
914
cy.get('#new_event_declaration', { timeout: 30000 }).should('be.visible')
1015
cy.get('#new_event_declaration').click()
1116
cy.get('#select_vital_event_view').should('be.visible')

packages/register/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
"query-string": "^6.1.0",
3232
"react": "^16.3.2",
3333
"react-apollo": "^2.1.4",
34+
"react-device-detect": "^1.6.2",
3435
"react-dom": "^16.3.2",
3536
"react-intl": "^2.4.0",
3637
"react-moment": "^0.8.4",
38+
"react-page-visibility": "^3.2.1",
3739
"react-redux": "^5.0.7",
3840
"react-router": "^4.2.0",
3941
"react-router-redux": "next",

packages/register/src/App.tsx

Lines changed: 79 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ProtectedRoute } from './components/ProtectedRoute'
1313
import * as routes from './navigation/routes'
1414
import { NotificationComponent } from './components/Notification'
1515
import { Page } from './components/Page'
16+
import { ProtectedPage } from './components/ProtectedPage'
1617
import { SelectVitalEvent } from './views/SelectVitalEvent/SelectVitalEvent'
1718
import { SelectInformant } from './views/SelectInformant/SelectInformant'
1819
import { ApplicationForm } from './views/RegisterForm/ApplicationForm'
@@ -29,9 +30,6 @@ import { ConfirmationScreen } from './views/ConfirmationScreen/ConfirmationScree
2930
import { PrintCertificateAction } from './views/PrintCertificate/PrintCertificateAction'
3031
import { ErrorBoundary } from './components/ErrorBoundary'
3132
import { WorkQueue } from './views/WorkQueue/WorkQueue'
32-
import { Unlock } from './views/Unlock/Unlock'
33-
import { CreatePin } from './views/PIN/CreatePin'
34-
import { SecureAccount } from './views/SecureAccount/SecureAccountView'
3533
import { StyledErrorBoundary } from './components/StyledErrorBoundary'
3634

3735
interface IAppProps {
@@ -58,94 +56,84 @@ export class App extends React.Component<IAppProps> {
5856
<SessionExpireConfirmation />
5957
<NotificationComponent>
6058
<Page>
61-
<Switch>
62-
<ProtectedRoute
63-
exact
64-
path={routes.HOME}
65-
component={Home}
66-
/>
67-
<ProtectedRoute
68-
exact
69-
path={routes.SELECT_VITAL_EVENT}
70-
component={SelectVitalEvent}
71-
/>
72-
<ProtectedRoute
73-
exact
74-
path={routes.SELECT_INFORMANT}
75-
component={SelectInformant}
76-
/>
77-
<ProtectedRoute
78-
exact
79-
path={routes.DRAFT_BIRTH_PARENT_FORM}
80-
component={ApplicationForm}
81-
/>
82-
<ProtectedRoute
83-
exact
84-
path={routes.DRAFT_BIRTH_PARENT_FORM_TAB}
85-
component={ApplicationForm}
86-
/>
87-
<ProtectedRoute
88-
exact
89-
path={routes.DRAFT_DEATH_FORM}
90-
component={ApplicationForm}
91-
/>
92-
<ProtectedRoute
93-
exact
94-
path={routes.DRAFT_DEATH_FORM_TAB}
95-
component={ApplicationForm}
96-
/>
97-
<ProtectedRoute
98-
exact
99-
path={routes.REVIEW_EVENT_PARENT_FORM_TAB}
100-
component={ReviewForm}
101-
/>
102-
<ProtectedRoute
103-
exact
104-
path={routes.WORK_QUEUE}
105-
component={WorkQueue}
106-
/>
107-
<ProtectedRoute
108-
exact
109-
path={routes.WORK_QUEUE_TAB}
110-
component={WorkQueue}
111-
/>
112-
<ProtectedRoute
113-
path={routes.CONFIRMATION_SCREEN}
114-
component={ConfirmationScreen}
115-
/>
116-
<ProtectedRoute
117-
path={routes.SEARCH_RESULT}
118-
component={SearchResult}
119-
/>
120-
<ProtectedRoute
121-
path={routes.MY_RECORDS}
122-
component={MyRecords}
123-
/>
124-
<ProtectedRoute
125-
path={routes.MY_DRAFTS}
126-
component={MyDrafts}
127-
/>
128-
<ProtectedRoute
129-
path={routes.REVIEW_DUPLICATES}
130-
component={ReviewDuplicates}
131-
/>
132-
<ProtectedRoute
133-
path={routes.PRINT_CERTIFICATE}
134-
component={PrintCertificateAction}
135-
/>
136-
<ProtectedRoute
137-
path={routes.CREATE_PIN}
138-
component={CreatePin}
139-
/>
140-
<ProtectedRoute
141-
path={routes.SECURE_ACCOUNT}
142-
component={SecureAccount}
143-
/>
144-
<ProtectedRoute
145-
path={routes.UNLOCK_SCREEN}
146-
component={Unlock}
147-
/>
148-
</Switch>
59+
<ProtectedPage>
60+
<Switch>
61+
<ProtectedRoute
62+
exact
63+
path={routes.HOME}
64+
component={Home}
65+
/>
66+
<ProtectedRoute
67+
exact
68+
path={routes.SELECT_VITAL_EVENT}
69+
component={SelectVitalEvent}
70+
/>
71+
<ProtectedRoute
72+
exact
73+
path={routes.SELECT_INFORMANT}
74+
component={SelectInformant}
75+
/>
76+
<ProtectedRoute
77+
exact
78+
path={routes.DRAFT_BIRTH_PARENT_FORM}
79+
component={ApplicationForm}
80+
/>
81+
<ProtectedRoute
82+
exact
83+
path={routes.DRAFT_BIRTH_PARENT_FORM_TAB}
84+
component={ApplicationForm}
85+
/>
86+
<ProtectedRoute
87+
exact
88+
path={routes.DRAFT_DEATH_FORM}
89+
component={ApplicationForm}
90+
/>
91+
<ProtectedRoute
92+
exact
93+
path={routes.DRAFT_DEATH_FORM_TAB}
94+
component={ApplicationForm}
95+
/>
96+
<ProtectedRoute
97+
exact
98+
path={routes.REVIEW_EVENT_PARENT_FORM_TAB}
99+
component={ReviewForm}
100+
/>
101+
<ProtectedRoute
102+
exact
103+
path={routes.WORK_QUEUE}
104+
component={WorkQueue}
105+
/>
106+
<ProtectedRoute
107+
exact
108+
path={routes.WORK_QUEUE_TAB}
109+
component={WorkQueue}
110+
/>
111+
<ProtectedRoute
112+
path={routes.CONFIRMATION_SCREEN}
113+
component={ConfirmationScreen}
114+
/>
115+
<ProtectedRoute
116+
path={routes.SEARCH_RESULT}
117+
component={SearchResult}
118+
/>
119+
<ProtectedRoute
120+
path={routes.MY_RECORDS}
121+
component={MyRecords}
122+
/>
123+
<ProtectedRoute
124+
path={routes.MY_DRAFTS}
125+
component={MyDrafts}
126+
/>
127+
<ProtectedRoute
128+
path={routes.REVIEW_DUPLICATES}
129+
component={ReviewDuplicates}
130+
/>
131+
<ProtectedRoute
132+
path={routes.PRINT_CERTIFICATE}
133+
component={PrintCertificateAction}
134+
/>
135+
</Switch>
136+
</ProtectedPage>
149137
</Page>
150138
</NotificationComponent>
151139
</ScrollToTop>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as React from 'react'
2+
import PageVisibility from 'react-page-visibility'
3+
import { SecureAccount } from 'src/views/SecureAccount/SecureAccountView'
4+
import { Unlock } from 'src/views/Unlock/Unlock'
5+
import { storage } from 'src/storage'
6+
import { withRouter, RouteComponentProps } from 'react-router'
7+
import { isMobileDevice } from 'src/utils/commonUtils'
8+
9+
export const SCREEN_LOCK = 'screenLock'
10+
11+
interface IProtectPageState {
12+
secured: boolean
13+
pinExists: boolean
14+
}
15+
class ProtectedPageComponent extends React.Component<
16+
RouteComponentProps<{}>,
17+
IProtectPageState
18+
> {
19+
constructor(props: RouteComponentProps<{}>) {
20+
super(props)
21+
this.state = {
22+
secured: true,
23+
pinExists: true
24+
}
25+
this.handleVisibilityChange = this.handleVisibilityChange.bind(this)
26+
this.markAsSecured = this.markAsSecured.bind(this)
27+
}
28+
29+
async componentDidMount() {
30+
const newState = { ...this.state }
31+
if (await storage.getItem(SCREEN_LOCK)) {
32+
newState.secured = false
33+
} else {
34+
newState.secured = true
35+
}
36+
if (await this.getPIN()) {
37+
newState.pinExists = true
38+
} else {
39+
newState.pinExists = false
40+
}
41+
this.setState(newState)
42+
}
43+
44+
async handleVisibilityChange(isVisible: boolean) {
45+
const newState = { ...this.state }
46+
if (!isVisible && !(await storage.getItem(SCREEN_LOCK))) {
47+
newState.secured = false
48+
if (await this.getPIN()) {
49+
newState.pinExists = true
50+
} else {
51+
newState.pinExists = false
52+
}
53+
this.setState(newState)
54+
storage.setItem(SCREEN_LOCK, 'true')
55+
}
56+
}
57+
58+
markAsSecured() {
59+
this.setState({ secured: true, pinExists: true })
60+
storage.removeItem(SCREEN_LOCK)
61+
}
62+
/* eventually we will take this pin from current user-details */
63+
async getPIN() {
64+
return await storage.getItem('pin')
65+
}
66+
67+
render() {
68+
const { secured, pinExists } = this.state
69+
return (
70+
(!pinExists && <SecureAccount onComplete={this.markAsSecured} />) ||
71+
(isMobileDevice() && (
72+
<PageVisibility onChange={this.handleVisibilityChange}>
73+
{(secured && this.props.children) ||
74+
(!secured && <Unlock onCorrectPinMatch={this.markAsSecured} />)}
75+
</PageVisibility>
76+
)) ||
77+
this.props.children
78+
)
79+
}
80+
}
81+
export const ProtectedPage = withRouter(ProtectedPageComponent)

packages/register/src/navigation/routes.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,3 @@ export const PRINT_CERTIFICATE = '/print/:registrationId/:eventType'
2323

2424
export const WORK_QUEUE = '/work-queue'
2525
export const WORK_QUEUE_TAB = '/work-queue/:tabId'
26-
27-
export const UNLOCK_SCREEN = '/unlock'
28-
export const CREATE_PIN = '/create-pin'
29-
export const SECURE_ACCOUNT = '/secure-account'

packages/register/src/utils/commonUtils.test.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
1-
export const isMobileDevice = () => {
2-
const IS_MOBILE = true
3-
const IS_DESKTOP = false
1+
import { isMobile } from 'react-device-detect'
42

5-
if (
6-
navigator.userAgent.match(/Android/i) ||
7-
navigator.userAgent.match(/webOS/i) ||
8-
navigator.userAgent.match(/iPhone/i) ||
9-
navigator.userAgent.match(/iPad/i) ||
10-
navigator.userAgent.match(/iPod/i) ||
11-
navigator.userAgent.match(/BlackBerry/i) ||
12-
navigator.userAgent.match(/Windows Phone/i)
13-
) {
14-
return IS_MOBILE
15-
}
16-
17-
if (window.outerWidth < 1033) {
18-
return IS_MOBILE
19-
}
20-
21-
return IS_DESKTOP
3+
export function isMobileDevice() {
4+
return isMobile
225
}

packages/register/src/views/Home/Home.test.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,24 @@ describe('when the home page loads for a field worker', () => {
5050
describe('when user is in home view', () => {
5151
const registerUserDetails = Object.assign({}, userDetails)
5252
registerUserDetails.role = 'LOCAL_REGISTRAR'
53-
beforeEach(() => {
53+
beforeEach(async () => {
5454
store.dispatch(getStorageUserDetailsSuccess(JSON.stringify(userDetails)))
5555
history.replace(HOME)
5656
app.update()
57+
app
58+
.find('#createPinBtn')
59+
.hostNodes()
60+
.simulate('click')
61+
await flushPromises()
62+
app.update()
63+
Array.apply(null, { length: 8 }).map(() => {
64+
app
65+
.find('#keypad-1')
66+
.hostNodes()
67+
.simulate('click')
68+
})
69+
await flushPromises()
70+
app.update()
5771
})
5872
it('lists the actions', () => {
5973
expect(app.find('#home_action_list').hostNodes()).toHaveLength(1)

packages/register/src/views/PIN/CreatePin.test.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ describe('Create PIN view', async () => {
1212

1313
beforeEach(() => {
1414
const { store } = createStore()
15-
const testComponent = createTestComponent(<CreatePin />, store)
15+
const testComponent = createTestComponent(
16+
<CreatePin onComplete={() => null} />,
17+
store
18+
)
1619

1720
c = testComponent.component
1821
})

0 commit comments

Comments
 (0)