Skip to content

Commit 7f943cf

Browse files
authored
Allow non-2fa-enabled accounts to log in (canada-ca#1425)
* Allow non-2fa-enabled accounts to log in * Fix tests * Fix SignInPage.test.js * Fix SignInPage.js checking wrong object for type * Fix SignInPage.test.js
1 parent 8ee4753 commit 7f943cf

8 files changed

Lines changed: 738 additions & 589 deletions

File tree

frontend/src/App.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export default function App() {
128128
<Route path="/sign-in" component={SignInPage} />
129129

130130
<Route
131-
path="/authenticate/:authenticateToken"
131+
path="/authenticate/:sendMethod/:authenticateToken"
132132
component={TwoFactorAuthenticatePage}
133133
/>
134134

frontend/src/AuthenticateField.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,29 @@ import WithPseudoBox from './withPseudoBox'
1717
const AuthenticateField = WithPseudoBox(function AuthenticateField({
1818
name,
1919
forwardedRef,
20+
sendMethod,
2021
...props
2122
}) {
2223
const [field, meta] = useField(name)
2324
const { i18n } = useLingui()
2425

26+
const codeSendMessage =
27+
sendMethod === 'email' ? (
28+
<Trans>
29+
We've sent you an email with an authentication code to sign into
30+
Tracker.
31+
</Trans>
32+
) : (
33+
<Trans>
34+
We've sent an SMS to your registered phone number with an authentication
35+
code to sign into Tracker.
36+
</Trans>
37+
)
38+
2539
return (
2640
<FormControl isInvalid={meta.error && meta.touched}>
2741
<FormLabel htmlFor="twoFactorCode" fontWeight="bold" mb="2">
42+
{codeSendMessage}{' '}
2843
<Trans>Please enter your two factor code below.</Trans>
2944
</FormLabel>
3045
<InputGroup>
@@ -50,6 +65,7 @@ const AuthenticateField = WithPseudoBox(function AuthenticateField({
5065
AuthenticateField.propTypes = {
5166
name: string.isRequired,
5267
forwardedRef: oneOfType([func, shape({ current: elementType })]),
68+
sendMethod: string.isRequired,
5369
}
5470

5571
const withForwardedRef = React.forwardRef((props, ref) => {

frontend/src/SignInPage.js

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ import EmailField from './EmailField'
1919
import { fieldRequirements } from './fieldRequirements'
2020
import { TrackerButton } from './TrackerButton'
2121
import { LoadingMessage } from './LoadingMessage'
22+
import { useUserState } from './UserState'
23+
import { useLingui } from '@lingui/react'
2224

2325
export default function SignInPage() {
26+
const { login } = useUserState()
27+
const { i18n } = useLingui()
2428
const history = useHistory()
2529
const toast = useToast()
2630

2731
const validationSchema = object().shape({
28-
password: string().required(
29-
fieldRequirements.password.required.message,
30-
),
32+
password: string().required(fieldRequirements.password.required.message),
3133
email: string()
3234
.required(fieldRequirements.email.required.message)
3335
.email(fieldRequirements.email.email.message),
@@ -45,8 +47,41 @@ export default function SignInPage() {
4547
})
4648
},
4749
onCompleted({ signIn }) {
48-
// redirect to the authenticate page
49-
history.push(`/authenticate/${signIn.authenticateToken}`)
50+
// 2FA not enabled
51+
if (signIn.result.__typename === 'RegularSignInResult') {
52+
login({
53+
jwt: signIn.result.authResult.authToken,
54+
tfa: signIn.result.authResult.user.tfaValidated,
55+
userName: signIn.result.authResult.user.userName,
56+
})
57+
// // redirect to the home page.
58+
history.push('/')
59+
// // Display a welcome message
60+
toast({
61+
title: i18n._(t`Sign In.`),
62+
description: i18n._(t`Welcome, you are successfully signed in!`),
63+
status: 'success',
64+
duration: 9000,
65+
isClosable: true,
66+
})
67+
}
68+
// 2FA enabled
69+
else if (signIn.result.__typename === 'TFASignInResult') {
70+
// redirect to the authenticate page
71+
history.push(
72+
`/authenticate/${signIn.result.sendMethod}/${signIn.result.authenticateToken}`,
73+
)
74+
} else {
75+
toast({
76+
title: t`Incorrect send method received.`,
77+
description: t`Incorrect signIn.result typename.`,
78+
status: 'error',
79+
duration: 9000,
80+
isClosable: true,
81+
position: 'top-left',
82+
})
83+
console.log('Incorrect signIn.result typename.')
84+
}
5085
},
5186
})
5287

frontend/src/TwoFactorAuthenticatePage.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default function TwoFactorAuthenticatePage() {
1616
const history = useHistory()
1717
const toast = useToast()
1818
const { i18n } = useLingui()
19-
const { authenticateToken } = useParams()
19+
const { sendMethod, authenticateToken } = useParams()
2020

2121
const validationSchema = object().shape({
2222
twoFactorCode: number()
@@ -91,7 +91,11 @@ export default function TwoFactorAuthenticatePage() {
9191
<Trans>Two Factor Authentication</Trans>
9292
</Heading>
9393

94-
<AuthenticateField name="twoFactorCode" mb="4" />
94+
<AuthenticateField
95+
name="twoFactorCode"
96+
mb="4"
97+
sendMethod={sendMethod}
98+
/>
9599

96100
<Button isLoading={isSubmitting} type="submit" variantColor="teal">
97101
<Trans>Submit</Trans>

frontend/src/__tests__/DmarcByDomainPage.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('<DmarcByDomainPage />', () => {
3636
query: FORWARD,
3737
variables: {
3838
month: 'LAST30DAYS',
39-
year: '2020',
39+
year: '2021',
4040
first: 10,
4141
},
4242
},
@@ -66,7 +66,7 @@ describe('<DmarcByDomainPage />', () => {
6666
{
6767
request: {
6868
query: FORWARD,
69-
variables: { first: 10, month: 'LAST30DAYS', year: '2020' },
69+
variables: { first: 10, month: 'LAST30DAYS', year: '2021' },
7070
},
7171
result: rawDmarcReportSummaryTableData,
7272
},
@@ -96,7 +96,7 @@ describe('<DmarcByDomainPage />', () => {
9696
query: FORWARD,
9797
variables: {
9898
month: 'LAST30DAYS',
99-
year: '2020',
99+
year: '2021',
100100
first: 10,
101101
},
102102
},

frontend/src/__tests__/SignInPage.test.js

Lines changed: 147 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -83,77 +83,164 @@ describe('<SignInPage />', () => {
8383
})
8484

8585
describe('when sign-in succeeds', () => {
86-
it('redirects to authenticate', async () => {
87-
const values = {
88-
email: 'testuser@testemail.ca',
89-
password: 'testuserpassword',
90-
authenticateToken: 'authenticate-token-test',
91-
}
92-
93-
const mocks = [
94-
{
95-
request: {
96-
query: SIGN_IN,
97-
variables: {
98-
userName: values.email,
99-
password: values.password,
86+
describe('and 2fa is enabled', () => {
87+
it('redirects to authenticate', async () => {
88+
const values = {
89+
email: 'testuser@testemail.ca',
90+
password: 'testuserpassword',
91+
authenticateToken: 'authenticate-token-test',
92+
}
93+
94+
const mocks = [
95+
{
96+
request: {
97+
query: SIGN_IN,
98+
variables: {
99+
userName: values.email,
100+
password: values.password,
101+
},
100102
},
101-
},
102-
result: {
103-
data: {
104-
signIn: {
105-
authenticateToken: values.authenticateToken,
103+
result: {
104+
data: {
105+
signIn: {
106+
result: {
107+
authenticateToken: values.authenticateToken,
108+
sendMethod: 'email',
109+
__typename: 'TFASignInResult',
110+
},
111+
},
106112
},
107113
},
108114
},
109-
},
110-
]
111-
112-
// create a history object and inject it so we can inspect it afterwards
113-
// for the side effects of our form submission (a redirect to /!).
114-
const history = createMemoryHistory({
115-
initialEntries: ['/sign-in'],
116-
initialIndex: 0,
117-
})
115+
]
116+
117+
// create a history object and inject it so we can inspect it afterwards
118+
// for the side effects of our form submission (a redirect to /!).
119+
const history = createMemoryHistory({
120+
initialEntries: ['/sign-in'],
121+
initialIndex: 0,
122+
})
123+
124+
const { container, getByRole } = render(
125+
<UserStateProvider
126+
initialState={{ userName: null, jwt: null, tfa: null }}
127+
>
128+
<ThemeProvider theme={theme}>
129+
<I18nProvider i18n={i18n}>
130+
<Router history={history}>
131+
<MockedProvider mocks={mocks} addTypename={false}>
132+
<SignInPage />
133+
</MockedProvider>
134+
</Router>
135+
</I18nProvider>
136+
</ThemeProvider>
137+
</UserStateProvider>,
138+
)
118139

119-
const { container, getByRole } = render(
120-
<UserStateProvider
121-
initialState={{ userName: null, jwt: null, tfa: null }}
122-
>
123-
<ThemeProvider theme={theme}>
124-
<I18nProvider i18n={i18n}>
125-
<Router history={history}>
126-
<MockedProvider mocks={mocks} addTypename={false}>
127-
<SignInPage />
128-
</MockedProvider>
129-
</Router>
130-
</I18nProvider>
131-
</ThemeProvider>
132-
</UserStateProvider>,
133-
)
140+
const email = container.querySelector('#email')
141+
const password = container.querySelector('#password')
142+
const form = getByRole('form')
134143

135-
const email = container.querySelector('#email')
136-
const password = container.querySelector('#password')
137-
const form = getByRole('form')
144+
fireEvent.change(email, {
145+
target: {
146+
value: values.email,
147+
},
148+
})
138149

139-
fireEvent.change(email, {
140-
target: {
141-
value: values.email,
142-
},
143-
})
150+
fireEvent.change(password, {
151+
target: {
152+
value: values.password,
153+
},
154+
})
155+
156+
fireEvent.submit(form)
144157

145-
fireEvent.change(password, {
146-
target: {
147-
value: values.password,
148-
},
158+
await waitFor(() => {
159+
expect(history.location.pathname).toEqual(
160+
`/authenticate/email/${values.authenticateToken}`,
161+
)
162+
})
149163
})
164+
})
165+
describe('and 2fa is NOT enabled', () => {
166+
it('redirects to home page', async () => {
167+
const values = {
168+
email: 'testuser@testemail.ca',
169+
password: 'testuserpassword',
170+
}
171+
172+
const mocks = [
173+
{
174+
request: {
175+
query: SIGN_IN,
176+
variables: {
177+
userName: values.email,
178+
password: values.password,
179+
},
180+
},
181+
result: {
182+
data: {
183+
signIn: {
184+
result: {
185+
authResult: {
186+
user: {
187+
userName: 'Thalia.Rosenbaum@gmail.com',
188+
tfa: false,
189+
},
190+
authToken: 'test123stringJWT',
191+
},
192+
__typename: 'RegularSignInResult',
193+
},
194+
},
195+
},
196+
},
197+
},
198+
]
199+
200+
// create a history object and inject it so we can inspect it afterwards
201+
// for the side effects of our form submission (a redirect to /!).
202+
const history = createMemoryHistory({
203+
initialEntries: ['/sign-in'],
204+
initialIndex: 0,
205+
})
206+
207+
const { container, getByRole } = render(
208+
<UserStateProvider
209+
initialState={{ userName: null, jwt: null, tfa: null }}
210+
>
211+
<ThemeProvider theme={theme}>
212+
<I18nProvider i18n={i18n}>
213+
<Router history={history}>
214+
<MockedProvider mocks={mocks} addTypename={false}>
215+
<SignInPage />
216+
</MockedProvider>
217+
</Router>
218+
</I18nProvider>
219+
</ThemeProvider>
220+
</UserStateProvider>,
221+
)
150222

151-
fireEvent.submit(form)
223+
const email = container.querySelector('#email')
224+
const password = container.querySelector('#password')
225+
const form = getByRole('form')
152226

153-
await waitFor(() => {
154-
expect(history.location.pathname).toEqual(
155-
`/authenticate/${values.authenticateToken}`,
156-
)
227+
fireEvent.change(email, {
228+
target: {
229+
value: values.email,
230+
},
231+
})
232+
233+
fireEvent.change(password, {
234+
target: {
235+
value: values.password,
236+
},
237+
})
238+
239+
fireEvent.submit(form)
240+
241+
await waitFor(() => {
242+
expect(history.location.pathname).toEqual('/')
243+
})
157244
})
158245
})
159246
})

0 commit comments

Comments
 (0)