Skip to content

Commit 633a9d2

Browse files
authored
feat: Add password validation via POST request for user with unverified email using master key and option ignoreEmailVerification (#8895)
1 parent abdba68 commit 633a9d2

File tree

2 files changed

+95
-8
lines changed

2 files changed

+95
-8
lines changed

spec/VerifyUserPassword.spec.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,4 +585,83 @@ describe('Verify User Password', () => {
585585
done();
586586
});
587587
});
588+
589+
it('verify password of user with unverified email with master key and ignoreEmailVerification=true', async () => {
590+
await reconfigureServer({
591+
publicServerURL: 'http://localhost:8378/',
592+
appName: 'emailVerify',
593+
verifyUserEmails: true,
594+
preventLoginWithUnverifiedEmail: true,
595+
emailAdapter: MockEmailAdapterWithOptions({
596+
fromAddress: '[email protected]',
597+
apiKey: 'k',
598+
domain: 'd',
599+
}),
600+
});
601+
602+
const user = new Parse.User();
603+
user.setUsername('user');
604+
user.setPassword('pass');
605+
user.setEmail('[email protected]');
606+
await user.signUp();
607+
608+
const { data: res } = await request({
609+
method: 'POST',
610+
url: Parse.serverURL + '/verifyPassword',
611+
headers: {
612+
'X-Parse-Master-Key': Parse.masterKey,
613+
'X-Parse-Application-Id': Parse.applicationId,
614+
'X-Parse-REST-API-Key': 'rest',
615+
'Content-Type': 'application/json',
616+
},
617+
body: {
618+
username: 'user',
619+
password: 'pass',
620+
ignoreEmailVerification: true,
621+
},
622+
json: true,
623+
});
624+
expect(res.objectId).toBe(user.id);
625+
expect(Object.prototype.hasOwnProperty.call(res, 'sessionToken')).toEqual(false);
626+
expect(Object.prototype.hasOwnProperty.call(res, 'password')).toEqual(false);
627+
});
628+
629+
it('fails to verify password of user with unverified email with master key and ignoreEmailVerification=false', async () => {
630+
await reconfigureServer({
631+
publicServerURL: 'http://localhost:8378/',
632+
appName: 'emailVerify',
633+
verifyUserEmails: true,
634+
preventLoginWithUnverifiedEmail: true,
635+
emailAdapter: MockEmailAdapterWithOptions({
636+
fromAddress: '[email protected]',
637+
apiKey: 'k',
638+
domain: 'd',
639+
}),
640+
});
641+
642+
const user = new Parse.User();
643+
user.setUsername('user');
644+
user.setPassword('pass');
645+
user.setEmail('[email protected]');
646+
await user.signUp();
647+
648+
const res = await request({
649+
method: 'POST',
650+
url: Parse.serverURL + '/verifyPassword',
651+
headers: {
652+
'X-Parse-Master-Key': Parse.masterKey,
653+
'X-Parse-Application-Id': Parse.applicationId,
654+
'X-Parse-REST-API-Key': 'rest',
655+
'Content-Type': 'application/json',
656+
},
657+
body: {
658+
username: 'user',
659+
password: 'pass',
660+
ignoreEmailVerification: false,
661+
},
662+
json: true,
663+
}).catch(e => e);
664+
expect(res.status).toBe(400);
665+
expect(res.text).toMatch(/User email is not verified/);
666+
});
588667
});

src/Routers/UsersRouter.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class UsersRouter extends ClassesRouter {
7575
) {
7676
payload = req.query;
7777
}
78-
const { username, email, password } = payload;
78+
const { username, email, password, ignoreEmailVerification } = payload;
7979

8080
// TODO: use the right error codes / descriptions.
8181
if (!username && !email) {
@@ -144,13 +144,18 @@ export class UsersRouter extends ClassesRouter {
144144
installationId: req.auth.installationId,
145145
object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)),
146146
};
147-
// Get verification conditions which can be booleans or functions; the purpose of this async/await
148-
// structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the
149-
// conditional statement below, as a developer may decide to execute expensive operations in them
150-
const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true);
151-
const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true);
152-
if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) {
153-
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
147+
148+
// If request doesn't use master or maintenance key with ignoring email verification
149+
if (!((req.auth.isMaster || req.auth.isMaintenance) && ignoreEmailVerification)) {
150+
151+
// Get verification conditions which can be booleans or functions; the purpose of this async/await
152+
// structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the
153+
// conditional statement below, as a developer may decide to execute expensive operations in them
154+
const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true);
155+
const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true);
156+
if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) {
157+
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
158+
}
154159
}
155160

156161
this._sanitizeAuthData(user);
@@ -658,6 +663,9 @@ export class UsersRouter extends ClassesRouter {
658663
this.route('GET', '/verifyPassword', req => {
659664
return this.handleVerifyPassword(req);
660665
});
666+
this.route('POST', '/verifyPassword', req => {
667+
return this.handleVerifyPassword(req);
668+
});
661669
this.route('POST', '/challenge', req => {
662670
return this.handleChallenge(req);
663671
});

0 commit comments

Comments
 (0)