Skip to content

Commit d486cde

Browse files
committed
feat: allow the master key to bypass email verification (parse-community#8230)
1 parent 4af13af commit d486cde

File tree

2 files changed

+197
-5
lines changed

2 files changed

+197
-5
lines changed

spec/ParseUser.spec.js

+188
Original file line numberDiff line numberDiff line change
@@ -2195,6 +2195,153 @@ describe('Parse.User testing', () => {
21952195
);
21962196
});
21972197

2198+
it('master key can bypass email verification for new users and changes to the email', done => {
2199+
const user = new Parse.User();
2200+
2201+
const emailAdapter = {
2202+
sendVerificationEmail: () => {
2203+
jfail('should not send email');
2204+
},
2205+
sendPasswordResetEmail: () => {
2206+
jfail('should not send email');
2207+
return Promise.resolve();
2208+
},
2209+
sendMail: () => {
2210+
jfail('should not send email');
2211+
},
2212+
};
2213+
const serverConfig = {
2214+
appName: 'emailVerifyToken',
2215+
verifyUserEmails: true,
2216+
preventLoginWithUnverifiedEmail: true,
2217+
emailAdapter: emailAdapter,
2218+
emailVerifyTokenValidityDuration: 5, // 5 seconds
2219+
publicServerURL: 'http://localhost:8378/1',
2220+
};
2221+
2222+
reconfigureServer(serverConfig)
2223+
.then(() => {
2224+
user.setUsername('masterKeyBypassEmailVerification');
2225+
user.setPassword('expiringToken');
2226+
user.set('email', '[email protected]');
2227+
user.set('emailVerified', true);
2228+
return user.save(null, { useMasterKey: true });
2229+
})
2230+
.then(() => {
2231+
const config = Config.get('test');
2232+
return config.database
2233+
.find('_User', { username: 'masterKeyBypassEmailVerification' })
2234+
.then(results => {
2235+
return results[0];
2236+
});
2237+
})
2238+
.then(userFromDb => {
2239+
expect(typeof userFromDb).toBe('object');
2240+
expect(userFromDb['emailVerified']).toBe(true);
2241+
expect(userFromDb['email']).toBe('[email protected]');
2242+
expect(user.getSessionToken()).toBeDefined();
2243+
2244+
// change the email
2245+
user.set('email', '[email protected]');
2246+
user.set('emailVerified', true);
2247+
return new Promise(resolve => {
2248+
setTimeout(() => resolve(user.save(null, { useMasterKey: true })), 500);
2249+
});
2250+
})
2251+
.then(() => {
2252+
const config = Config.get('test');
2253+
return config.database
2254+
.find('_User', { username: 'masterKeyBypassEmailVerification' })
2255+
.then(results => {
2256+
return results[0];
2257+
});
2258+
})
2259+
.then(userAfterEmailReset => {
2260+
expect(typeof userAfterEmailReset).toBe('object');
2261+
expect(userAfterEmailReset['emailVerified']).toBe(true);
2262+
expect(userAfterEmailReset['email']).toBe('[email protected]');
2263+
done();
2264+
})
2265+
.catch(error => {
2266+
jfail(error);
2267+
done();
2268+
});
2269+
});
2270+
2271+
it('email verification happens even for signups/email changes by the master key', done => {
2272+
const user = new Parse.User();
2273+
2274+
let sendVerificationEmailCount = 0;
2275+
const emailAdapter = {
2276+
sendVerificationEmail: () => {
2277+
sendVerificationEmailCount += 1;
2278+
},
2279+
sendPasswordResetEmail: () => {
2280+
jfail('should only send verification email');
2281+
return Promise.resolve();
2282+
},
2283+
sendMail: () => {
2284+
jfail('should only send verification email');
2285+
},
2286+
};
2287+
const serverConfig = {
2288+
appName: 'emailVerifyToken',
2289+
verifyUserEmails: true,
2290+
preventLoginWithUnverifiedEmail: true,
2291+
emailAdapter: emailAdapter,
2292+
emailVerifyTokenValidityDuration: 5, // 5 seconds
2293+
publicServerURL: 'http://localhost:8378/1',
2294+
};
2295+
2296+
reconfigureServer(serverConfig)
2297+
.then(() => {
2298+
user.setUsername('masterKeyBypassEmailVerification');
2299+
user.setPassword('expiringToken');
2300+
user.set('email', '[email protected]');
2301+
return user.save(null, { useMasterKey: true });
2302+
})
2303+
.then(() => {
2304+
const config = Config.get('test');
2305+
return config.database
2306+
.find('_User', { username: 'masterKeyBypassEmailVerification' })
2307+
.then(results => {
2308+
return results[0];
2309+
});
2310+
})
2311+
.then(userFromDb => {
2312+
expect(typeof userFromDb).toBe('object');
2313+
expect(sendVerificationEmailCount).toBe(1);
2314+
expect(userFromDb['emailVerified']).toBe(false);
2315+
expect(userFromDb['email']).toBe('[email protected]');
2316+
expect(user.getSessionToken()).toBeUndefined();
2317+
2318+
// change the email
2319+
user.set('email', '[email protected]');
2320+
return new Promise(resolve => {
2321+
setTimeout(() => resolve(user.save(null, { useMasterKey: true })), 500);
2322+
});
2323+
})
2324+
.then(() => {
2325+
const config = Config.get('test');
2326+
return config.database
2327+
.find('_User', { username: 'masterKeyBypassEmailVerification' })
2328+
.then(results => {
2329+
return results[0];
2330+
});
2331+
})
2332+
.then(userAfterEmailReset => {
2333+
expect(typeof userAfterEmailReset).toBe('object');
2334+
expect(sendVerificationEmailCount).toBe(2);
2335+
expect(userAfterEmailReset['emailVerified']).toBe(false);
2336+
expect(userAfterEmailReset['email']).toBe('[email protected]');
2337+
done();
2338+
})
2339+
.catch(error => {
2340+
jfail(error);
2341+
done();
2342+
});
2343+
});
2344+
21982345
describe('case insensitive signup not allowed', () => {
21992346
it('signup should fail with duplicate case insensitive username with basic setter', async () => {
22002347
const user = new Parse.User();
@@ -3316,6 +3463,47 @@ describe('Parse.User testing', () => {
33163463
});
33173464
});
33183465

3466+
it('should not allow users to sign up with emailVerified set', done => {
3467+
const user = new Parse.User();
3468+
3469+
const emailAdapter = {
3470+
sendVerificationEmail: () => {
3471+
jfail('should not send email');
3472+
},
3473+
sendPasswordResetEmail: () => {
3474+
jfail('should not send email');
3475+
},
3476+
sendMail: () => {
3477+
jfail('should not send email');
3478+
},
3479+
};
3480+
const serverConfig = {
3481+
appName: 'emailVerifyToken',
3482+
verifyUserEmails: true,
3483+
preventLoginWithUnverifiedEmail: true,
3484+
emailAdapter: emailAdapter,
3485+
emailVerifyTokenValidityDuration: 5, // 5 seconds
3486+
publicServerURL: 'http://localhost:8378/1',
3487+
};
3488+
3489+
reconfigureServer(serverConfig)
3490+
.then(() => {
3491+
user.setUsername('hello');
3492+
user.setPassword('world');
3493+
user.set('email', '[email protected]');
3494+
user.set('emailVerified', true);
3495+
return user.save();
3496+
})
3497+
.then(() => {
3498+
fail('Should not be able to sign up with emailVerified');
3499+
done();
3500+
})
3501+
.catch(error => {
3502+
expect(error.message).toBe("Clients aren't allowed to manually update email verification.");
3503+
done();
3504+
});
3505+
});
3506+
33193507
it('should not retrieve hidden fields on GET users/me (#3432)', done => {
33203508
const emailAdapter = {
33213509
sendVerificationEmail: () => {},

src/RestWrite.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -732,9 +732,11 @@ RestWrite.prototype._validateEmail = function () {
732732
(Object.keys(this.data.authData).length === 1 &&
733733
Object.keys(this.data.authData)[0] === 'anonymous')
734734
) {
735-
// We updated the email, send a new validation
736-
this.storage['sendVerificationEmail'] = true;
737-
this.config.userController.setEmailVerifyToken(this.data);
735+
// We updated the email, send a new verification unless the master key explicitly suppresses it
736+
if (!this.auth.isMaster || !this.data['emailVerified']) {
737+
this.storage['sendVerificationEmail'] = true;
738+
this.config.userController.setEmailVerifyToken(this.data);
739+
}
738740
}
739741
});
740742
};
@@ -862,8 +864,10 @@ RestWrite.prototype.createSessionTokenIfNeeded = function () {
862864
this.config.preventLoginWithUnverifiedEmail && // no login without verification
863865
this.config.verifyUserEmails
864866
) {
865-
// verification is on
866-
return; // do not create the session token in that case!
867+
// verification is on and the master key has not explicitly suppressed verification
868+
if (!this.auth.isMaster || !this.data['emailVerified']) {
869+
return; // do not create the session token in that case!
870+
}
867871
}
868872
return this.createSessionToken();
869873
};

0 commit comments

Comments
 (0)