Skip to content

Commit

Permalink
Migrate auth and settings to graphQL (#19507)
Browse files Browse the repository at this point in the history
  • Loading branch information
Scroody committed Mar 6, 2024
1 parent 5518dff commit 58a0efe
Show file tree
Hide file tree
Showing 180 changed files with 4,618 additions and 463 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,4 @@ select_permissions:
filter:
meetingId:
_eq: X-Hasura-MeetingId
comment: ""
comment: ""
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ select_permissions:
filter:
meetingId:
_eq: X-Hasura-MeetingId
comment: ""
comment: ""
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,4 @@ update_permissions:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
check: null
check: null
41 changes: 41 additions & 0 deletions bigbluebutton-html5/client/clientStartup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, {
Suspense,
useContext,
useEffect,
} from 'react';
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
import { defineMessages, useIntl } from 'react-intl';

const MeetingClientLazy = React.lazy(() => import('./meetingClient'));

const intlMessages = defineMessages({
loadingClientLabel: {
id: 'app.meeting.loadingClient',
description: 'loading client label',
},
});

const ClientStartup: React.FC = () => {
const loadingContextInfo = useContext(LoadingContext);
const intl = useIntl();
useEffect(() => {
loadingContextInfo.setLoading(true, intl.formatMessage(intlMessages.loadingClientLabel));
}, []);

return (
<Suspense>
{
(() => {
try {
return <MeetingClientLazy />;
} catch (error) {
loadingContextInfo.setLoading(false, '');
throw new Error('Error on rendering MeetingClientLazy: '.concat(JSON.stringify(error) || ''));
}
})()
}
</Suspense>
);
};

export default ClientStartup;
38 changes: 38 additions & 0 deletions bigbluebutton-html5/client/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import ConnectionManager from '/imports/ui/components/connection-manager/component';
// eslint-disable-next-line react/no-deprecated
import { render } from 'react-dom';
import SettingsLoader from '/imports/ui/components/settings-loader/component';
import ErrorBoundary from '/imports/ui/components/common/error-boundary/component';
import { ErrorScreen } from '/imports/ui/components/error-screen/component';
import PresenceManager from '/imports/ui/components/join-handler/presenceManager/component';
import LoadingScreenHOC from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
import IntlLoaderContainer from '/imports/startup/client/intlLoader';
import LocatedErrorBoundary from '/imports/ui/components/common/error-boundary/located-error-boundary/component';
import StartupDataFetch from '/imports/ui/components/connection-manager/startup-data-fetch/component';

const Main: React.FC = () => {
return (
<StartupDataFetch>
<ErrorBoundary Fallback={ErrorScreen}>
<LoadingScreenHOC>
<IntlLoaderContainer>
{/* from there the error messages are located */}
<LocatedErrorBoundary Fallback={ErrorScreen}>
<ConnectionManager>
<PresenceManager>
<SettingsLoader />
</PresenceManager>
</ConnectionManager>
</LocatedErrorBoundary>
</IntlLoaderContainer>
</LoadingScreenHOC>
</ErrorBoundary>
</StartupDataFetch>
);
};

render(
<Main />,
document.getElementById('app'),
);
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,49 @@
*/
/* eslint no-unused-vars: 0 */

import React from 'react';
import React, { useContext, useEffect } from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import logger from '/imports/startup/client/logger';
import '/imports/ui/services/mobile-app';
import Base from '/imports/startup/client/base';
import JoinHandler from '/imports/ui/components/join-handler/component';
import JoinHandler from '../imports/ui/components/join-handler/component';
import AuthenticatedHandler from '/imports/ui/components/authenticated-handler/component';
import Subscriptions from '/imports/ui/components/subscriptions/component';
import IntlStartup from '/imports/startup/client/intl';
import ContextProviders from '/imports/ui/components/context-providers/component';
import UsersAdapter from '/imports/ui/components/components-data/users-context/adapter';
import GraphqlProvider from '/imports/ui/components/graphql-provider/component';
import ConnectionManager from '/imports/ui/components/connection-manager/component';
import { liveDataEventBrokerInitializer } from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker';
// The adapter import is "unused" as far as static code is concerned, but it
// needs to here to override global prototypes. So: don't remove it - prlanzarin 25 Apr 2022
import adapter from 'webrtc-adapter';

import collectionMirrorInitializer from './collection-mirror-initializer';
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
import IntlAdapter from '/imports/startup/client/intlAdapter';
import PresenceAdapter from '/imports/ui/components/authenticated-handler/presence-adapter/component';
import CustomUsersSettings from '/imports/ui/components/join-handler/custom-users-settings/component';

import('/imports/api/audio/client/bridge/bridge-whitelist').catch(() => {
// bridge loading
});

const { disableWebsocketFallback } = Meteor.settings.public.app;

if (disableWebsocketFallback) {
Meteor.connection._stream._sockjsProtocolsWhitelist = function () { return ['websocket']; };

Meteor.disconnect();
Meteor.reconnect();
}

collectionMirrorInitializer();
liveDataEventBrokerInitializer();

Meteor.startup(() => {
// eslint-disable-next-line import/prefer-default-export
const Startup = () => {
const loadingContextInfo = useContext(LoadingContext);
useEffect(() => {
const { disableWebsocketFallback } = window.meetingClientSettings.public.app;
loadingContextInfo.setLoading(false, '');
if (disableWebsocketFallback) {
Meteor.connection._stream._sockjsProtocolsWhitelist = function () { return ['websocket']; };

Meteor.disconnect();
Meteor.reconnect();
}
}, []);
// Logs all uncaught exceptions to the client logger
window.addEventListener('error', (e) => {
let message = e.message || e.error.toString();
Expand All @@ -76,24 +82,20 @@ Meteor.startup(() => {
}, message);
});

// TODO make this a Promise
render(
return (
<ContextProviders>
<>
<JoinHandler>
<AuthenticatedHandler>
<GraphqlProvider>
<Subscriptions>
<IntlStartup>
<Base />
</IntlStartup>
</Subscriptions>
</GraphqlProvider>
</AuthenticatedHandler>
</JoinHandler>
<PresenceAdapter>
<Subscriptions>
<IntlAdapter>
<Base />
</IntlAdapter>
</Subscriptions>
</PresenceAdapter>
<UsersAdapter />
</>
</ContextProviders>,
document.getElementById('app'),
</ContextProviders>
);
});
};

export default Startup;
2 changes: 2 additions & 0 deletions bigbluebutton-html5/imports/api/users/server/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Meteor } from 'meteor/meteor';
import validateAuthToken from './methods/validateAuthToken';
import setUserEffectiveConnectionType from './methods/setUserEffectiveConnectionType';
import userActivitySign from './methods/userActivitySign';
import validateConnection from './methods/validateConnection';

Meteor.methods({
validateConnection,
validateAuthToken,
setUserEffectiveConnectionType,
userActivitySign,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Client } from 'pg';
import upsertValidationState from '/imports/api/auth-token-validation/server/modifiers/upsertValidationState';
import { ValidationStates } from '/imports/api/auth-token-validation';
import userJoin from '/imports/api/users/server/handlers/userJoin';
import Users from '/imports/api/users';

import createDummyUser from '../modifiers/createDummyUser';
import updateUserConnectionId from '../modifiers/updateUserConnectionId';

async function validateConnection(requesterToken, meetingId, userId) {
try {
const client = new Client({
host: process.env.POSTGRES_HOST || Meteor.settings.private.postgresql.host,
port: process.env.POSTGRES_PORT || Meteor.settings.private.postgresql.port,
database: process.env.POSTGRES_HOST || Meteor.settings.private.postgresql.database,
user: process.env.POSTGRES_USER || Meteor.settings.private.postgresql.user,
password: process.env.POSTGRES_PASSWORD || Meteor.settings.private.postgresql.password,
query_timeout: process.env.POSTGRES_TIMEOUT || Meteor.settings.private.postgresql.timeout,
});

await client.connect();

const res = await client.query('select "meetingId", "userId" from v_user_connection_auth where "authToken" = $1', [requesterToken]);

if (res.rows.length === 0) {
await upsertValidationState(
meetingId,
userId,
ValidationStates.INVALID,
this.connection.id,
);
} else {
const sessionId = `${meetingId}--${userId}`;
this.setUserId(sessionId);
await upsertValidationState(
meetingId,
userId,
ValidationStates.VALIDATED,
this.connection.id,
);

const User = await Users.findOneAsync({
meetingId,
userId,
});

if (!User) {
await createDummyUser(meetingId, userId, requesterToken);
} else {
await updateUserConnectionId(meetingId, userId, this.connection.id);
}
userJoin(meetingId, userId, requesterToken);
}
await client.end();
} catch (e) {
await upsertValidationState(
meetingId,
userId,
ValidationStates.INVALID,
this.connection.id,
);
}
}

export default validateConnection;
19 changes: 5 additions & 14 deletions bigbluebutton-html5/imports/startup/client/base.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { useMutation } from '@apollo/client';
import { SET_EXIT_REASON } from '/imports/ui/core/graphql/mutations/userMutations';
import useUserChangedLocalSettings from '/imports/ui/services/settings/hooks/useUserChangedLocalSettings';

const CHAT_CONFIG = Meteor.settings.public.chat;
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_group_id;
const USER_WAS_EJECTED = 'userWasEjected';

Expand Down Expand Up @@ -395,7 +395,7 @@ export default withTracker(() => {
currentConnectionId: 1,
connectionIdUpdateTime: 1,
};
const User = Users.findOne({ intId: credentials.requesterUserId }, { fields });
const User = Users.findOne({ userId: credentials.requesterUserId }, { fields });
const meeting = Meetings.findOne({ meetingId }, {
fields: {
meetingEnded: 1,
Expand All @@ -412,30 +412,21 @@ export default withTracker(() => {
const ejected = User?.ejected;
const ejectedReason = User?.ejectedReason;
const meetingEndedReason = meeting?.meetingEndedReason;
const currentConnectionId = User?.currentConnectionId;
const { connectionID, connectionAuthTime } = Auth;
const connectionIdUpdateTime = User?.connectionIdUpdateTime;


if (ejected) {
// use the connectionID to block users, so we can detect if the user was
// blocked by the current connection. This is the case when a a user is
// ejected from a meeting but not permanently ejected. Permanent ejects are
// managed by the server, not by the client.
BBBStorage.setItem(USER_WAS_EJECTED, connectionID);
}

if (currentConnectionId && currentConnectionId !== connectionID && connectionIdUpdateTime > connectionAuthTime) {
Session.set('codeError', '409');
Session.set('errorMessageDescription', 'joined_another_window_reason')
BBBStorage.setItem(USER_WAS_EJECTED, User.userId);
}

let userSubscriptionHandler;

const codeError = Session.get('codeError');
const { streams: usersVideo } = VideoService.getVideoStreams();

return {
userWasEjected: (BBBStorage.getItem(USER_WAS_EJECTED) == connectionID),
userWasEjected: (BBBStorage.getItem(USER_WAS_EJECTED)),
approved,
ejected,
ejectedReason,
Expand Down
6 changes: 3 additions & 3 deletions bigbluebutton-html5/imports/startup/client/intl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ const propTypes = {
children: PropTypes.element.isRequired,
};

const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale;
const CLIENT_VERSION = Meteor.settings.public.app.html5ClientBuild;
const FALLBACK_ON_EMPTY_STRING = Meteor.settings.public.app.fallbackOnEmptyLocaleString;
const DEFAULT_LANGUAGE = window.meetingClientSettings.public.app.defaultSettings.application.fallbackLocale;
const CLIENT_VERSION = window.meetingClientSettings.public.app.html5ClientBuild;
const FALLBACK_ON_EMPTY_STRING = window.meetingClientSettings.public.app.fallbackOnEmptyLocaleString;

const RTL_LANGUAGES = ['ar', 'dv', 'fa', 'he'];
const LARGE_FONT_LANGUAGES = ['te', 'km'];
Expand Down
52 changes: 52 additions & 0 deletions bigbluebutton-html5/imports/startup/client/intlAdapter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useContext, useEffect } from 'react';
import { useIntl } from 'react-intl';
import Settings from '/imports/ui/services/settings';
import { Session } from 'meteor/session';
import { formatLocaleCode } from '/imports/utils/string-utils';
import Intl from '/imports/ui/services/locale';
import useCurrentLocale from '/imports/ui/core/local-states/useCurrentLocale';
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';

const RTL_LANGUAGES = ['ar', 'dv', 'fa', 'he'];
const LARGE_FONT_LANGUAGES = ['te', 'km'];

interface IntlAdapterProps {
children: React.ReactNode;
}

const IntlAdapter: React.FC<IntlAdapterProps> = ({
children,
}) => {
const [currentLocale] = useCurrentLocale();
const intl = useIntl();
const loadingContextInfo = useContext(LoadingContext);
const setUp = () => {
if (currentLocale) {
const { language, formattedLocale } = formatLocaleCode(currentLocale);
// @ts-ignore - JS code
Settings.application.locale = currentLocale;
Intl.setLocale(formattedLocale, intl.messages);
if (RTL_LANGUAGES.includes(currentLocale.substring(0, 2))) {
// @ts-ignore - JS code
document.body.parentNode.setAttribute('dir', 'rtl');
// @ts-ignore - JS code
Settings.application.isRTL = true;
} else {
// @ts-ignore - JS code
document.body.parentNode.setAttribute('dir', 'ltr');
// @ts-ignore - JS code
Settings.application.isRTL = false;
}
Session.set('isLargeFont', LARGE_FONT_LANGUAGES.includes(currentLocale.substring(0, 2)));
document.getElementsByTagName('html')[0].lang = formattedLocale;
document.body.classList.add(`lang-${language}`);
Settings.save();
}
};

useEffect(setUp, []);
useEffect(setUp, [currentLocale]);
return !loadingContextInfo.isLoading ? children : null;
};

export default IntlAdapter;

0 comments on commit 58a0efe

Please sign in to comment.