From 7360d2796d621b75adfa19f8beb57cfea3a3aed0 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 19 Aug 2020 14:11:34 +1000 Subject: [PATCH 1/4] Update CloudCode.js Hi all, Not sure if my formatting is perfect but closes #1176 (removes pre. SDK 2.0 success functions), and adds documentation for LiveQuery triggers. Also, for me, when I google "Parse Javascript SDK", it takes me to v1.11.0. Is there any way we can add "This SDK is outdated" or some other warning to point users to the latest SDK? Thank you! --- src/CloudCode.js | 85 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/src/CloudCode.js b/src/CloudCode.js index 5169834a2..04f6d7e47 100644 --- a/src/CloudCode.js +++ b/src/CloudCode.js @@ -62,11 +62,11 @@ * * If you want to use beforeDelete for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User}), you should pass the class itself and not the String for arg1. * ``` - * Parse.Cloud.beforeDelete('MyCustomClass', (request, response) => { + * Parse.Cloud.beforeDelete('MyCustomClass', (request) => { * // code here * }) * - * Parse.Cloud.beforeDelete(Parse.User, (request, response) => { + * Parse.Cloud.beforeDelete(Parse.User, (request) => { * // code here * }) *``` @@ -74,7 +74,7 @@ * @method beforeDelete * @name Parse.Cloud.beforeDelete * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before delete function for. This can instead be a String that is the className of the subclass. - * @param {Function} func The function to run before a delete. This function should take two parameters a {@link Parse.Cloud.TriggerRequest} and a {@link Parse.Cloud.BeforeDeleteResponse}. + * @param {Function} func The function to run after a save. This function should take just one parameter, {@link Parse.Cloud.TriggerRequest}. */ /** @@ -86,11 +86,11 @@ * If you want to use beforeSave for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User}), you should pass the class itself and not the String for arg1. * * ``` - * Parse.Cloud.beforeSave('MyCustomClass', (request, response) => { + * Parse.Cloud.beforeSave('MyCustomClass', (request) => { * // code here * }) * - * Parse.Cloud.beforeSave(Parse.User, (request, response) => { + * Parse.Cloud.beforeSave(Parse.User, (request) => { * // code here * }) * ``` @@ -98,7 +98,7 @@ * @method beforeSave * @name Parse.Cloud.beforeSave * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass. - * @param {Function} func The function to run before a save. This function should take two parameters a {@link Parse.Cloud.TriggerRequest} and a {@link Parse.Cloud.BeforeSaveResponse}. + * @param {Function} func The function to run after a save. This function should take just one parameter, {@link Parse.Cloud.TriggerRequest}. */ /** @@ -166,6 +166,51 @@ * @param {Function} func The function to run after a file saves. This function should take one parameter, a {@link Parse.Cloud.FileTriggerRequest}. */ +/** + * @method beforeConnect + * @name Parse.Cloud.beforeConnect + * @param {Function} func The function to before connection is made. This function can be async and should take just one parameter, {@link Parse.Cloud.ConnectTriggerRequest}. + */ +/** + * + * Registers a before connect function. + * + * **Available in Cloud Code only.** + * + * Example: restrict LiveQueries to logged in users. + * ``` + * Parse.Cloud.beforeConnect((request) => { + * if (!request.user) { + * throw "Please login before you attempt to connect." + * } + * }); + * ``` +*/ + +/** + * @method beforeSubscribe + * @name Parse.Cloud.beforeSubscribe + * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before subscription function for. This can instead be a String that is the className of the subclass. + * @param {Function} func The function to run before a subscription. This function can be async and should take one parameter, a {@link Parse.Cloud.TriggerRequest}. + */ +/** + * + * Registers a before subscribe function. + * + * **Available in Cloud Code only.** + * Example: restrict subscriptions to MyObject to Admin accounts only. + * ``` + * Parse.Cloud.beforeSubscribe('MyObject', (request) => { + * if (!request.user.get('Admin')) { + * throw new Parse.Error(101, 'You are not authorized to subscribe to MyObject.'); + * } + * let query = request.query; // the Parse.Query + * query.select("name","year") + * }); + * ``` +*/ + + /** * Makes an HTTP Request. * @@ -229,6 +274,16 @@ * @property {Object} log The current logger inside Parse Server. */ +/** + * @typedef Parse.Cloud.ConnectTriggerRequest + * @property {String} installationId If set, the installationId triggering the request. + * @property {Boolean} useMasterKey If true, means the master key was used. + * @property {Parse.User} user If set, the user that made the request. + * @property {Integer} clients The number of clients connected. + * @property {Integer} subscriptions The number of subscriptions connected. + * @property {String} sessionToken If set, the session of the user that made the request. + */ + /** * @typedef Parse.Cloud.FunctionRequest * @property {String} installationId If set, the installationId triggering the request. @@ -249,24 +304,6 @@ * @property {function} success If success is called, will end the job successfullly with the optional completion message to be stored in the job status. */ -/** - * @typedef Parse.Cloud.BeforeSaveResponse - * @property {function} success If called, will allow the save to happen. If a Parse.Object is passed in, then the passed in object will be saved instead. - * @property {function} error If called, will reject the save. An optional error message may be passed in. - */ - -/** - * @typedef Parse.Cloud.BeforeDeleteResponse - * @property {function} success If called, will allow the delete to happen. - * @property {function} error If called, will reject the save. An optional error message may be passed in. - */ - -/** - * @typedef Parse.Cloud.FunctionResponse - * @property {function} success If success is called, will return a successful response with the optional argument to the caller. - * @property {function} error If error is called, will return an error response with an optionally passed message. - */ - /** * @typedef Parse.Cloud.HTTPOptions * @property {String|Object} body The body of the request. If it is a JSON object, then the Content-Type set in the headers must be application/x-www-form-urlencoded or application/json. You can also set this to a {@link Buffer} object to send raw bytes. If you use a Buffer, you should also set the Content-Type header explicitly to describe what these bytes represent. From ed9e32a9a843f2d80a09501b0b0bd8dc09e175b5 Mon Sep 17 00:00:00 2001 From: dblythy Date: Wed, 19 Aug 2020 14:17:07 +1000 Subject: [PATCH 2/4] Update CloudCode.js --- src/CloudCode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CloudCode.js b/src/CloudCode.js index 04f6d7e47..6278da7ac 100644 --- a/src/CloudCode.js +++ b/src/CloudCode.js @@ -74,7 +74,7 @@ * @method beforeDelete * @name Parse.Cloud.beforeDelete * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before delete function for. This can instead be a String that is the className of the subclass. - * @param {Function} func The function to run after a save. This function should take just one parameter, {@link Parse.Cloud.TriggerRequest}. + * @param {Function} func The function to run before a delete. This function should take just one parameter, {@link Parse.Cloud.TriggerRequest}. */ /** @@ -98,7 +98,7 @@ * @method beforeSave * @name Parse.Cloud.beforeSave * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the after save function for. This can instead be a String that is the className of the subclass. - * @param {Function} func The function to run after a save. This function should take just one parameter, {@link Parse.Cloud.TriggerRequest}. + * @param {Function} func The function to run before a save. This function should take just one parameter, {@link Parse.Cloud.TriggerRequest}. */ /** From 1029d8b2d1a91fc83953b025cfbb487acc0d3f50 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 2 Mar 2023 14:00:53 +1100 Subject: [PATCH 3/4] feat: allow logout with `clearSession` to logout with invalid session token --- src/ParseUser.js | 16 ++++++++++++---- src/__tests__/ParseUser-test.js | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/ParseUser.js b/src/ParseUser.js index 9e2f43d9a..d5ad1769b 100644 --- a/src/ParseUser.js +++ b/src/ParseUser.js @@ -1134,8 +1134,14 @@ const DefaultController = { logOut(options: RequestOptions): Promise { const RESTController = CoreManager.getRESTController(); + const promiseCatch = e => { + if (e.code === ParseError.INVALID_SESSION_TOKEN && options.clearSession) { + return; + } + throw e; + }; if (options.sessionToken) { - return RESTController.request('POST', 'logout', {}, options); + return RESTController.request('POST', 'logout', {}, options).catch(promiseCatch); } return DefaultController.currentUserAsync().then(currentUser => { const path = Storage.generatePath(CURRENT_USER_KEY); @@ -1143,9 +1149,11 @@ const DefaultController = { if (currentUser !== null) { const currentSession = currentUser.getSessionToken(); if (currentSession && isRevocableSession(currentSession)) { - promise = promise.then(() => { - return RESTController.request('POST', 'logout', {}, { sessionToken: currentSession }); - }); + promise = promise + .then(() => { + return RESTController.request('POST', 'logout', {}, { sessionToken: currentSession }); + }) + .catch(promiseCatch); } currentUser._logOutWithAll(); currentUser._finishFetch({ sessionToken: undefined }); diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index ba0fbda75..18e6e3f16 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -940,6 +940,40 @@ describe('ParseUser', () => { }); }); + it('can logout user with clear session', async () => { + ParseUser.disableUnsafeCurrentUser(); + ParseUser._clearCache(); + Storage._clear(); + const RESTController = { + request() { + const error = new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token.'); + return Promise.reject(error); + }, + ajax() {}, + }; + jest.spyOn(RESTController, 'request'); + CoreManager.setRESTController(RESTController); + + await ParseUser.logOut({ clearSession: true }); + }); + + it('can logout user with clear session and session token', async () => { + ParseUser.disableUnsafeCurrentUser(); + ParseUser._clearCache(); + Storage._clear(); + const RESTController = { + request() { + const error = new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token.'); + return Promise.reject(error); + }, + ajax() {}, + }; + jest.spyOn(RESTController, 'request'); + CoreManager.setRESTController(RESTController); + + await ParseUser.logOut({ sessionToken: '1234', clearSession: true }); + }); + it('can retreive a user with sessionToken (me)', async () => { ParseUser.disableUnsafeCurrentUser(); ParseUser._clearCache(); From 36c67ab3b64cfb3919c1c0393b83e5f488b9a5d2 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Tue, 15 Apr 2025 23:13:53 -0500 Subject: [PATCH 4/4] docs, types, coverage --- src/CoreManager.ts | 2 +- src/ParseUser.ts | 9 ++++++--- src/__tests__/ParseUser-test.js | 22 ++++++++++++++++++++++ types/CoreManager.d.ts | 4 +++- types/ParseUser.d.ts | 6 +++++- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/CoreManager.ts b/src/CoreManager.ts index 03e2b6002..4871b88a1 100644 --- a/src/CoreManager.ts +++ b/src/CoreManager.ts @@ -197,7 +197,7 @@ export interface UserController { loginAs: (user: ParseUser, userId: string) => Promise; become: (user: ParseUser, options?: RequestOptions) => Promise; hydrate: (user: ParseUser, userJSON: AttributeMap) => Promise; - logOut: (options?: RequestOptions) => Promise; + logOut: (options?: RequestOptions & { clearSession?: boolean }) => Promise; me: (user: ParseUser, options?: RequestOptions) => Promise; requestPasswordReset: (email: string, options?: RequestOptions) => Promise; updateUserOnDisk: (user: ParseUser) => Promise; diff --git a/src/ParseUser.ts b/src/ParseUser.ts index bc62dbecd..e636cf687 100644 --- a/src/ParseUser.ts +++ b/src/ParseUser.ts @@ -812,11 +812,13 @@ class ParseUser extends ParseObject { * current will return null. * * @param {object} options + * @param {boolean} [options.clearSession] If true, the session token will be + * removed from the user object when the session token is invalid. * @static * @returns {Promise} A promise that is resolved when the session is * destroyed on the server. */ - static logOut(options?: RequestOptions): Promise { + static logOut(options?: RequestOptions & { clearSession?: boolean }): Promise { const controller = CoreManager.getUserController(); return controller.logOut(options); } @@ -1205,10 +1207,11 @@ const DefaultController = { }); }, - logOut(options?: RequestOptions): Promise { + logOut(options?: RequestOptions & { clearSession?: boolean }): Promise { const RESTController = CoreManager.getRESTController(); const promiseCatch = e => { - if (e.code === ParseError.INVALID_SESSION_TOKEN && options.clearSession) { + console.log(e, options); + if (e.code === ParseError.INVALID_SESSION_TOKEN && options?.clearSession) { return; } throw e; diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js index b1ab9f61c..fd81ae7ae 100644 --- a/src/__tests__/ParseUser-test.js +++ b/src/__tests__/ParseUser-test.js @@ -1076,6 +1076,28 @@ describe('ParseUser', () => { await ParseUser.logOut({ sessionToken: '1234', clearSession: true }); }); + it('cannot logout user with invalid session', async () => { + expect.assertions(2); + ParseUser.disableUnsafeCurrentUser(); + ParseUser._clearCache(); + Storage._clear(); + const RESTController = { + request() { + const error = new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token.'); + return Promise.reject(error); + }, + ajax() {}, + }; + jest.spyOn(RESTController, 'request'); + CoreManager.setRESTController(RESTController); + try { + await ParseUser.logOut({ sessionToken: '1234', clearSession: false }); + } catch (e) { + expect(e.code).toBe(ParseError.INVALID_SESSION_TOKEN); + expect(e.message).toBe('Invalid session token.'); + } + }); + it('can retreive a user with sessionToken (me)', async () => { ParseUser.disableUnsafeCurrentUser(); ParseUser._clearCache(); diff --git a/types/CoreManager.d.ts b/types/CoreManager.d.ts index c01c1d4a9..d66014350 100644 --- a/types/CoreManager.d.ts +++ b/types/CoreManager.d.ts @@ -178,7 +178,9 @@ export interface UserController { loginAs: (user: ParseUser, userId: string) => Promise; become: (user: ParseUser, options?: RequestOptions) => Promise; hydrate: (user: ParseUser, userJSON: AttributeMap) => Promise; - logOut: (options?: RequestOptions) => Promise; + logOut: (options?: RequestOptions & { + clearSession?: boolean; + }) => Promise; me: (user: ParseUser, options?: RequestOptions) => Promise; requestPasswordReset: (email: string, options?: RequestOptions) => Promise; updateUserOnDisk: (user: ParseUser) => Promise; diff --git a/types/ParseUser.d.ts b/types/ParseUser.d.ts index 8e08dc2a5..88fd74a6e 100644 --- a/types/ParseUser.d.ts +++ b/types/ParseUser.d.ts @@ -392,11 +392,15 @@ declare class ParseUser extends ParseObjectcurrent will return null. * * @param {object} options + * @param {boolean} [options.clearSession] If true, the session token will be + * removed from the user object when the session token is invalid. * @static * @returns {Promise} A promise that is resolved when the session is * destroyed on the server. */ - static logOut(options?: RequestOptions): Promise; + static logOut(options?: RequestOptions & { + clearSession?: boolean; + }): Promise; /** * Requests a password reset email to be sent to the specified email address * associated with the user account. This email allows the user to securely