Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Change response types of TOTP adapter to match existing adapters #8661

Merged
merged 7 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions spec/AuthenticationAdapters.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2445,9 +2445,9 @@ describe('OTP TOTP auth adatper', () => {
const response = user.get('authDataResponse');
expect(response.mfa).toBeDefined();
expect(response.mfa.recovery).toBeDefined();
expect(response.mfa.recovery.length).toEqual(2);
expect(response.mfa.recovery.split(',').length).toEqual(2);
await user.fetch();
expect(user.get('authData').mfa).toEqual({ enabled: true });
expect(user.get('authData').mfa).toEqual({ status: 'enabled' });
});

it('can login with valid token', async () => {
Expand All @@ -2473,13 +2473,15 @@ describe('OTP TOTP auth adatper', () => {
username: 'username',
password: 'password',
authData: {
mfa: totp.generate(),
mfa: {
token: totp.generate(),
},
},
}),
}).then(res => res.data);
expect(response.objectId).toEqual(user.id);
expect(response.sessionToken).toBeDefined();
expect(response.authData).toEqual({ mfa: { enabled: true } });
expect(response.authData).toEqual({ mfa: { status: 'enabled' } });
expect(Object.keys(response).sort()).toEqual(
[
'objectId',
Expand Down Expand Up @@ -2528,6 +2530,42 @@ describe('OTP TOTP auth adatper', () => {
expect(user.get('authData').mfa.secret).toEqual(new_secret.base32);
});

it('cannot change OTP with invalid token', async () => {
const user = await Parse.User.signUp('username', 'password');
const OTPAuth = require('otpauth');
const secret = new OTPAuth.Secret();
const totp = new OTPAuth.TOTP({
algorithm: 'SHA1',
digits: 6,
period: 30,
secret,
});
const token = totp.generate();
await user.save(
{ authData: { mfa: { secret: secret.base32, token } } },
{ sessionToken: user.getSessionToken() }
);

const new_secret = new OTPAuth.Secret();
const new_totp = new OTPAuth.TOTP({
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: new_secret,
});
const new_token = new_totp.generate();
await expectAsync(
user.save(
{
authData: { mfa: { secret: new_secret.base32, token: new_token, old: '123' } },
},
{ sessionToken: user.getSessionToken() }
)
).toBeRejectedWith(new Parse.Error(Parse.Error.OTHER_CAUSE, 'Invalid MFA token'));
await user.fetch({ useMasterKey: true });
expect(user.get('authData').mfa.secret).toEqual(secret.base32);
});

it('future logins require TOTP token', async () => {
const user = await Parse.User.signUp('username', 'password');
const OTPAuth = require('otpauth');
Expand Down Expand Up @@ -2572,7 +2610,9 @@ describe('OTP TOTP auth adatper', () => {
username: 'username',
password: 'password',
authData: {
mfa: 'abcd',
mfa: {
token: 'abcd',
},
},
}),
}).catch(e => {
Expand Down Expand Up @@ -2619,7 +2659,7 @@ describe('OTP SMS auth adatper', () => {
const spy = spyOn(mfa, 'sendSMS').and.callThrough();
await user.save({ authData: { mfa: { mobile: '+11111111111' } } }, { sessionToken });
await user.fetch({ sessionToken });
expect(user.get('authData')).toEqual({ mfa: { enabled: false } });
expect(user.get('authData')).toEqual({ mfa: { status: 'disabled' } });
expect(spy).toHaveBeenCalledWith(code, '+11111111111');
await user.fetch({ useMasterKey: true });
const authData = user.get('authData').mfa?.pending;
Expand All @@ -2629,7 +2669,7 @@ describe('OTP SMS auth adatper', () => {

await user.save({ authData: { mfa: { mobile, token: code } } }, { sessionToken });
await user.fetch({ sessionToken });
expect(user.get('authData')).toEqual({ mfa: { enabled: true } });
expect(user.get('authData')).toEqual({ mfa: { status: 'enabled' } });
});

it('future logins require SMS code', async () => {
Expand Down Expand Up @@ -2658,7 +2698,9 @@ describe('OTP SMS auth adatper', () => {
username: 'username',
password: 'password',
authData: {
mfa: true,
mfa: {
token: 'request',
},
},
}),
}).catch(e => e.data);
Expand All @@ -2672,13 +2714,15 @@ describe('OTP SMS auth adatper', () => {
username: 'username',
password: 'password',
authData: {
mfa: code,
mfa: {
token: code,
},
},
}),
}).then(res => res.data);
expect(response.objectId).toEqual(user.id);
expect(response.sessionToken).toBeDefined();
expect(response.authData).toEqual({ mfa: { enabled: true } });
expect(response.authData).toEqual({ mfa: { status: 'enabled' } });
expect(Object.keys(response).sort()).toEqual(
[
'objectId',
Expand Down
17 changes: 9 additions & 8 deletions src/Adapters/Auth/mfa.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@ class MFAAdapter extends AuthAdapter {
}
throw 'Invalid MFA data';
}
async validateLogin(token, _, req) {
async validateLogin(loginData, _, req) {
const saveResponse = {
doNotSave: true,
};
const token = loginData.token;
const auth = req.original.get('authData') || {};
const { secret, recovery, mobile, token: saved, expiry } = auth.mfa || {};
if (this.sms && mobile) {
if (typeof token === 'boolean') {
if (token === 'request') {
const { token: sendToken, expiry } = await this.sendSMS(mobile);
auth.mfa.token = sendToken;
auth.mfa.expiry = expiry;
Expand Down Expand Up @@ -96,7 +97,7 @@ class MFAAdapter extends AuthAdapter {
}
return saveResponse;
}
validateUpdate(authData, _, req) {
async validateUpdate(authData, _, req) {
if (req.master) {
return;
}
Expand All @@ -107,7 +108,7 @@ class MFAAdapter extends AuthAdapter {
return this.confirmSMSOTP(authData, req.original.get('authData')?.mfa || {});
}
if (this.totp) {
this.validateLogin(authData.old, null, req);
await this.validateLogin({ token: authData.old }, null, req);
return this.validateSetUp(authData);
}
throw 'Invalid MFA data';
Expand All @@ -118,16 +119,16 @@ class MFAAdapter extends AuthAdapter {
}
if (this.totp && authData.secret) {
return {
enabled: true,
status: 'enabled',
};
}
if (this.sms && authData.mobile) {
return {
enabled: true,
status: 'enabled',
};
}
return {
enabled: false,
status: 'disabled',
};
}

Expand Down Expand Up @@ -204,7 +205,7 @@ class MFAAdapter extends AuthAdapter {
}
const recovery = [randomString(30), randomString(30)];
return {
response: { recovery },
response: { recovery: recovery.join(', ') },
save: { secret, recovery },
};
}
Expand Down
Loading