Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PB-2022]: Feat/Invited users can join calls #4

Merged
merged 19 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
DRIVE_API_URL=http://drive-server:8000
DRIVE_API_URL=http://drive-server:8000/api
DRIVE_NEW_API_URL=http://drive-server-wip:3004/api
CRYPTO_SECRET=6KYQBP847D4ATSFA
MAGIC_IV=d139cb9a2cd17092e79e1861cf9d7023
MAGIC_SALT=38dce0391b49efba88dbc8c39ebf868f0267eb110bb0012ab27dc52a528d61b1d1ed9d76f400ff58e3240028442b1eab9bb84e111d9dadd997982dbde9dbd25e
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish to Cloudflare

on: [pull_request]

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Publish to Cloudflare Pages
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install dependencies
run: npm install

- name: Configure environment
run: |
touch .env
echo DRIVE_API_URL="https://drive.internxt.com/api" >> .env
echo DRIVE_NEW_API_URL="https://api.internxt.com/drive" >> .env
echo CRYPTO_SECRET="6KYQBP847D4ATSFA" >> .env
echo MAGIC_IV="d139cb9a2cd17092e79e1861cf9d7023" >> .env
echo MAGIC_SALT="38dce0391b49efba88dbc8c39ebf868f0267eb110bb0012ab27dc52a528d61b1d1ed9d76f400ff58e3240028442b1eab9bb84e111d9dadd997982dbde9dbd25e" >> .env
echo JITSI_APP_ID="vpaas-magic-cookie-04a19c25aaab448c9cf74516ffb5ebf2" >> .env
cat .env

- name: Build application
run: make compile deploy

- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: 157168a1684d7105399ec2339cf1281b
projectName: meet-web
directory: .
2 changes: 1 addition & 1 deletion lang/main-es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1465,7 +1465,7 @@
"roomnameHint": "Introduce el nombre o URL de la sala a la que deseas unirte. Puedes inventar un nombre, simplemente infórmaselo a las personas con las que te reunirás para que introduzcan el mismo nombre.",
"sendFeedback": "Enviar sugerencias",
"settings": "Ajustes",
"startMeeting": "Iniciar la reunión",
"startMeeting": "Iniciar nueva reunión",
"terms": "Términos",
"title": "Videoconferencias seguras, con gran variedad de funcionalidades y completamente gratuitas",
"upcomingMeetings": "Tus próximas reuniones"
Expand Down
2 changes: 1 addition & 1 deletion lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1534,7 +1534,7 @@
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
"sendFeedback": "Send feedback",
"settings": "Settings",
"startMeeting": "Start meeting",
"startMeeting": "Start new meeting",
"terms": "Terms",
"title": "Secure, fully featured, and completely free video conferencing",
"upcomingMeetings": "Your upcoming meetings"
Expand Down
2 changes: 1 addition & 1 deletion react/features/app/actions.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
redirectWithStoredParams,
reloadWithStoredParams
} from './actions.any';
import { getDefaultURL, getName } from './functions.web';

Check failure on line 23 in react/features/app/actions.web.ts

View workflow job for this annotation

GitHub Actions / Lint

'getName' is defined but never used
import logger from './logger';
import { IStore } from './types';

