Skip to content

Commit

Permalink
Remove push tokens when login tokens are removed
Browse files Browse the repository at this point in the history
  • Loading branch information
sampaiodiego committed Jun 29, 2022
1 parent d05834f commit 30c6b16
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 63 deletions.
8 changes: 6 additions & 2 deletions apps/meteor/app/push/server/methods.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { Random } from 'meteor/random';
Expand All @@ -23,6 +24,9 @@ Meteor.methods({
throw new Meteor.Error(403, 'Forbidden access');
}

// we always store the hashed token to protect users
const hashedToken = Accounts._hashLoginToken(options.authToken);

let doc;

// lookup app by id if one was included
Expand All @@ -49,7 +53,7 @@ Meteor.methods({
// Rig default doc
doc = {
token: options.token,
authToken: options.authToken,
authToken: hashedToken,
appName: options.appName,
userId: options.userId,
enabled: true,
Expand All @@ -73,7 +77,7 @@ Meteor.methods({
$set: {
updatedAt: new Date(),
token: options.token,
authToken: options.authToken,
authToken: hashedToken,
},
},
);
Expand Down
55 changes: 3 additions & 52 deletions apps/meteor/app/push/server/push.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ import { initAPN, sendAPN } from './apn';
import { sendGCM } from './gcm';
import { logger } from './logger';
import { settings } from '../../settings/server';
import { Users } from '../../models/server';

export const _matchToken = Match.OneOf({ apn: String }, { gcm: String });
export const appTokensCollection = new Mongo.Collection('_raix_push_app_tokens');

appTokensCollection._ensureIndex({ userId: 1 });

export class PushClass {
options = {};

Expand Down Expand Up @@ -78,60 +75,17 @@ export class PushClass {
return !!this.options.gateways && settings.get('Register_Server') && settings.get('Cloud_Service_Agree_PrivacyTerms');
}

_validateAuthTokenByPushToken(pushToken) {
const pushTokenQuery = appTokensCollection.findOne({ token: pushToken });

if (!pushTokenQuery) {
return false;
}

const { authToken, userId, expiration, usesLeft, _id } = pushTokenQuery;
if (!authToken) {
if (expiration && expiration > Date.now()) {
return true;
}
if (usesLeft > 0) {
appTokensCollection.rawCollection().updateOne(
{
_id,
},
{
$inc: {
usesLeft: -1,
},
},
);

return true;
}
}

const user = authToken && userId && Users.findOneByIdAndLoginToken(userId, authToken, { projection: { _id: 1 } });

if (!user) {
this._removeToken(pushToken);
return false;
}

return true;
}

sendNotificationNative(app, notification, countApn, countGcm) {
logger.debug('send to token', app.token);

const validToken = (app.token.apn || app.token.gcm) && this._validateAuthTokenByPushToken(app.token);
if (!validToken) {
throw new Error('send got a faulty query');
}

if (app.token.apn) {
countApn.push(app._id);
// Send to APN
if (this.options.apn) {
notification.topic = app.appName;
sendAPN({ userToken: app.token.apn, notification, _removeToken: this._removeToken });
}
} else {
} else if (app.token.gcm) {
countGcm.push(app._id);

// Send to GCM
Expand All @@ -146,6 +100,8 @@ export class PushClass {
options: this.options,
});
}
} else {
throw new Error('send got a faulty query');
}
}

Expand Down Expand Up @@ -211,11 +167,6 @@ export class PushClass {
for (const gateway of this.options.gateways) {
logger.debug('send to token', app.token);

const validToken = (app.token.apn || app.token.gcm) && this._validateAuthTokenByPushToken(app.token);
if (!validToken) {
continue;
}

if (app.token.apn) {
countApn.push(app._id);
notification.topic = app.appName;
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/server/models/PushToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { registerModel } from '@rocket.chat/models';

import { db } from '../database/utils';
import { PushTokenRaw } from './raw/PushToken';

registerModel('IPushTokenModel', new PushTokenRaw(db));
22 changes: 22 additions & 0 deletions apps/meteor/server/models/raw/PushToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { IPushToken } from '@rocket.chat/core-typings';
import type { IPushTokenModel } from '@rocket.chat/model-typings';
import type { Db, DeleteWriteOpResultObject, IndexSpecification } from 'mongodb';

import { BaseRaw } from './BaseRaw';

export class PushTokenRaw extends BaseRaw<IPushToken> implements IPushTokenModel {
constructor(db: Db) {
super(db, '_raix_push_app_tokens');
}

modelIndexes(): IndexSpecification[] {
return [{ key: { userId: 1, authToken: 1 } }, { key: { appName: 1, token: 1 } }];
}

removeByUserIdExceptTokens(userId: string, tokens: string[]): Promise<DeleteWriteOpResultObject> {
return this.deleteMany({
userId,
authToken: { $nin: tokens },
});
}
}
1 change: 1 addition & 0 deletions apps/meteor/server/models/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import './OAuthApps';
import './OEmbedCache';
import './OmnichannelQueue';
import './PbxEvents';
import './PushToken';
import './Permissions';
import './ReadReceipts';
import './Reports';
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/server/sdk/types/IPushService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { IServiceClass } from './ServiceClass';

export type IPushService = IServiceClass;
23 changes: 23 additions & 0 deletions apps/meteor/server/services/push/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { PushToken } from '@rocket.chat/models';

import { IPushService } from '../../sdk/types/IPushService';
import { ServiceClassInternal } from '../../sdk/types/ServiceClass';

export class PushService extends ServiceClassInternal implements IPushService {
protected name = 'push';

constructor() {
super();

this.onEvent('watch.users', async ({ id, diff }) => {
if (diff && 'services.resume.loginTokens' in diff) {
const tokens = diff['services.resume.loginTokens'].map(({ hashedToken }: { hashedToken: string }) => hashedToken);
this.cleanUpUserTokens(id, tokens);
}
});
}

private async cleanUpUserTokens(userId: string, tokens: string[]): Promise<void> {
await PushToken.removeByUserIdExceptTokens(userId, tokens);
}
}
2 changes: 2 additions & 0 deletions apps/meteor/server/services/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { UiKitCoreApp } from './uikit-core-app/service';
import { OmnichannelVoipService } from './omnichannel-voip/service';
import { VoipService } from './voip/service';
import { isRunningMs } from '../lib/isRunningMs';
import { PushService } from './push/service';

const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo;

Expand All @@ -31,6 +32,7 @@ api.registerService(new VoipService(db));
api.registerService(new OmnichannelVoipService());
api.registerService(new TeamService());
api.registerService(new UiKitCoreApp());
api.registerService(new PushService());

// if the process is running in micro services mode we don't need to register services that will run separately
if (!isRunningMs()) {
Expand Down
10 changes: 1 addition & 9 deletions apps/meteor/server/startup/migrations/v273.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ import { addMigration } from '../../lib/migrations';
addMigration({
version: 273,
async up() {
return appTokensCollection.rawCollection().updateMany(
{},
{
$set: {
expiration: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
usesLeft: 7,
},
},
);
return appTokensCollection.rawCollection().dropIndex('userId_1');
},
});
1 change: 1 addition & 0 deletions packages/model-typings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export * from './models/IOAuthAppsModel';
export * from './models/IOEmbedCacheModel';
export * from './models/IOmnichannelQueueModel';
export * from './models/IPbxEventsModel';
export * from './models/IPushTokenModel';
export * from './models/IPermissionsModel';
export * from './models/IReadReceiptsModel';
export * from './models/IReportsModel';
Expand Down
8 changes: 8 additions & 0 deletions packages/model-typings/src/models/IPushTokenModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { IPushToken } from '@rocket.chat/core-typings';
import type { DeleteWriteOpResultObject } from 'mongodb';

import type { IBaseModel } from './IBaseModel';

export interface IPushTokenModel extends IBaseModel<IPushToken> {
removeByUserIdExceptTokens(userId: string, tokens: string[]): Promise<DeleteWriteOpResultObject>;
}
2 changes: 2 additions & 0 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import type {
IOEmbedCacheModel,
IOmnichannelQueueModel,
IPbxEventsModel,
IPushTokenModel,
IPermissionsModel,
IReadReceiptsModel,
IReportsModel,
Expand Down Expand Up @@ -111,6 +112,7 @@ export const OAuthApps = proxify<IOAuthAppsModel>('IOAuthAppsModel');
export const OEmbedCache = proxify<IOEmbedCacheModel>('IOEmbedCacheModel');
export const OmnichannelQueue = proxify<IOmnichannelQueueModel>('IOmnichannelQueueModel');
export const PbxEvents = proxify<IPbxEventsModel>('IPbxEventsModel');
export const PushToken = proxify<IPushTokenModel>('IPushTokenModel');
export const Permissions = proxify<IPermissionsModel>('IPermissionsModel');
export const ReadReceipts = proxify<IReadReceiptsModel>('IReadReceiptsModel');
export const Reports = proxify<IReportsModel>('IReportsModel');
Expand Down

0 comments on commit 30c6b16

Please sign in to comment.