Merge pull request #13601 from Tainan404/refactor-authentication

Client authentication refactoring
This commit is contained in:
Anton Georgiev 2021-11-02 10:15:16 -04:00 committed by GitHub
commit 7551efaa9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 57 deletions

View File

@ -37,7 +37,6 @@ export default function handleValidateAuthToken({ body }, meetingId) {
check(reasonCode, String); check(reasonCode, String);
const pendingAuths = pendingAuthenticationsStore.take(meetingId, userId, authToken); const pendingAuths = pendingAuthenticationsStore.take(meetingId, userId, authToken);
Logger.info(`PendingAuths length [${pendingAuths.length}]`); Logger.info(`PendingAuths length [${pendingAuths.length}]`);
if (pendingAuths.length === 0) return; if (pendingAuths.length === 0) return;

View File

@ -2,32 +2,77 @@ 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 upsertValidationState from '/imports/api/auth-token-validation/server/modifiers/upsertValidationState'; import upsertValidationState from '/imports/api/auth-token-validation/server/modifiers/upsertValidationState';
import { ValidationStates } from '/imports/api/auth-token-validation'; import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
import pendingAuthenticationsStore from '../store/pendingAuthentications'; import pendingAuthenticationsStore from '../store/pendingAuthentications';
export default function validateAuthToken(meetingId, requesterUserId, requesterToken, externalId) { const AUTH_TIMEOUT = 120000;
try {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'ValidateAuthTokenReqMsg';
Logger.debug('ValidateAuthToken method called', { meetingId, requesterUserId, requesterToken, externalId }); async function validateAuthToken(meetingId, requesterUserId, requesterToken, externalId) {
let setTimeoutRef = null;
if (!meetingId) return false; const userValidation = await new Promise((res, rej) => {
const observeFunc = (obj) => {
// Store reference of methodInvocationObject ( to postpone the connection userId definition ) if (obj.validationStatus === ValidationStates.VALIDATED) {
pendingAuthenticationsStore.add(meetingId, requesterUserId, requesterToken, this); clearTimeout(setTimeoutRef);
upsertValidationState(meetingId, requesterUserId, ValidationStates.VALIDATING, this.connection.id); return res(obj);
}
const payload = { if (obj.validationStatus === ValidationStates.INVALID) {
userId: requesterUserId, clearTimeout(setTimeoutRef);
authToken: requesterToken, return res(obj);
}
}; };
const authTokenValidationObserver = AuthTokenValidation.find({
connectionId: this.connection.id,
}).observe({
added: observeFunc,
changed: observeFunc,
});
Logger.info(`User '${requesterUserId}' is trying to validate auth token for meeting '${meetingId}' from connection '${this.connection.id}'`); setTimeoutRef = setTimeout(() => {
observeFunc.stop();
rej();
}, AUTH_TIMEOUT);
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); try {
} catch (err) { const REDIS_CONFIG = Meteor.settings.private.redis;
Logger.error(`Exception while invoking method validateAuthToken ${err.stack}`); const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
} const EVENT_NAME = 'ValidateAuthTokenReqMsg';
Logger.debug('ValidateAuthToken method called', { meetingId, requesterUserId, requesterToken, externalId });
if (!meetingId) return false;
// Store reference of methodInvocationObject ( to postpone the connection userId definition )
pendingAuthenticationsStore.add(meetingId, requesterUserId, requesterToken, this);
upsertValidationState(
meetingId,
requesterUserId,
ValidationStates.VALIDATING,
this.connection.id,
);
const payload = {
userId: requesterUserId,
authToken: requesterToken,
};
Logger.info(`User '${requesterUserId}' is trying to validate auth token for meeting '${meetingId}' from connection '${this.connection.id}'`);
return RedisPubSub.publishUserMessage(
CHANNEL,
EVENT_NAME,
meetingId,
requesterUserId,
payload,
);
} catch (err) {
const errMsg = `Exception while invoking method validateAuthToken ${err}`;
Logger.error(errMsg);
rej(errMsg);
clearTimeout(setTimeoutRef);
authTokenValidationObserver.stop();
}
});
return userValidation;
} }
export default validateAuthToken;

View File

