From 5ed0885440e0beafe5b5cc9f53d45958b21190a6 Mon Sep 17 00:00:00 2001 From: Manuel Trezza Date: Sat, 16 Nov 2019 04:52:57 +0100 Subject: [PATCH] added afterLogout trigger (#6217) * added afterLogout trigger * added verification of session object in tests * removed obsolete code * removed unsued code * improved tests to verify user ID --- spec/CloudCode.spec.js | 40 +++++++++++++++++++++++++++++++++-- spec/ParseUser.spec.js | 18 ++++++++++++++++ src/Routers/UsersRouter.js | 12 +++++++++++ src/cloud-code/Parse.Cloud.js | 35 ++++++++++++++++++++++++++++++ src/triggers.js | 15 +++++++++---- 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index e233a34e3b5..d9a6be30f9a 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -2219,10 +2219,14 @@ describe('afterFind hooks', () => { it('should validate triggers correctly', () => { expect(() => { Parse.Cloud.beforeSave('_Session', () => {}); - }).toThrow('Triggers are not supported for _Session class.'); + }).toThrow( + 'Only the afterLogout trigger is allowed for the _Session class.' + ); expect(() => { Parse.Cloud.afterSave('_Session', () => {}); - }).toThrow('Triggers are not supported for _Session class.'); + }).toThrow( + 'Only the afterLogout trigger is allowed for the _Session class.' + ); expect(() => { Parse.Cloud.beforeSave('_PushStatus', () => {}); }).toThrow('Only afterSave is allowed on _PushStatus'); @@ -2247,6 +2251,22 @@ describe('afterFind hooks', () => { expect(() => { Parse.Cloud.beforeLogin('SomeClass', () => {}); }).toThrow('Only the _User class is allowed for the beforeLogin trigger'); + expect(() => { + Parse.Cloud.afterLogout(() => {}); + }).not.toThrow(); + expect(() => { + Parse.Cloud.afterLogout('_Session', () => {}); + }).not.toThrow(); + expect(() => { + Parse.Cloud.afterLogout('_User', () => {}); + }).toThrow( + 'Only the _Session class is allowed for the afterLogout trigger.' + ); + expect(() => { + Parse.Cloud.afterLogout('SomeClass', () => {}); + }).toThrow( + 'Only the _Session class is allowed for the afterLogout trigger.' + ); }); it('should skip afterFind hooks for aggregate', done => { @@ -2436,6 +2456,22 @@ describe('beforeLogin hook', () => { done(); }); + it('should trigger afterLogout hook on logout', async done => { + let userId; + Parse.Cloud.afterLogout(req => { + expect(req.object.className).toEqual('_Session'); + expect(req.object.id).toBeDefined(); + const user = req.object.get('user'); + expect(user).toBeDefined(); + userId = user.id; + }); + + const user = await Parse.User.signUp('user', 'pass'); + await Parse.User.logOut(); + expect(user.id).toBe(userId); + done(); + }); + it('should have expected data in request', async done => { Parse.Cloud.beforeLogin(req => { expect(req.object).toBeDefined(); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index c04c33de1f1..805ef8f3e01 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -1523,6 +1523,24 @@ describe('Parse.User testing', () => { done(); }); + it('logout with provider should call afterLogout trigger', async done => { + const provider = getMockFacebookProvider(); + Parse.User._registerAuthenticationProvider(provider); + + let userId; + Parse.Cloud.afterLogout(req => { + expect(req.object.className).toEqual('_Session'); + expect(req.object.id).toBeDefined(); + const user = req.object.get('user'); + expect(user).toBeDefined(); + userId = user.id; + }); + const user = await Parse.User._logInWith('facebook'); + await Parse.User.logOut(); + expect(user.id).toBe(userId); + done(); + }); + it('link with provider', async done => { const provider = getMockFacebookProvider(); Parse.User._registerAuthenticationProvider(provider); diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index 7af3f31ed48..1da42681ece 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -302,6 +302,7 @@ export class UsersRouter extends ClassesRouter { records.results[0].objectId ) .then(() => { + this._runAfterLogoutTrigger(req, records.results[0]); return Promise.resolve(success); }); } @@ -311,6 +312,17 @@ export class UsersRouter extends ClassesRouter { return Promise.resolve(success); } + _runAfterLogoutTrigger(req, session) { + // After logout trigger + maybeRunTrigger( + TriggerTypes.afterLogout, + req.auth, + Parse.Session.fromJSON(Object.assign({ className: '_Session' }, session)), + null, + req.config + ); + } + _throwOnBadEmailConfig(req) { try { Config.validateEmailConfiguration({ diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 9039c3ef2fe..5ad6769d3f0 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -165,6 +165,41 @@ ParseCloud.beforeLogin = function(handler) { ); }; +/** + * + * Registers the after logout function. + * + * **Available in Cloud Code only.** + * + * This function is triggered after a user logs out. + * + * ``` + * Parse.Cloud.afterLogout((request) => { + * // code here + * }) + * + * ``` + * + * @method afterLogout + * @name Parse.Cloud.afterLogout + * @param {Function} func The function to run after a logout. This function can be async and should take one parameter a {@link Parse.Cloud.TriggerRequest}; + */ +ParseCloud.afterLogout = function(handler) { + let className = '_Session'; + 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.afterLogout, + className, + handler, + Parse.applicationId + ); +}; + /** * Registers an after save function. * diff --git a/src/triggers.js b/src/triggers.js index 07ef6dc1fda..85108a42252 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -4,6 +4,7 @@ import { logger } from './logger'; export const Types = { beforeLogin: 'beforeLogin', + afterLogout: 'afterLogout', beforeSave: 'beforeSave', afterSave: 'afterSave', beforeDelete: 'beforeDelete', @@ -32,10 +33,6 @@ const baseStore = function() { }; function validateClassNameForTriggers(className, type) { - const restrictedClassNames = ['_Session']; - if (restrictedClassNames.indexOf(className) != -1) { - throw `Triggers are not supported for ${className} class.`; - } if (type == Types.beforeSave && className === '_PushStatus') { // _PushStatus uses undocumented nested key increment ops // allowing beforeSave would mess up the objects big time @@ -47,6 +44,16 @@ function validateClassNameForTriggers(className, type) { // than this anti-pattern of throwing strings throw 'Only the _User class is allowed for the beforeLogin trigger'; } + if (type === Types.afterLogout && className !== '_Session') { + // TODO: check if upstream code will handle `Error` instance rather + // than this anti-pattern of throwing strings + throw 'Only the _Session class is allowed for the afterLogout trigger.'; + } + if (className === '_Session' && type !== Types.afterLogout) { + // TODO: check if upstream code will handle `Error` instance rather + // than this anti-pattern of throwing strings + throw 'Only the afterLogout trigger is allowed for the _Session class.'; + } return className; }