Skip to content

Commit c9b5971

Browse files
authored
refactor: Change response types of TOTP adapter to match existing adapters (#8661)
1 parent 02f40fd commit c9b5971

File tree

2 files changed

+63
-18
lines changed

2 files changed

+63
-18
lines changed

spec/AuthenticationAdapters.spec.js

+54-10
Original file line numberDiff line numberDiff line change
@@ -2445,9 +2445,9 @@ describe('OTP TOTP auth adatper', () => {
24452445
const response = user.get('authDataResponse');
24462446
expect(response.mfa).toBeDefined();
24472447
expect(response.mfa.recovery).toBeDefined();
2448-
expect(response.mfa.recovery.length).toEqual(2);
2448+
expect(response.mfa.recovery.split(',').length).toEqual(2);
24492449
await user.fetch();
2450-
expect(user.get('authData').mfa).toEqual({ enabled: true });
2450+
expect(user.get('authData').mfa).toEqual({ status: 'enabled' });
24512451
});
24522452

24532453
it('can login with valid token', async () => {
@@ -2473,13 +2473,15 @@ describe('OTP TOTP auth adatper', () => {
24732473
username: 'username',
24742474
password: 'password',
24752475
authData: {
2476-
mfa: totp.generate(),
2476+
mfa: {
2477+
token: totp.generate(),
2478+
},
24772479
},
24782480
}),
24792481
}).then(res => res.data);
24802482
expect(response.objectId).toEqual(user.id);
24812483
expect(response.sessionToken).toBeDefined();
2482-
expect(response.authData).toEqual({ mfa: { enabled: true } });
2484+
expect(response.authData).toEqual({ mfa: { status: 'enabled' } });
24832485
expect(Object.keys(response).sort()).toEqual(
24842486
[
24852487
'objectId',
@@ -2528,6 +2530,42 @@ describe('OTP TOTP auth adatper', () => {
25282530
expect(user.get('authData').mfa.secret).toEqual(new_secret.base32);
25292531
});
25302532

2533+
it('cannot change OTP with invalid token', async () => {
2534+
const user = await Parse.User.signUp('username', 'password');
2535+
const OTPAuth = require('otpauth');
2536+
const secret = new OTPAuth.Secret();
2537+
const totp = new OTPAuth.TOTP({
2538+
algorithm: 'SHA1',
2539+
digits: 6,
2540+
period: 30,
2541+
secret,
2542+
});
2543+
const token = totp.generate();
2544+
await user.save(
2545+
{ authData: { mfa: { secret: secret.base32, token } } },
2546+
{ sessionToken: user.getSessionToken() }
2547+
);
2548+
2549+
const new_secret = new OTPAuth.Secret();
2550+
const new_totp = new OTPAuth.TOTP({
2551+
algorithm: 'SHA1',
2552+
digits: 6,
2553+
period: 30,
2554+
secret: new_secret,
2555+
});
2556+
const new_token = new_totp.generate();
2557+
await expectAsync(
2558+
user.save(
2559+
{
2560+
authData: { mfa: { secret: new_secret.base32, token: new_token, old: '123' } },
2561+
},
2562+
{ sessionToken: user.getSessionToken() }
2563+
)
2564+
).toBeRejectedWith(new Parse.Error(Parse.Error.OTHER_CAUSE, 'Invalid MFA token'));
2565+
await user.fetch({ useMasterKey: true });
2566+
expect(user.get('authData').mfa.secret).toEqual(secret.base32);
2567+
});
2568+
25312569
it('future logins require TOTP token', async () => {
25322570
const user = await Parse.User.signUp('username', 'password');
25332571
const OTPAuth = require('otpauth');
@@ -2572,7 +2610,9 @@ describe('OTP TOTP auth adatper', () => {
25722610
username: 'username',
25732611
password: 'password',
25742612
authData: {
2575-
mfa: 'abcd',
2613+
mfa: {
2614+
token: 'abcd',
2615+
},
25762616
},
25772617
}),
25782618
}).catch(e => {
@@ -2619,7 +2659,7 @@ describe('OTP SMS auth adatper', () => {
26192659
const spy = spyOn(mfa, 'sendSMS').and.callThrough();
26202660
await user.save({ authData: { mfa: { mobile: '+11111111111' } } }, { sessionToken });
26212661
await user.fetch({ sessionToken });
2622-
expect(user.get('authData')).toEqual({ mfa: { enabled: false } });
2662+
expect(user.get('authData')).toEqual({ mfa: { status: 'disabled' } });
26232663
expect(spy).toHaveBeenCalledWith(code, '+11111111111');
26242664
await user.fetch({ useMasterKey: true });
26252665
const authData = user.get('authData').mfa?.pending;
@@ -2629,7 +2669,7 @@ describe('OTP SMS auth adatper', () => {
26292669

26302670
await user.save({ authData: { mfa: { mobile, token: code } } }, { sessionToken });
26312671
await user.fetch({ sessionToken });
2632-
expect(user.get('authData')).toEqual({ mfa: { enabled: true } });
2672+
expect(user.get('authData')).toEqual({ mfa: { status: 'enabled' } });
26332673
});
26342674

26352675
it('future logins require SMS code', async () => {
@@ -2658,7 +2698,9 @@ describe('OTP SMS auth adatper', () => {
26582698
username: 'username',
26592699
password: 'password',
26602700
authData: {
2661-
mfa: true,
2701+
mfa: {
2702+
token: 'request',
2703+
},
26622704
},
26632705
}),
26642706
}).catch(e => e.data);
@@ -2672,13 +2714,15 @@ describe('OTP SMS auth adatper', () => {
26722714
username: 'username',
26732715
password: 'password',
26742716
authData: {
2675-
mfa: code,
2717+
mfa: {
2718+
token: code,
2719+
},
26762720
},
26772721
}),
26782722
}).then(res => res.data);
26792723
expect(response.objectId).toEqual(user.id);
26802724
expect(response.sessionToken).toBeDefined();
2681-
expect(response.authData).toEqual({ mfa: { enabled: true } });
2725+
expect(response.authData).toEqual({ mfa: { status: 'enabled' } });
26822726
expect(Object.keys(response).sort()).toEqual(
26832727
[
26842728
'objectId',

src/Adapters/Auth/mfa.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,15 @@ class MFAAdapter extends AuthAdapter {
4444
}
4545
throw 'Invalid MFA data';
4646
}
47-
async validateLogin(token, _, req) {
47+
async validateLogin(loginData, _, req) {
4848
const saveResponse = {
4949
doNotSave: true,
5050
};
51+
const token = loginData.token;
5152
const auth = req.original.get('authData') || {};
5253
const { secret, recovery, mobile, token: saved, expiry } = auth.mfa || {};
5354
if (this.sms && mobile) {
54-
if (typeof token === 'boolean') {
55+
if (token === 'request') {
5556
const { token: sendToken, expiry } = await this.sendSMS(mobile);
5657
auth.mfa.token = sendToken;
5758
auth.mfa.expiry = expiry;
@@ -96,7 +97,7 @@ class MFAAdapter extends AuthAdapter {
9697
}
9798
return saveResponse;
9899
}
99-
validateUpdate(authData, _, req) {
100+
async validateUpdate(authData, _, req) {
100101
if (req.master) {
101102
return;
102103
}
@@ -107,7 +108,7 @@ class MFAAdapter extends AuthAdapter {
107108
return this.confirmSMSOTP(authData, req.original.get('authData')?.mfa || {});
108109
}
109110
if (this.totp) {
110-
this.validateLogin(authData.old, null, req);
111+
await this.validateLogin({ token: authData.old }, null, req);
111112
return this.validateSetUp(authData);
112113
}
113114
throw 'Invalid MFA data';
@@ -118,16 +119,16 @@ class MFAAdapter extends AuthAdapter {
118119
}
119120
if (this.totp && authData.secret) {
120121
return {
121-
enabled: true,
122+
status: 'enabled',
122123
};
123124
}
124125
if (this.sms && authData.mobile) {
125126
return {
126-
enabled: true,
127+
status: 'enabled',
127128
};
128129
}
129130
return {
130-
enabled: false,
131+
status: 'disabled',
131132
};
132133
}
133134

@@ -204,7 +205,7 @@ class MFAAdapter extends AuthAdapter {
204205
}
205206
const recovery = [randomString(30), randomString(30)];
206207
return {
207-
response: { recovery },
208+
response: { recovery: recovery.join(', ') },
208209
save: { secret, recovery },
209210
};
210211
}

0 commit comments

Comments
 (0)