diff --git a/.changeset/poor-apricots-heal.md b/.changeset/poor-apricots-heal.md new file mode 100644 index 0000000000000..420ed96068488 --- /dev/null +++ b/.changeset/poor-apricots-heal.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes push notifications continuing after logout due to missing token cleanup. diff --git a/apps/meteor/server/services/push/service.ts b/apps/meteor/server/services/push/service.ts index 2d2c1c61ea381..50f977c0d7606 100644 --- a/apps/meteor/server/services/push/service.ts +++ b/apps/meteor/server/services/push/service.ts @@ -13,11 +13,13 @@ export class PushService extends ServiceClassInternal implements IPushService { if (!('diff' in data) || !data.diff || !('services.resume.loginTokens' in data.diff)) { return; } - if (data.diff['services.resume.loginTokens'] === undefined) { + + const loginTokens = Array.isArray(data.diff['services.resume.loginTokens']) ? data.diff['services.resume.loginTokens'] : []; + + if (data.diff['services.resume.loginTokens'] === undefined || loginTokens.length === 0) { await PushToken.removeAllByUserId(data.id); return; } - const loginTokens = Array.isArray(data.diff['services.resume.loginTokens']) ? data.diff['services.resume.loginTokens'] : []; const tokens = loginTokens.map(({ hashedToken }: { hashedToken: string }) => hashedToken); if (tokens.length > 0) { await PushToken.removeByUserIdExceptTokens(data.id, tokens); diff --git a/apps/meteor/tests/end-to-end/api/users.ts b/apps/meteor/tests/end-to-end/api/users.ts index cb3652cfdd918..1cbcfaeb1001d 100644 --- a/apps/meteor/tests/end-to-end/api/users.ts +++ b/apps/meteor/tests/end-to-end/api/users.ts @@ -4433,6 +4433,69 @@ describe('[Users]', () => { }) .then(tryAuthentication); }); + + it('should remove only logged out session push tokens', async () => { + const credentials1 = await login(user.username, password); + const credentials2 = await login(user.username, password); + + await request + .post(api('push.token')) + .set(credentials1) + .send({ + type: 'gcm', + value: 'device-1-token', + appName: 'com.example.device1', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('push.token')) + .set(credentials2) + .send({ + type: 'gcm', + value: 'device-2-token', + appName: 'com.example.device2', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.logoutOtherClients')) + .set(credentials2) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + await request + .delete(api('push.token')) + .set(credentials2) + .send({ + token: 'device-1-token', + }) + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + + await request + .delete(api('push.token')) + .set(credentials2) + .send({ + token: 'device-2-token', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); }); describe('[/users.autocomplete]', () => { @@ -4962,6 +5025,49 @@ describe('[Users]', () => { void request.post(api('users.logout')).set(userCredentials).expect('Content-Type', 'application/json').expect(200).end(done); }); }); + + it('should remove all push tokens when user logs out', async () => { + const testCredentials = await login(user.username, password); + + await request + .post(api('push.token')) + .set(testCredentials) + .send({ + type: 'gcm', + value: 'logout-test-token', + appName: 'com.example.logout.test', + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.logout')) + .set(testCredentials) + .send({ + userId: user._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const newCredentials = await login(user.username, password); + + await request + .delete(api('push.token')) + .set(newCredentials) + .send({ + token: 'logout-test-token', + }) + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); }); describe('[/users.listByStatus]', () => {