feat: Add event information to verifyUserEmails, preventLoginWithUnverifiedEmail to identify invoking signup / login action and auth provider (#9963)
This commit is contained in:
@@ -288,7 +288,15 @@ describe('Email Verification Token Expiration:', () => {
|
||||
};
|
||||
const verifyUserEmails = {
|
||||
method(req) {
|
||||
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip', 'installationId']);
|
||||
expect(Object.keys(req)).toEqual([
|
||||
'original',
|
||||
'object',
|
||||
'master',
|
||||
'ip',
|
||||
'installationId',
|
||||
'createdWith',
|
||||
]);
|
||||
expect(req.createdWith).toEqual({ action: 'signup', authProvider: 'password' });
|
||||
return false;
|
||||
},
|
||||
};
|
||||
@@ -349,7 +357,15 @@ describe('Email Verification Token Expiration:', () => {
|
||||
};
|
||||
const verifyUserEmails = {
|
||||
method(req) {
|
||||
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip', 'installationId']);
|
||||
expect(Object.keys(req)).toEqual([
|
||||
'original',
|
||||
'object',
|
||||
'master',
|
||||
'ip',
|
||||
'installationId',
|
||||
'createdWith',
|
||||
]);
|
||||
expect(req.createdWith).toEqual({ action: 'signup', authProvider: 'password' });
|
||||
if (req.object.get('username') === 'no_email') {
|
||||
return false;
|
||||
}
|
||||
@@ -384,6 +400,144 @@ describe('Email Verification Token Expiration:', () => {
|
||||
expect(verifySpy).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('provides createdWith on signup when verification blocks session creation', async () => {
|
||||
const verifyUserEmails = {
|
||||
method: params => {
|
||||
expect(params.object).toBeInstanceOf(Parse.User);
|
||||
expect(params.createdWith).toEqual({ action: 'signup', authProvider: 'password' });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough();
|
||||
await reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: verifyUserEmails.method,
|
||||
preventLoginWithUnverifiedEmail: true,
|
||||
preventSignupWithUnverifiedEmail: true,
|
||||
emailAdapter: MockEmailAdapterWithOptions({
|
||||
fromAddress: 'parse@example.com',
|
||||
apiKey: 'k',
|
||||
domain: 'd',
|
||||
}),
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
});
|
||||
|
||||
const user = new Parse.User();
|
||||
user.setUsername('signup_created_with');
|
||||
user.setPassword('pass');
|
||||
user.setEmail('signup@example.com');
|
||||
const res = await user.signUp().catch(e => e);
|
||||
expect(res.message).toBe('User email is not verified.');
|
||||
expect(user.getSessionToken()).toBeUndefined();
|
||||
expect(verifySpy).toHaveBeenCalledTimes(2); // before signup completion and on preventLoginWithUnverifiedEmail
|
||||
});
|
||||
|
||||
it('provides createdWith with auth provider on login verification', async () => {
|
||||
const user = new Parse.User();
|
||||
user.setUsername('user_created_with_login');
|
||||
user.setPassword('pass');
|
||||
user.set('email', 'login@example.com');
|
||||
await user.signUp();
|
||||
|
||||
const verifyUserEmails = {
|
||||
method: async params => {
|
||||
expect(params.object).toBeInstanceOf(Parse.User);
|
||||
expect(params.createdWith).toEqual({ action: 'login', authProvider: 'password' });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const verifyUserEmailsSpy = spyOn(verifyUserEmails, 'method').and.callThrough();
|
||||
await reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
verifyUserEmails: verifyUserEmails.method,
|
||||
preventLoginWithUnverifiedEmail: verifyUserEmails.method,
|
||||
preventSignupWithUnverifiedEmail: true,
|
||||
emailAdapter: MockEmailAdapterWithOptions({
|
||||
fromAddress: 'parse@example.com',
|
||||
apiKey: 'k',
|
||||
domain: 'd',
|
||||
}),
|
||||
});
|
||||
|
||||
const res = await Parse.User.logIn('user_created_with_login', 'pass').catch(e => e);
|
||||
expect(res.code).toBe(205);
|
||||
expect(verifyUserEmailsSpy).toHaveBeenCalledTimes(2); // before login completion and on preventLoginWithUnverifiedEmail
|
||||
});
|
||||
|
||||
it('provides createdWith with auth provider on signup verification', async () => {
|
||||
const createdWithValues = [];
|
||||
const verifyUserEmails = {
|
||||
method: params => {
|
||||
createdWithValues.push(params.createdWith);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough();
|
||||
await reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
verifyUserEmails: verifyUserEmails.method,
|
||||
preventLoginWithUnverifiedEmail: true,
|
||||
preventSignupWithUnverifiedEmail: true,
|
||||
emailAdapter: MockEmailAdapterWithOptions({
|
||||
fromAddress: 'parse@example.com',
|
||||
apiKey: 'k',
|
||||
domain: 'd',
|
||||
}),
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
});
|
||||
|
||||
const provider = {
|
||||
authData: { id: '8675309', access_token: 'jenny' },
|
||||
shouldError: false,
|
||||
authenticate(options) {
|
||||
options.success(this, this.authData);
|
||||
},
|
||||
restoreAuthentication() {
|
||||
return true;
|
||||
},
|
||||
getAuthType() {
|
||||
return 'facebook';
|
||||
},
|
||||
deauthenticate() {},
|
||||
};
|
||||
Parse.User._registerAuthenticationProvider(provider);
|
||||
const res = await Parse.User._logInWith('facebook').catch(e => e);
|
||||
expect(res.message).toBe('User email is not verified.');
|
||||
// Called once in createSessionTokenIfNeeded (no email set, so _validateEmail skips)
|
||||
expect(verifySpy).toHaveBeenCalledTimes(1);
|
||||
expect(createdWithValues[0]).toEqual({ action: 'signup', authProvider: 'facebook' });
|
||||
});
|
||||
|
||||
it('provides createdWith for preventLoginWithUnverifiedEmail function', async () => {
|
||||
const user = new Parse.User();
|
||||
user.setUsername('user_prevent_login_fn');
|
||||
user.setPassword('pass');
|
||||
user.set('email', 'preventlogin@example.com');
|
||||
await user.signUp();
|
||||
|
||||
const preventLoginCreatedWith = [];
|
||||
await reconfigureServer({
|
||||
appName: 'emailVerifyToken',
|
||||
publicServerURL: 'http://localhost:8378/1',
|
||||
verifyUserEmails: true,
|
||||
preventLoginWithUnverifiedEmail: params => {
|
||||
preventLoginCreatedWith.push(params.createdWith);
|
||||
return true;
|
||||
},
|
||||
emailAdapter: MockEmailAdapterWithOptions({
|
||||
fromAddress: 'parse@example.com',
|
||||
apiKey: 'k',
|
||||
domain: 'd',
|
||||
}),
|
||||
});
|
||||
|
||||
const res = await Parse.User.logIn('user_prevent_login_fn', 'pass').catch(e => e);
|
||||
expect(res.code).toBe(205);
|
||||
expect(preventLoginCreatedWith.length).toBe(1);
|
||||
expect(preventLoginCreatedWith[0]).toEqual({ action: 'login', authProvider: 'password' });
|
||||
});
|
||||
|
||||
it_id('d812de87-33d1-495e-a6e8-3485f6dc3589')(it)('can conditionally send user email verification', async () => {
|
||||
const emailAdapter = {
|
||||
sendVerificationEmail: () => {},
|
||||
@@ -779,6 +933,7 @@ describe('Email Verification Token Expiration:', () => {
|
||||
expect(params.master).toBeDefined();
|
||||
expect(params.installationId).toBeDefined();
|
||||
expect(params.resendRequest).toBeTrue();
|
||||
expect(params.createdWith).toBeUndefined();
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -284,6 +284,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
|
||||
expect(params.ip).toBeDefined();
|
||||
expect(params.master).toBeDefined();
|
||||
expect(params.installationId).toBeDefined();
|
||||
expect(params.createdWith).toEqual({ action: 'login', authProvider: 'password' });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -133,6 +133,72 @@ describe('buildConfigDefinitions', () => {
|
||||
expect(result.property.name).toBe('arrayParser');
|
||||
});
|
||||
|
||||
it('should return booleanOrFunctionParser for UnionTypeAnnotation containing boolean (nullable)', () => {
|
||||
const mockElement = {
|
||||
type: 'UnionTypeAnnotation',
|
||||
typeAnnotation: {
|
||||
types: [
|
||||
{ type: 'BooleanTypeAnnotation' },
|
||||
{ type: 'FunctionTypeAnnotation' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = mapperFor(mockElement, t);
|
||||
|
||||
expect(t.isMemberExpression(result)).toBe(true);
|
||||
expect(result.object.name).toBe('parsers');
|
||||
expect(result.property.name).toBe('booleanOrFunctionParser');
|
||||
});
|
||||
|
||||
it('should return booleanOrFunctionParser for UnionTypeAnnotation containing boolean (non-nullable)', () => {
|
||||
const mockElement = {
|
||||
type: 'UnionTypeAnnotation',
|
||||
types: [
|
||||
{ type: 'BooleanTypeAnnotation' },
|
||||
{ type: 'FunctionTypeAnnotation' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = mapperFor(mockElement, t);
|
||||
|
||||
expect(t.isMemberExpression(result)).toBe(true);
|
||||
expect(result.object.name).toBe('parsers');
|
||||
expect(result.property.name).toBe('booleanOrFunctionParser');
|
||||
});
|
||||
|
||||
it('should return undefined for UnionTypeAnnotation without boolean', () => {
|
||||
const mockElement = {
|
||||
type: 'UnionTypeAnnotation',
|
||||
typeAnnotation: {
|
||||
types: [
|
||||
{ type: 'StringTypeAnnotation' },
|
||||
{ type: 'NumberTypeAnnotation' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = mapperFor(mockElement, t);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for UnionTypeAnnotation with boolean but without function', () => {
|
||||
const mockElement = {
|
||||
type: 'UnionTypeAnnotation',
|
||||
typeAnnotation: {
|
||||
types: [
|
||||
{ type: 'BooleanTypeAnnotation' },
|
||||
{ type: 'VoidTypeAnnotation' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = mapperFor(mockElement, t);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return objectParser for unknown GenericTypeAnnotation', () => {
|
||||
const mockElement = {
|
||||
type: 'GenericTypeAnnotation',
|
||||
|
||||
@@ -3,6 +3,7 @@ const {
|
||||
numberOrBoolParser,
|
||||
numberOrStringParser,
|
||||
booleanParser,
|
||||
booleanOrFunctionParser,
|
||||
objectParser,
|
||||
arrayParser,
|
||||
moduleOrObjectParser,
|
||||
@@ -48,6 +49,23 @@ describe('parsers', () => {
|
||||
expect(parser(2)).toEqual(false);
|
||||
});
|
||||
|
||||
it('parses correctly with booleanOrFunctionParser', () => {
|
||||
const parser = booleanOrFunctionParser;
|
||||
// Preserves functions
|
||||
const fn = () => true;
|
||||
expect(parser(fn)).toBe(fn);
|
||||
const asyncFn = async () => false;
|
||||
expect(parser(asyncFn)).toBe(asyncFn);
|
||||
// Parses booleans and string booleans like booleanParser
|
||||
expect(parser(true)).toEqual(true);
|
||||
expect(parser(false)).toEqual(false);
|
||||
expect(parser('true')).toEqual(true);
|
||||
expect(parser('false')).toEqual(false);
|
||||
expect(parser('1')).toEqual(true);
|
||||
expect(parser(1)).toEqual(true);
|
||||
expect(parser(0)).toEqual(false);
|
||||
});
|
||||
|
||||
it('parses correctly with objectParser', () => {
|
||||
const parser = objectParser;
|
||||
expect(parser({ hello: 'world' })).toEqual({ hello: 'world' });
|
||||
|
||||
Reference in New Issue
Block a user