Prevent banned user from trying to validate auth token multiple times. close #9798

This commit is contained in:
Joao Siebel 2020-06-12 13:24:11 -03:00
parent 6da65c3c84
commit 45f99fed8b
6 changed files with 95 additions and 33 deletions

View File

@ -9,6 +9,7 @@ import createNote from '/imports/api/note/server/methods/createNote';
import createCaptions from '/imports/api/captions/server/methods/createCaptions'; import createCaptions from '/imports/api/captions/server/methods/createCaptions';
import { addAnnotationsStreamer } from '/imports/api/annotations/server/streamer'; import { addAnnotationsStreamer } from '/imports/api/annotations/server/streamer';
import { addCursorStreamer } from '/imports/api/cursor/server/streamer'; import { addCursorStreamer } from '/imports/api/cursor/server/streamer';
import BannedUsers from '/imports/api/users/server/store/bannedUsers';
export default function addMeeting(meeting) { export default function addMeeting(meeting) {
const meetingId = meeting.meetingProp.intId; const meetingId = meeting.meetingProp.intId;
@ -145,6 +146,7 @@ export default function addMeeting(meeting) {
// better place we can run this post-creation routine? // better place we can run this post-creation routine?
createNote(meetingId); createNote(meetingId);
createCaptions(meetingId); createCaptions(meetingId);
BannedUsers.init(meetingId);
} }
if (numChanged) { if (numChanged) {

View File

@ -15,7 +15,12 @@ const clearOtherSessions = (sessionUserId, current = false) => {
}; };
export default function handleValidateAuthToken({ body }, meetingId) { export default function handleValidateAuthToken({ body }, meetingId) {
const { userId, valid, authToken, waitForApproval } = body; const {
userId,
valid,
authToken,
waitForApproval,
} = body;
check(userId, String); check(userId, String);
check(authToken, String); check(authToken, String);
@ -24,50 +29,50 @@ export default function handleValidateAuthToken({ body }, meetingId) {
const pendingAuths = pendingAuthenticationsStore.take(meetingId, userId, authToken); const pendingAuths = pendingAuthenticationsStore.take(meetingId, userId, authToken);
if(!valid) { if (!valid) {
pendingAuths.forEach ( pendingAuths.forEach(
pendingAuth => { (pendingAuth) => {
try { try {
const {methodInvocationObject} = pendingAuth; const { methodInvocationObject } = pendingAuth;
const connectionId = methodInvocationObject.connection.id; const connectionId = methodInvocationObject.connection.id;
// Schedule socket disconnection for this user, giving some time for client receiving the reason of disconnection // Schedule socket disconnection for this user, giving some time for client receiving the reason of disconnection
Meteor.setTimeout(()=>{ Meteor.setTimeout(() => {
methodInvocationObject.connection.close(); methodInvocationObject.connection.close();
}, 2000); }, 2000);
Logger.info(`Closed connection ${connectionId} due to invalid auth token.`); Logger.info(`Closed connection ${connectionId} due to invalid auth token.`);
} catch (e) { } catch (e) {
Logger.error(`Error closing socket for meetingId '${meetingId}', userId '${userId}', authToken ${authToken}`); Logger.error(`Error closing socket for meetingId '${meetingId}', userId '${userId}', authToken ${authToken}`);
} }
} },
); );
return; return;
} }
if(valid) { if (valid) {
// Define user ID on connections // Define user ID on connections
pendingAuths.forEach ( pendingAuths.forEach(
pendingAuth => { (pendingAuth) => {
const {methodInvocationObject} = pendingAuth; const { methodInvocationObject } = pendingAuth;
/* Logic migrated from validateAuthToken method ( postponed to only run in case of success response ) - Begin */ /* Logic migrated from validateAuthToken method ( postponed to only run in case of success response ) - Begin */
const sessionId = `${meetingId}--${userId}`; const sessionId = `${meetingId}--${userId}`;
methodInvocationObject.setUserId(sessionId); methodInvocationObject.setUserId(sessionId);
const User = Users.findOne({ const User = Users.findOne({
meetingId, meetingId,
userId: userId, userId,
}); });
if (!User) { if (!User) {
createDummyUser(meetingId, userId, authToken); createDummyUser(meetingId, userId, authToken);
}
setConnectionIdAndAuthToken(meetingId, userId, methodInvocationObject.connection.id, authToken);
/* End of logic migrated from validateAuthToken */
} }
setConnectionIdAndAuthToken(meetingId, userId, methodInvocationObject.connection.id, authToken);
/* End of logic migrated from validateAuthToken */
},
); );
} }
@ -116,4 +121,4 @@ export default function handleValidateAuthToken({ body }, meetingId) {
}; };
Users.update(selector, modifier, cb); Users.update(selector, modifier, cb);
} }

View File

@ -2,6 +2,8 @@ import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check'; import { check } from 'meteor/check';
import RedisPubSub from '/imports/startup/server/redis'; import RedisPubSub from '/imports/startup/server/redis';
import { extractCredentials } from '/imports/api/common/server/helpers'; import { extractCredentials } from '/imports/api/common/server/helpers';
import Users from '/imports/api/users';
import BannedUsers from '/imports/api/users/server/store/bannedUsers';
export default function removeUser(userId, banUser) { export default function removeUser(userId, banUser) {
const REDIS_CONFIG = Meteor.settings.private.redis; const REDIS_CONFIG = Meteor.settings.private.redis;
@ -18,5 +20,9 @@ export default function removeUser(userId, banUser) {
banUser, banUser,
}; };
const removedUser = Users.findOne({ meetingId, userId }, { extId: 1 });
if (banUser && removedUser) BannedUsers.add(meetingId, removedUser.extId);
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, ejectedBy, payload); return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, ejectedBy, payload);
} }