@ -35,7 +35,6 @@ class PendingAuthentitcations {
// find matches // find matches
const matches = this.store.filter(e => e.key === key); const matches = this.store.filter(e => e.key === key);
// remove matches (if any) // remove matches (if any)
if (matches.length) { if (matches.length) {
this.store = this.store.filter(e => e.key !== key); this.store = this.store.filter(e => e.key !== key);

View File

@ -3,7 +3,6 @@ import { withTracker } from 'meteor/react-meteor-data';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Auth from '/imports/ui/services/auth'; import Auth from '/imports/ui/services/auth';
import AuthTokenValidation from '/imports/api/auth-token-validation';
import Users from '/imports/ui/local-collections/users-collection/users'; import Users from '/imports/ui/local-collections/users-collection/users';
import Meetings from '/imports/ui/local-collections/meetings-collection/meetings'; import Meetings from '/imports/ui/local-collections/meetings-collection/meetings';
import { notify } from '/imports/ui/services/notification'; import { notify } from '/imports/ui/services/notification';
@ -120,9 +119,7 @@ const currentUserEmoji = (currentUser) => (currentUser
); );
export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) => { export default injectIntl(withModalMounter(withTracker(({ intl, baseControls }) => {
const authTokenValidation = AuthTokenValidation.findOne({}, { sort: { updatedAt: -1 } }); if (Auth.connectionID !== Meteor.connection._lastSessionId) {
if (authTokenValidation.connectionId !== Meteor.connection._lastSessionId) {
endMeeting('403'); endMeeting('403');
} }

View File

@ -94,5 +94,4 @@ class AuthenticatedHandler extends Component {
} }
} }
export default AuthenticatedHandler; export default AuthenticatedHandler;

View File

@ -33,6 +33,7 @@ class Auth {
this._confname = Storage.getItem('confname'); this._confname = Storage.getItem('confname');
this._externUserID = Storage.getItem('externUserID'); this._externUserID = Storage.getItem('externUserID');
this._fullname = Storage.getItem('fullname'); this._fullname = Storage.getItem('fullname');
this._connectionID = Storage.getItem('connectionID');
} }
get meetingID() { get meetingID() {
@ -142,6 +143,15 @@ class Auth {
}; };
} }
set _connectionID(connectionId) {
this._connectionID = connectionId;
Storage.setItem('sessionToken', this._connectionID);
}
get sessionToken() {
return this._sessionToken;
}
set( set(
meetingId, meetingId,
requesterUserId, requesterUserId,
@ -213,53 +223,30 @@ class Auth {
} }
validateAuthToken() { validateAuthToken() {
return new Promise(async (resolve, reject) => { return new Promise((resolve, reject) => {
let computation = null; SubscriptionRegistry.createSubscription('current-user');
const validationTimeout = setTimeout(() => { const validationTimeout = setTimeout(() => {
computation.stop();
reject({ reject({
error: 408, error: 408,
description: 'Authentication timeout', description: 'Authentication timeout',
}); });
}, CONNECTION_TIMEOUT); }, CONNECTION_TIMEOUT);
makeCall('validateAuthToken', this.meetingID, this.userID, this.token, this.externUserID); Meteor.call('validateAuthToken', this.meetingID, this.userID, this.token, this.externUserID, (err, result) => {
const authenticationTokenValidation = result;
const authTokenSubscription = SubscriptionRegistry.createSubscription('auth-token-validation', {}, { meetingId: this.meetingID, userId: this.userID });
SubscriptionRegistry.createSubscription('current-user');
Tracker.autorun((c) => {
computation = c;
if (!authTokenSubscription.ready()) {
return;
}
const selector = {
connectionId: Meteor.connection._lastSessionId,
};
const authenticationTokenValidation = AuthTokenValidation.findOne(selector);
if (!authenticationTokenValidation) return; if (!authenticationTokenValidation) return;
switch (authenticationTokenValidation.validationStatus) { switch (authenticationTokenValidation.validationStatus) {
case ValidationStates.INVALID: case ValidationStates.INVALID:
c.stop();
reject({ error: 403, description: authenticationTokenValidation.reason }); reject({ error: 403, description: authenticationTokenValidation.reason });
break; break;
case ValidationStates.VALIDATED: case ValidationStates.VALIDATED:
initCursorStreamListener(); initCursorStreamListener();
initAnnotationsStreamListener(); initAnnotationsStreamListener();
c.stop();
clearTimeout(validationTimeout); clearTimeout(validationTimeout);
this.connectionID = authenticationTokenValidation.connectionId;
setTimeout(() => resolve(true), 100); setTimeout(() => resolve(true), 100);
break; break;
case ValidationStates.VALIDATING:
break;
case ValidationStates.NOT_VALIDATED:
break;
default: default:
} }
}); });