Add new afterLogin cloud code hook (#6387)

* add new afterLogin cloud code hook

* include user on req.user for afterLogin hook
This commit is contained in:
David Corona
2020-02-11 17:38:14 -06:00
committed by GitHub
parent 8e195ef5ae
commit 09a1dca5e3
4 changed files with 145 additions and 7 deletions

View File

@@ -2269,21 +2269,43 @@ describe('afterFind hooks', () => {
expect(() => { expect(() => {
Parse.Cloud.beforeLogin(() => {}); Parse.Cloud.beforeLogin(() => {});
}).not.toThrow( }).not.toThrow(
'Only the _User class is allowed for the beforeLogin trigger' 'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
); );
expect(() => { expect(() => {
Parse.Cloud.beforeLogin('_User', () => {}); Parse.Cloud.beforeLogin('_User', () => {});
}).not.toThrow( }).not.toThrow(
'Only the _User class is allowed for the beforeLogin trigger' 'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
); );
expect(() => { expect(() => {
Parse.Cloud.beforeLogin(Parse.User, () => {}); Parse.Cloud.beforeLogin(Parse.User, () => {});
}).not.toThrow( }).not.toThrow(
'Only the _User class is allowed for the beforeLogin trigger' 'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
); );
expect(() => { expect(() => {
Parse.Cloud.beforeLogin('SomeClass', () => {}); Parse.Cloud.beforeLogin('SomeClass', () => {});
}).toThrow('Only the _User class is allowed for the beforeLogin trigger'); }).toThrow(
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
);
expect(() => {
Parse.Cloud.afterLogin(() => {});
}).not.toThrow(
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
);
expect(() => {
Parse.Cloud.afterLogin('_User', () => {});
}).not.toThrow(
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
);
expect(() => {
Parse.Cloud.afterLogin(Parse.User, () => {});
}).not.toThrow(
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
);
expect(() => {
Parse.Cloud.afterLogin('SomeClass', () => {});
}).toThrow(
'Only the _User class is allowed for the beforeLogin and afterLogin triggers'
);
expect(() => { expect(() => {
Parse.Cloud.afterLogout(() => {}); Parse.Cloud.afterLogout(() => {});
}).not.toThrow(); }).not.toThrow();
@@ -2574,3 +2596,66 @@ describe('beforeLogin hook', () => {
expect(afterFinds).toEqual(1); expect(afterFinds).toEqual(1);
}); });
}); });
describe('afterLogin hook', () => {
it('should run afterLogin after successful login', async done => {
let hit = 0;
Parse.Cloud.afterLogin(req => {
hit++;
expect(req.object.get('username')).toEqual('testuser');
});
await Parse.User.signUp('testuser', 'p@ssword');
const user = await Parse.User.logIn('testuser', 'p@ssword');
expect(hit).toBe(1);
expect(user).toBeDefined();
expect(user.getUsername()).toBe('testuser');
expect(user.getSessionToken()).toBeDefined();
done();
});
it('should not run afterLogin after unsuccessful login', async done => {
let hit = 0;
Parse.Cloud.afterLogin(req => {
hit++;
expect(req.object.get('username')).toEqual('testuser');
});
await Parse.User.signUp('testuser', 'p@ssword');
try {
await Parse.User.logIn('testuser', 'badpassword');
} catch (e) {
expect(e.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
}
expect(hit).toBe(0);
done();
});
it('should not run afterLogin on sign up', async done => {
let hit = 0;
Parse.Cloud.afterLogin(req => {
hit++;
expect(req.object.get('username')).toEqual('testuser');
});
const user = await Parse.User.signUp('testuser', 'p@ssword');
expect(user).toBeDefined();
expect(hit).toBe(0);
done();
});
it('should have expected data in request', async done => {
Parse.Cloud.afterLogin(req => {
expect(req.object).toBeDefined();
expect(req.user).toBeDefined();
expect(req.headers).toBeDefined();
expect(req.ip).toBeDefined();
expect(req.installationId).toBeDefined();
expect(req.context).toBeUndefined();
});
await Parse.User.signUp('testuser', 'p@ssword');
await Parse.User.logIn('testuser', 'p@ssword');
done();
});
});

View File

@@ -264,6 +264,18 @@ export class UsersRouter extends ClassesRouter {
user.sessionToken = sessionData.sessionToken; user.sessionToken = sessionData.sessionToken;
await createSession(); await createSession();
const afterLoginUser = Parse.User.fromJSON(
Object.assign({ className: '_User' }, user)
);
maybeRunTrigger(
TriggerTypes.afterLogin,
{ ...req.auth, user: afterLoginUser },
afterLoginUser,
null,
req.config
);
return { response: user }; return { response: user };
} }

View File

@@ -165,6 +165,42 @@ ParseCloud.beforeLogin = function(handler) {
); );
}; };
/**
*
* Registers the after login function.
*
* **Available in Cloud Code only.**
*
* This function is triggered after a user logs in successfully,
* and after a _Session object has been created.
*
* ```
* Parse.Cloud.afterLogin((request) => {
* // code here
* })
*
* ```
*
* @method afterLogin
* @name Parse.Cloud.afterLogin
* @param {Function} func The function to run after a login. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest};
*/
ParseCloud.afterLogin = function(handler) {
let className = '_User';
if (typeof handler === 'string' || isParseObjectConstructor(handler)) {
// validation will occur downstream, this is to maintain internal
// code consistency with the other hook types.
className = getClassName(handler);
handler = arguments[1];
}
triggers.addTrigger(
triggers.Types.afterLogin,
className,
handler,
Parse.applicationId
);
};
/** /**
* *
* Registers the after logout function. * Registers the after logout function.

View File

@@ -4,6 +4,7 @@ import { logger } from './logger';
export const Types = { export const Types = {
beforeLogin: 'beforeLogin', beforeLogin: 'beforeLogin',
afterLogin: 'afterLogin',
afterLogout: 'afterLogout', afterLogout: 'afterLogout',
beforeSave: 'beforeSave', beforeSave: 'beforeSave',
afterSave: 'afterSave', afterSave: 'afterSave',
@@ -39,10 +40,13 @@ function validateClassNameForTriggers(className, type) {
// TODO: Allow proper documented way of using nested increment ops // TODO: Allow proper documented way of using nested increment ops
throw 'Only afterSave is allowed on _PushStatus'; throw 'Only afterSave is allowed on _PushStatus';
} }
if (type === Types.beforeLogin && className !== '_User') { if (
(type === Types.beforeLogin || type === Types.afterLogin) &&
className !== '_User'
) {
// TODO: check if upstream code will handle `Error` instance rather // TODO: check if upstream code will handle `Error` instance rather
// than this anti-pattern of throwing strings // than this anti-pattern of throwing strings
throw 'Only the _User class is allowed for the beforeLogin trigger'; throw 'Only the _User class is allowed for the beforeLogin and afterLogin triggers';
} }
if (type === Types.afterLogout && className !== '_Session') { if (type === Types.afterLogout && className !== '_Session') {
// TODO: check if upstream code will handle `Error` instance rather // TODO: check if upstream code will handle `Error` instance rather
@@ -615,7 +619,8 @@ export function maybeRunTrigger(
const promise = trigger(request); const promise = trigger(request);
if ( if (
triggerType === Types.afterSave || triggerType === Types.afterSave ||
triggerType === Types.afterDelete triggerType === Types.afterDelete ||
triggerType === Types.afterLogin
) { ) {
logTriggerAfterHook( logTriggerAfterHook(
triggerType, triggerType,