Expand Down Expand Up @@ -142,7 +142,7 @@
// else: show thankYou dialog only if there is no feedback
if (options.showThankYou) {
dispatch(showNotification({
titleArguments: { appName: getName() },
titleArguments: { appName: 'Internxt Meet' },
titleKey: 'dialog.thankYou'
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface ConfigKeys {
readonly DRIVE_API_URL: string;
readonly DRIVE_NEW_API_URL: string;
readonly CRYPTO_SECRET: string;
readonly MAGIC_IV: string;
readonly MAGIC_SALT: string;
Expand Down
6 changes: 2 additions & 4 deletions react/features/base/connection/actions.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
import { JITSI_CONNECTION_URL_KEY } from './constants';
import logger from './logger';
import { ConnectionFailedError, IIceServers } from './types';
import { get8x8AppId, get8x8JWT, get8x8Options } from './options8x8';
import { get8x8AppId, get8x8Options, get8x8JWT } from './options8x8';

/**
* The options that will be passed to the JitsiConnection instance.
Expand Down Expand Up @@ -223,9 +223,7 @@ export function _connectInternal(id?: string, password?: string) {
const { locationURL } = state['features/base/connection'];

const room = state['features/base/conference'].room || '';

const inxtToken = localStorage.getItem('xNewToken') || '';
const jwt = await get8x8JWT(inxtToken);
const jwt = await get8x8JWT(room);
const appId = get8x8AppId();
const newOptions = get8x8Options(options, appId, room);

Expand Down
32 changes: 29 additions & 3 deletions react/features/base/connection/options8x8.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,42 @@ import { ConfigService } from "../../authentication/internxt/config.service";
import { doGetJSON } from "../util/httpUtils";
import { IOptions } from "./actions.any";

export async function get8x8JWT(inxtNewToken: string) {
export async function get8x8UserJWT(room: string) {
//A Jitsi JWT, can be manually generated here: https://jaas.8x8.vc/#/apikeys
//more documentation: https://developer.8x8.com/jaas/docs/api-keys-jwt
const res = await doGetJSON('https://api.internxt.com/drive/users/meet-token', false, {
const res = await doGetJSON(`${ConfigService.instance.get('DRIVE_NEW_API_URL')}/users/meet-token/anon?room=${room}`, false, {
method: 'get',
});
return res;
}

export async function get8x8BetaJWT(inxtNewToken: string, room?: string) {
//A Jitsi JWT, can be manually generated here: https://jaas.8x8.vc/#/apikeys
//more documentation: https://developer.8x8.com/jaas/docs/api-keys-jwt
const roomString = room ? `?room=${room}` : '';
const res = await doGetJSON(`${ConfigService.instance.get('DRIVE_NEW_API_URL')}/users/meet-token/beta${roomString}`, false, {
method: 'get',
headers: new Headers({
'Authorization': 'Bearer ' + inxtNewToken,
}),
});
return res.token || '';
return res;
}

export async function get8x8JWT(room?: string) {
let jwt: string | undefined;
let inxtUserToken = localStorage.getItem('xNewToken');
if (inxtUserToken) {
try {
jwt = (await get8x8BetaJWT(inxtUserToken, room)).token;
} catch { }
}
if (!jwt) {
try {
jwt = (await get8x8UserJWT(room || '')).token;
} catch { }
}
return jwt;
}

export function get8x8AppId() {
Expand Down
80 changes: 80 additions & 0 deletions react/features/base/jwt/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,83 @@ export function getJwtExpirationDate(jwt: string | undefined) {
return new Date(exp * 1000);
}
}


/**
* Extracts and returns the expiration date of jwt.
*
* @param {string|undefined} jwt - The jwt to check.
* @returns {Date} The expiration date of the jwt.
*/
export function isJwtValid(jwt: string | undefined) {
if (!jwt) {
return false;
}

const currentTimestamp = new Date().getTime();
try {
const header = jwtDecode(jwt, { header: true });
const payload = jwtDecode(jwt);

if (!header) {
return false;
}
if (!payload) {
return false;
}

const {
aud,
context,
exp,
iss,
nbf,
sub
} = payload;

// JaaS only
if (sub?.startsWith('vpaas-magic-cookie')) {
const { kid } = header;

// if Key ID is missing, we return the error immediately without further validations.
if (!kid) {
return false;
}

if (kid.substring(0, kid.indexOf('/')) !== sub) {
return false;
}

if (aud !== 'jitsi') {
return false;
}

if (iss !== 'chat') {
return false;
}

if (!context?.features) {
return false;
}
}

if (!isValidUnixTimestamp(nbf)) {
return false;
} else if (currentTimestamp < nbf * 1000) {
return false;
}

if (!isValidUnixTimestamp(exp)) {
return false;
} else if (currentTimestamp > exp * 1000) {
return false;
}

if (!context) {
return false;
}
} catch (e: any) {
return false;
}
return true;
}
2 changes: 1 addition & 1 deletion react/features/base/react/components/web/Watermarks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
}

return {
_logoLink,
_logoLink: 'https://internxt.com',
_logoUrl,
_showJitsiWatermark
};
Expand Down
12 changes: 12 additions & 0 deletions react/features/base/ui/components/web/DialogWithTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import BaseDialog, { IProps as IBaseProps } from './BaseDialog';
import Button from './Button';
import ClickableIcon from './ClickableIcon';
import ContextMenuItem from './ContextMenuItem';
import { redirectToStaticPage } from '../../../../app/actions.any';

const MOBILE_BREAKPOINT = 607;

Expand Down Expand Up @@ -214,6 +215,11 @@ const DialogWithTabs = ({
}
}, [ isMobile, userSelected, selectedTab ]);

const onLogout = () => {
localStorage.setItem('xNewToken', '');
dispatch(redirectToStaticPage('/'));
};

const onClose = useCallback((isCancel = true) => {
if (isCancel) {
tabs.forEach(({ cancel }) => {
Expand Down Expand Up @@ -398,6 +404,12 @@ const DialogWithTabs = ({
)}
<div
className = { cx(classes.buttonContainer, classes.footer) }>
<Button
accessibilityLabel = { t('dialog.logoutTitle') }
id = 'modal-dialog-logout-button'
labelKey = { 'dialog.logoutTitle' }
onClick = { onLogout }
type = 'secondary' />
<Button
accessibilityLabel = { t('dialog.accessibilityLabel.Cancel') }
id = 'modal-dialog-cancel-button'
Expand Down
2 changes: 1 addition & 1 deletion react/features/conference/components/web/SubjectText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const SubjectText = () => {
content = { subject }
position = 'bottom'>
<div className = { classes.container }>
<div className = { clsx('subject-text--content', classes.content) }>{subject}</div>
<div className = { clsx('subject-text--content', classes.content) }>Internxt Meet</div>
</div>
</Tooltip>
);
Expand Down
46 changes: 12 additions & 34 deletions react/features/jaas/functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IReduxState } from '../app/types';
import { IJitsiConference } from '../base/conference/reducer';
import { get8x8JWT } from '../base/connection/options8x8';

import { VPAAS_TENANT_PREFIX } from './constants';
import logger from './logger';
Expand Down Expand Up @@ -120,26 +121,10 @@ export function isFeatureDisabled(state: IReduxState, feature: string) {
* @param {string} reqData.baseUrl - The base url for the request.
* @returns {void}
*/
export async function sendGetJWTRequest({ appId, baseUrl }: {
appId: string;
baseUrl: string;
export async function sendGetJWTRequest({ room }: {
room: string;
}) {
const fullUrl = `${baseUrl}/v1/public/token/${encodeURIComponent(appId)}`;

try {
const res = await fetch(fullUrl, {
method: 'GET'
});

if (res.ok) {
return res.json();
}

throw new Error('Request not successful');
} catch (err: any) {
throw new Error(err);

}
return await get8x8JWT(room);
}

/**
Expand All @@ -149,21 +134,14 @@ export async function sendGetJWTRequest({ appId, baseUrl }: {
* @returns {string} The JWT.
*/
export async function getJaasJWT(state: IReduxState) {
const baseUrl = state['features/base/config'].jaasTokenUrl;
const appId = getVpaasTenant(state);

const shouldSendRequest = Boolean(baseUrl && appId);

if (shouldSendRequest) {
try {
const jwt = await sendGetJWTRequest({
appId,
baseUrl: baseUrl ?? ''
});
const room = state['features/base/conference'].room || '';
try {
const jwt = await sendGetJWTRequest({
room,
});

return jwt.token;
} catch (err) {
logger.error('Could not send request', err);
}
return jwt;
} catch (err) {
logger.error('Could not send request', err);
}
}
19 changes: 7 additions & 12 deletions react/features/welcome/components/AbstractWelcomePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import isInsecureRoomName from '../../base/util/isInsecureRoomName';
import { isCalendarEnabled } from '../../calendar-sync/functions';
import { isUnsafeRoomWarningEnabled } from '../../prejoin/functions';
import { isRecentListEnabled } from '../../recent-list/functions';
import { get8x8BetaJWT } from '../../base/connection/options8x8';

/**
* {@code AbstractWelcomePage}'s React {@code Component} prop types.
Expand Down Expand Up @@ -206,24 +207,18 @@ export class AbstractWelcomePage<P extends IProps> extends Component<P, IState>
* @protected
* @returns {void}
*/
_onJoin() {
const room = this.state.room || this.state.generatedRoomName;
async _onJoin() {
this.setState({ joining: true });

sendAnalytics(
createWelcomePageEvent('clicked', 'joinButton', {
isGenerated: !this.state.room,
room
}));

if (room) {
this.setState({ joining: true });
const meetTokenCreator = await get8x8BetaJWT(localStorage.getItem('xNewToken') || '');

if (meetTokenCreator?.room) {
// By the time the Promise of appNavigate settles, this component
// may have already been unmounted.
const onAppNavigateSettled
= () => this._mounted && this.setState({ joining: false });

this.props.dispatch(appNavigate(room))
this.props.dispatch(appNavigate(meetTokenCreator.room))
.then(onAppNavigateSettled, onAppNavigateSettled);
}
}
Expand Down Expand Up @@ -284,7 +279,7 @@ export class AbstractWelcomePage<P extends IProps> extends Component<P, IState>
* @protected
* @returns {void}
*/
_updateInxtToken = (inxtToken: string) =>{
_updateInxtToken = (inxtToken: string) => {
this.setState(
{
inxtToken,
Expand Down
Loading
Loading