77const { assert } = require ( 'chai' ) ;
88const TestServer = require ( '../test_server' ) ;
99const Client = require ( '../client' ) ( ) ;
10+ const otplib = require ( 'otplib' ) ;
11+ const crypto = require ( 'crypto' ) ;
1012
1113const config = require ( '../../config' ) . default . getProperties ( ) ;
1214
@@ -15,108 +17,264 @@ const config = require('../../config').default.getProperties();
1517 describe ( `#integration${ testOptions . version } - remote account destroy` , function ( ) {
1618 this . timeout ( 60000 ) ;
1719 let server ;
20+ let tempConfigValue ;
1821
1922 before ( async function ( ) {
23+ // Important, this config impacts test logic. By default this is enabled
24+ // in development/test with a very large max time. In the real world the max time
25+ // is much lower, and confirmation is not skipped very often.The test cases in this
26+ // suite are looking at edge cases on unconfirmed accounts and sessions. Therefore,
27+ // this config will always be disabled here.
28+ tempConfigValue = config . signinConfirmation . skipForNewAccounts . enabled ;
29+ config . signinConfirmation . skipForNewAccounts . enabled = false ;
2030 server = await TestServer . start ( config ) ;
2131 } ) ;
2232
2333 after ( async function ( ) {
34+ config . signinConfirmation . skipForNewAccounts . enabled = tempConfigValue ;
2435 await TestServer . stop ( server ) ;
2536 } ) ;
2637
27- it ( 'account destroy' , ( ) => {
38+ // Note that this test case most closely aligns with what most users experience.
39+ // In this case we have a user with a verified account, but unverified session
40+ // and no totp. When this occurs our UI will send an OTP email to user and prompt
41+ // them to enter the code prior to deleting the account.
42+ it ( 'can delete account by providing short code' , async function ( ) {
2843 const email = server . uniqueEmail ( ) ;
29- const password = 'allyourbasearebelongtous' ;
30- let client = null ;
31- return Client . createAndVerify (
44+ const password = 'ok' ;
45+ await Client . create ( config . publicUrl , email , password , {
46+ ...testOptions ,
47+ verificationMethod : 'email-2fa' ,
48+ keys : true ,
49+ } ) ;
50+ const client = await Client . login ( config . publicUrl , email , password , {
51+ ...testOptions ,
52+ verificationMethod : 'email-2fa' ,
53+ keys : true ,
54+ } ) ;
55+
56+ // Send a short code, this will validate the account and the session.
57+ // In the UI this happens when a user clicks on delete account, and
58+ // OTP message box pops up.
59+ await client . resendVerifyShortCodeEmail ( ) ;
60+ const emailData = await server . mailbox . waitForEmail ( email ) ;
61+ let code ;
62+ for ( let i = 0 ; i < emailData . length ; i ++ ) {
63+ if ( emailData [ i ] . headers [ 'x-verify-short-code' ] ) {
64+ code = emailData [ i ] . headers [ 'x-verify-short-code' ] ;
65+ }
66+ }
67+ assert . isDefined ( code ) ;
68+ await client . verifyShortCodeEmail ( code ) ;
69+
70+ // Should not throw
71+ await client . destroyAccount ( ) ;
72+ } ) ;
73+
74+ it ( 'can delete account by providing verify code' , async function ( ) {
75+ const email = server . uniqueEmail ( ) ;
76+ const password = 'ok' ;
77+ await Client . create ( config . publicUrl , email , password , {
78+ ...testOptions ,
79+ verificationMethod : 'email-2fa' ,
80+ keys : true ,
81+ } ) ;
82+ const client = await Client . login ( config . publicUrl , email , password , {
83+ ...testOptions ,
84+ verificationMethod : 'email-2fa' ,
85+ keys : true ,
86+ } ) ;
87+
88+ const emailData = await server . mailbox . waitForEmail ( email ) ;
89+ const code = emailData [ emailData . length - 1 ] . headers [ 'x-verify-code' ] ;
90+ await client . verifyEmail ( code ) ;
91+
92+ // Should not throw
93+ await client . destroyAccount ( ) ;
94+ } ) ;
95+
96+ it ( 'cannot delete account with invalid authPW' , async function ( ) {
97+ const email = server . uniqueEmail ( ) ;
98+ const password = 'ok' ;
99+ const c = await Client . createAndVerify (
32100 config . publicUrl ,
33101 email ,
34102 password ,
35103 server . mailbox ,
36104 testOptions
37- )
38- . then ( ( x ) => {
39- client = x ;
40- return client . sessionStatus ( ) ;
41- } )
42- . then ( ( status ) => {
43- return client . destroyAccount ( ) ;
44- } )
45- . then ( ( ) => {
46- return client . keys ( ) ;
47- } )
48- . then (
49- ( keys ) => {
50- assert ( false , 'account not destroyed' ) ;
51- } ,
52- ( err ) => {
53- assert . equal ( err . message , 'Unknown account' , 'account destroyed' ) ;
54- }
105+ ) ;
106+
107+ c . authPW = Buffer . from (
108+ '0000000000000000000000000000000000000000000000000000000000000000' ,
109+ 'hex'
110+ ) ;
111+ c . authPWVersion2 = Buffer . from (
112+ '0000000000000000000000000000000000000000000000000000000000000000' ,
113+ 'hex'
114+ ) ;
115+
116+ try {
117+ await c . destroyAccount ( ) ;
118+ assert . fail (
119+ 'should not be able to destroy account with invalid password'
55120 ) ;
121+ } catch ( err ) {
122+ assert . equal ( err . errno , 103 ) ;
123+ }
56124 } ) ;
57125
58- it ( 'invalid authPW on account destroy ' , ( ) => {
126+ it ( 'cannot delete account without verifying TOTP ' , async function ( ) {
59127 const email = server . uniqueEmail ( ) ;
60128 const password = 'ok' ;
61- return Client . createAndVerify (
129+
130+ await Client . createAndVerifyAndTOTP (
62131 config . publicUrl ,
63132 email ,
64133 password ,
65134 server . mailbox ,
135+ {
136+ ...testOptions ,
137+ keys : true ,
138+ }
139+ ) ;
140+
141+ // Create a new unverified session
142+ const client = await Client . login (
143+ config . publicUrl ,
144+ email ,
145+ password ,
146+ testOptions
147+ ) ;
148+ const res = await await client . emailStatus ( ) ;
149+ assert . equal ( res . sessionVerified , false , 'session not verified' ) ;
150+
151+ try {
152+ await client . destroyAccount ( ) ;
153+ assert . fail (
154+ 'Should not be able to destroy account without verifying totp'
155+ ) ;
156+ } catch ( err ) {
157+ assert . equal ( err . errno , 138 , 'unverified session' ) ;
158+ }
159+ } ) ;
160+
161+ it ( 'cannot delete account with TOTP by supplying email otp code' , async function ( ) {
162+ const email = server . uniqueEmail ( ) ;
163+ const password = 'ok' ;
164+ await Client . create ( config . publicUrl , email , password , {
165+ ...testOptions ,
166+ verificationMethod : 'email-2fa' ,
167+ keys : true ,
168+ } ) ;
169+ let client = await Client . login ( config . publicUrl , email , password , {
170+ ...testOptions ,
171+ verificationMethod : 'email-2fa' ,
172+ keys : true ,
173+ } ) ;
174+
175+ // Send a short code, this will validate the account and the session.
176+ // In the UI this happens when a user clicks on delete account, and
177+ // OTP message box pops up.
178+ await client . resendVerifyShortCodeEmail ( ) ;
179+ const emailData = await server . mailbox . waitForEmail ( email ) ;
180+ let code ;
181+ for ( let i = 0 ; i < emailData . length ; i ++ ) {
182+ if ( emailData [ i ] . headers [ 'x-verify-short-code' ] ) {
183+ code = emailData [ i ] . headers [ 'x-verify-short-code' ] ;
184+ }
185+ }
186+ assert . isDefined ( code ) ;
187+ await client . verifyShortCodeEmail ( code ) ;
188+
189+ // Add totp to account.
190+ client . totpAuthenticator = new otplib . authenticator . Authenticator ( ) ;
191+ const totpTokenResult = await client . createTotpToken ( ) ;
192+ assert . isDefined ( totpTokenResult ) ;
193+ 60 ;
194+ client . totpAuthenticator . options = {
195+ secret : totpTokenResult . secret ,
196+ crypto : crypto ,
197+ } ;
198+ const totpCode = client . totpAuthenticator . generate ( ) ;
199+ await client . verifyTotpCode ( totpCode ) ;
200+
201+ // Log in again. This creates a new unverified session
202+ client = await Client . login (
203+ config . publicUrl ,
204+ email ,
205+ password ,
66206 testOptions
67- )
68- . then ( ( c ) => {
69- c . authPW = Buffer . from (
70- '0000000000000000000000000000000000000000000000000000000000000000' ,
71- 'hex'
72- ) ;
73- c . authPWVersion2 = Buffer . from (
74- '0000000000000000000000000000000000000000000000000000000000000000' ,
75- 'hex'
76- ) ;
77- return c . destroyAccount ( ) ;
78- } )
79- . then (
80- ( r ) => {
81- assert ( false ) ;
82- } ,
83- ( err ) => {
84- assert . equal ( err . errno , 103 ) ;
85- }
207+ ) ;
208+ const res = await client . emailStatus ( ) ;
209+ assert . equal ( res . sessionVerified , false , 'session not verified' ) ;
210+
211+ // Try verifying the the session with a short code. This should
212+ // not be enough to bypass 2FA. This sort of mimics a valid email code
213+ // being stolen...
214+ await client . verifyShortCodeEmail ( code ) ;
215+ assert . equal (
216+ ( await client . emailStatus ( ) ) . sessionVerified ,
217+ true ,
218+ 'session should be verified'
219+ ) ;
220+
221+ // Destroying the account should not work. Despite the session being 'verified',
222+ // totp has not been provided.
223+ try {
224+ await client . destroyAccount ( ) ;
225+ assert . fail (
226+ 'Should not be able to destroy account without verifying totp'
86227 ) ;
228+ } catch ( error ) {
229+ assert . equal ( error . errno , 138 , 'unverified session' ) ;
230+ }
87231 } ) ;
88232
89- it ( 'should fail to delete account with TOTP with unverified session' , ( ) => {
233+ it ( 'cannot delete without verifying session' , async function ( ) {
90234 const email = server . uniqueEmail ( ) ;
91235 const password = 'ok' ;
92- let client ;
93- return Client . createAndVerifyAndTOTP (
236+ let client = await Client . createAndVerify (
94237 config . publicUrl ,
95238 email ,
96239 password ,
97240 server . mailbox ,
98- {
99- ...testOptions ,
100- keys : true ,
101- }
102- )
103- . then ( ( ) => {
104- // Create a new unverified session
105- return Client . login ( config . publicUrl , email , password , testOptions ) ;
106- } )
107- . then ( ( response ) => {
108- client = response ;
109- return client . emailStatus ( ) ;
110- } )
111- . then ( ( res ) =>
112- assert . equal ( res . sessionVerified , false , 'session not verified' )
113- )
114- . then ( ( ) => client . destroyAccount ( ) )
115- . then ( assert . fail , ( err ) => {
116- assert . equal ( err . errno , 138 , 'unverified session' ) ;
117- } ) ;
118- } ) ;
241+ testOptions
242+ ) ;
243+
244+ // Login again requiring email-2fa for session verification. The account is now verified but the session is not.
245+ client = await Client . login ( config . publicUrl , email , password , {
246+ ...testOptions ,
247+ verificationMethod : 'email-2fa' ,
248+ } ) ;
119249
250+ try {
251+ await client . destroyAccount ( ) ;
252+ assert . fail ( 'Should not be able allowed to destroy account.' ) ;
253+ } catch ( err ) {
254+ assert . equal ( err . message , 'Unconfirmed session' ) ;
255+ }
256+ } ) ;
120257
258+ it ( 'cannot delete without verifying account' , async function ( ) {
259+ const email = server . uniqueEmail ( ) ;
260+ const password = 'ok' ;
261+ await Client . create ( config . publicUrl , email , password , {
262+ ...testOptions ,
263+ verificationMethod : 'email-2fa' ,
264+ keys : true ,
265+ } ) ;
266+ const client = await Client . login (
267+ config . publicUrl ,
268+ email ,
269+ password ,
270+ testOptions
271+ ) ;
272+ try {
273+ await client . destroyAccount ( ) ;
274+ assert . fail ( 'Should not be able allowed to destroy account.' ) ;
275+ } catch ( err ) {
276+ assert . equal ( err . message , 'Unconfirmed account' ) ;
277+ }
278+ } ) ;
121279 } ) ;
122280} ) ;
0 commit comments