diff --git a/.env.template b/.env.template index 10f9a55355f5..9f8a6d08bffe 100644 --- a/.env.template +++ b/.env.template @@ -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 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000000..9fff300ea06f --- /dev/null +++ b/.github/workflows/publish.yml @@ -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: . \ No newline at end of file diff --git a/lang/main-es.json b/lang/main-es.json index 0e0ab29875c8..fab932ab0d19 100644 --- a/lang/main-es.json +++ b/lang/main-es.json @@ -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" diff --git a/lang/main.json b/lang/main.json index f2be6218d00e..aa24545df3e1 100644 --- a/lang/main.json +++ b/lang/main.json @@ -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" diff --git a/react/features/app/actions.web.ts b/react/features/app/actions.web.ts index 9c482a33ba9f..dc25b5a0a191 100644 --- a/react/features/app/actions.web.ts +++ b/react/features/app/actions.web.ts @@ -142,7 +142,7 @@ export function maybeRedirectToWelcomePage(options: { feedbackSubmitted?: boolea // 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)); } diff --git a/react/features/authentication/internxt/types/config.types.ts b/react/features/authentication/internxt/types/config.types.ts index d1064655ab83..777979c951b1 100644 --- a/react/features/authentication/internxt/types/config.types.ts +++ b/react/features/authentication/internxt/types/config.types.ts @@ -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; diff --git a/react/features/base/connection/actions.any.ts b/react/features/base/connection/actions.any.ts index 4852ff7664f1..c1d99e3214c5 100644 --- a/react/features/base/connection/actions.any.ts +++ b/react/features/base/connection/actions.any.ts @@ -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. @@ -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); diff --git a/react/features/base/connection/options8x8.ts b/react/features/base/connection/options8x8.ts index 89e93831cb52..2d7418c2cb8a 100644 --- a/react/features/base/connection/options8x8.ts +++ b/react/features/base/connection/options8x8.ts @@ -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() { diff --git a/react/features/base/jwt/functions.ts b/react/features/base/jwt/functions.ts index a500103adefd..886a715a7584 100644 --- a/react/features/base/jwt/functions.ts +++ b/react/features/base/jwt/functions.ts @@ -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; +} diff --git a/react/features/base/react/components/web/Watermarks.tsx b/react/features/base/react/components/web/Watermarks.tsx index b6b7d923b439..22d36eb801ec 100644 --- a/react/features/base/react/components/web/Watermarks.tsx +++ b/react/features/base/react/components/web/Watermarks.tsx @@ -256,7 +256,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) { } return { - _logoLink, + _logoLink: 'https://internxt.com', _logoUrl, _showJitsiWatermark }; diff --git a/react/features/base/ui/components/web/DialogWithTabs.tsx b/react/features/base/ui/components/web/DialogWithTabs.tsx index 5d5929e74f4d..dfe515f532be 100644 --- a/react/features/base/ui/components/web/DialogWithTabs.tsx +++ b/react/features/base/ui/components/web/DialogWithTabs.tsx @@ -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; @@ -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 }) => { @@ -398,6 +404,12 @@ const DialogWithTabs = ({ )}