View File

@ -2,12 +2,16 @@ import { Meteor } from 'meteor/meteor';
import RedisPubSub from '/imports/startup/server/redis'; import RedisPubSub from '/imports/startup/server/redis';
import Logger from '/imports/startup/server/logger'; import Logger from '/imports/startup/server/logger';
import pendingAuthenticationsStore from '../store/pendingAuthentications'; import pendingAuthenticationsStore from '../store/pendingAuthentications';
import BannedUsers from '../store/bannedUsers';
export default function validateAuthToken(meetingId, requesterUserId, requesterToken) { export default function validateAuthToken(meetingId, requesterUserId, requesterToken, externalId) {
const REDIS_CONFIG = Meteor.settings.private.redis; const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ValidateAuthTokenReqMsg'; const EVENT_NAME = 'ValidateAuthTokenReqMsg';
// Check if externalId is banned from the meeting
if (externalId && BannedUsers.has(meetingId, externalId)) return;
// Store reference of methodInvocationObject ( to postpone the connection userId definition ) // Store reference of methodInvocationObject ( to postpone the connection userId definition )
pendingAuthenticationsStore.add(meetingId, requesterUserId, requesterToken, this); pendingAuthenticationsStore.add(meetingId, requesterUserId, requesterToken, this);

View File

@ -0,0 +1,35 @@
import Logger from '/imports/startup/server/logger';
class BannedUsers {
constructor() {
Logger.debug('BannedUsers :: Initializing');
this.store = {};
}
init(meetingId) {
Logger.debug('BannedUsers :: init', meetingId);
if (!this.store[meetingId]) this.store[meetingId] = new Set();
}
add(meetingId, externalId) {
Logger.debug('BannedUsers :: add', { meetingId, externalId });
if (!this.store[meetingId]) this.store[meetingId] = new Set();
this.store[meetingId].add(externalId);
}
delete(meetingId) {
Logger.debug('BannedUsers :: delete', meetingId);
delete this.store[meetingId];
}
has(meetingId, externalId) {
Logger.debug('BannedUsers :: has', { meetingId, externalId });
if (!this.store[meetingId]) this.store[meetingId] = new Set();
return this.store[meetingId].has(externalId);
}
}
export default new BannedUsers();

View File

@ -207,7 +207,7 @@ class Auth {
} }
validateAuthToken() { validateAuthToken() {
return new Promise((resolve, reject) => { return new Promise(async (resolve, reject) => {
let computation = null; let computation = null;
const validationTimeout = setTimeout(() => { const validationTimeout = setTimeout(() => {
@ -218,9 +218,18 @@ class Auth {
}); });
}, CONNECTION_TIMEOUT); }, CONNECTION_TIMEOUT);
const result = await makeCall('validateAuthToken', this.meetingID, this.userID, this.token, this.externUserID);
if (!result) {
reject({
error: 401,
description: 'User has been banned.',
});
return;
}
Tracker.autorun((c) => { Tracker.autorun((c) => {
computation = c; computation = c;
makeCall('validateAuthToken', this.meetingID, this.userID, this.token);
Meteor.subscribe('current-user'); Meteor.subscribe('current-user');
const selector = { meetingId: this.meetingID, userId: this.userID }; const selector = { meetingId: this.meetingID, userId: this.userID };
@ -237,6 +246,7 @@ class Auth {
} }
if (User.ejected) { if (User.ejected) {
computation.stop();
reject({ reject({
error: 401, error: 401,
description: 'User has been ejected.', description: 'User has been ejected.',
@ -271,4 +281,4 @@ class Auth {
} }
const AuthSingleton = new Auth(); const AuthSingleton = new Auth();
export default AuthSingleton; export default AuthSingleton;