Feature: Reuse tokens if they haven't expired (#7017)

* Reuse tokens if they haven't expired

* Fix failing tests

* Update UserController.js

* Update tests

* Tests for invalid config

* restart tests
This commit is contained in:
dblythy
2020-11-26 04:30:52 +11:00
committed by GitHub
parent 0bf2e84f81
commit e88f2e38f9
8 changed files with 289 additions and 26 deletions

View File

@@ -510,7 +510,7 @@ describe('Email Verification Token Expiration: ', () => {
userAfterEmailReset._email_verify_token
);
expect(userBeforeEmailReset._email_verify_token_expires_at).not.toEqual(
userAfterEmailReset.__email_verify_token_expires_at
userAfterEmailReset._email_verify_token_expires_at
);
expect(sendEmailOptions).toBeDefined();
done();
@@ -594,7 +594,7 @@ describe('Email Verification Token Expiration: ', () => {
userAfterRequest._email_verify_token
);
expect(userBeforeRequest._email_verify_token_expires_at).not.toEqual(
userAfterRequest.__email_verify_token_expires_at
userAfterRequest._email_verify_token_expires_at
);
done();
})
@@ -604,6 +604,110 @@ describe('Email Verification Token Expiration: ', () => {
});
});
it('should throw with invalid emailVerifyTokenReuseIfValid', async done => {
const sendEmailOptions = [];
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
sendEmailOptions.push(options);
},
sendMail: () => {},
};
try {
await reconfigureServer({
appName: 'passwordPolicy',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5 * 60, // 5 minutes
emailVerifyTokenReuseIfValid: [],
publicServerURL: 'http://localhost:8378/1',
});
fail('should have thrown.');
} catch (e) {
expect(e).toBe('emailVerifyTokenReuseIfValid must be a boolean value');
}
try {
await reconfigureServer({
appName: 'passwordPolicy',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenReuseIfValid: true,
publicServerURL: 'http://localhost:8378/1',
});
fail('should have thrown.');
} catch (e) {
expect(e).toBe(
'You cannot use emailVerifyTokenReuseIfValid without emailVerifyTokenValidityDuration'
);
}
done();
});
it('should match codes with emailVerifyTokenReuseIfValid', async done => {
let sendEmailOptions;
let sendVerificationEmailCallCount = 0;
const emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
sendVerificationEmailCallCount++;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
await reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5 * 60, // 5 minutes
publicServerURL: 'http://localhost:8378/1',
emailVerifyTokenReuseIfValid: true,
});
const user = new Parse.User();
user.setUsername('resends_verification_token');
user.setPassword('expiringToken');
user.set('email', 'user@example.com');
await user.signUp();
const config = Config.get('test');
const [userBeforeRequest] = await config.database.find('_User', {
username: 'resends_verification_token',
});
// store this user before we make our email request
expect(sendVerificationEmailCallCount).toBe(1);
await new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
const response = await request({
url: 'http://localhost:8378/1/verificationEmailRequest',
method: 'POST',
body: {
email: 'user@example.com',
},
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'application/json',
},
});
expect(response.status).toBe(200);
expect(sendVerificationEmailCallCount).toBe(2);
expect(sendEmailOptions).toBeDefined();
const [userAfterRequest] = await config.database.find('_User', {
username: 'resends_verification_token',
});
// verify that our token & expiration has been changed for this new request
expect(typeof userAfterRequest).toBe('object');
expect(userBeforeRequest._email_verify_token).toEqual(userAfterRequest._email_verify_token);
expect(userBeforeRequest._email_verify_token_expires_at).toEqual(
userAfterRequest._email_verify_token_expires_at
);
done();
});
it('should not send a new verification email when a resend is requested and the user is VERIFIED', done => {
const user = new Parse.User();
let sendEmailOptions;

View File

@@ -122,6 +122,102 @@ describe('Password Policy: ', () => {
});
});
it('should not keep reset token by default', async done => {
const sendEmailOptions = [];
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
sendEmailOptions.push(options);
},
sendMail: () => {},
};
await reconfigureServer({
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
passwordPolicy: {
resetTokenValidityDuration: 5 * 60, // 5 minutes
},
publicServerURL: 'http://localhost:8378/1',
});
const user = new Parse.User();
user.setUsername('testResetTokenValidity');
user.setPassword('original');
user.set('email', 'user@example.com');
await user.signUp();
await Parse.User.requestPasswordReset('user@example.com');
await Parse.User.requestPasswordReset('user@example.com');
expect(sendEmailOptions[0].link).not.toBe(sendEmailOptions[1].link);
done();
});
it('should keep reset token with resetTokenReuseIfValid', async done => {
const sendEmailOptions = [];
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
sendEmailOptions.push(options);
},
sendMail: () => {},
};
await reconfigureServer({
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
passwordPolicy: {
resetTokenValidityDuration: 5 * 60, // 5 minutes
resetTokenReuseIfValid: true,
},
publicServerURL: 'http://localhost:8378/1',
});
const user = new Parse.User();
user.setUsername('testResetTokenValidity');
user.setPassword('original');
user.set('email', 'user@example.com');
await user.signUp();
await Parse.User.requestPasswordReset('user@example.com');
await Parse.User.requestPasswordReset('user@example.com');
expect(sendEmailOptions[0].link).toBe(sendEmailOptions[1].link);
done();
});
it('should throw with invalid resetTokenReuseIfValid', async done => {
const sendEmailOptions = [];
const emailAdapter = {
sendVerificationEmail: () => Promise.resolve(),
sendPasswordResetEmail: options => {
sendEmailOptions.push(options);
},
sendMail: () => {},
};
try {
await reconfigureServer({
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
passwordPolicy: {
resetTokenValidityDuration: 5 * 60, // 5 minutes
resetTokenReuseIfValid: [],
},
publicServerURL: 'http://localhost:8378/1',
});
fail('should have thrown.');
} catch (e) {
expect(e).toBe('resetTokenReuseIfValid must be a boolean value');
}
try {
await reconfigureServer({
appName: 'passwordPolicy',
emailAdapter: emailAdapter,
passwordPolicy: {
resetTokenReuseIfValid: true,
},
publicServerURL: 'http://localhost:8378/1',
});
fail('should have thrown.');
} catch (e) {
expect(e).toBe('You cannot use resetTokenReuseIfValid without resetTokenValidityDuration');
}
done();
});
it('should fail if passwordPolicy.resetTokenValidityDuration is not a number', done => {
reconfigureServer({
appName: 'passwordPolicy',