From f92bcb37f08855da25cdab05746042e991e25bcf Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Fri, 4 Jul 2025 21:17:03 +0700 Subject: [PATCH] Check user token in love service (#9464) Signed-off-by: Anton Alexeyev --- plugins/love-resources/src/utils.ts | 4 +- services/love/src/main.ts | 103 ++++++++++++++++++---------- 2 files changed, 70 insertions(+), 37 deletions(-) diff --git a/plugins/love-resources/src/utils.ts b/plugins/love-resources/src/utils.ts index 03272acc3c..2979a2cc64 100644 --- a/plugins/love-resources/src/utils.ts +++ b/plugins/love-resources/src/utils.ts @@ -113,9 +113,11 @@ export async function getToken ( if (endpoint === undefined) { throw new Error('Love service endpoint not found') } + const token = getPlatformToken() const res = await fetch(concatLink(endpoint, '/getToken'), { method: 'POST', headers: { + Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ roomName: getTokenRoomName(roomName, roomId), _id: userId, participantName }) @@ -553,7 +555,7 @@ async function withRetries (fn: () => Promise, retries: number, delay: num async function connect (name: string, room: Room, _id: string): Promise { const wsURL = getMetadata(love.metadata.WebSocketURL) - if (wsURL === undefined) { + if (wsURL === undefined || getCurrentAccount().role === AccountRole.ReadOnlyGuest) { return } diff --git a/services/love/src/main.ts b/services/love/src/main.ts index c3a8b6b117..e491bbb693 100644 --- a/services/love/src/main.ts +++ b/services/love/src/main.ts @@ -17,7 +17,7 @@ import { setMetadata } from '@hcengineering/platform' import serverClient from '@hcengineering/server-client' import { initStatisticsContext, StorageConfig, StorageConfiguration } from '@hcengineering/server-core' import { storageConfigFromEnv } from '@hcengineering/server-storage' -import serverToken, { decodeToken } from '@hcengineering/server-token' +import serverToken, { decodeToken, Token } from '@hcengineering/server-token' import { getClient as getAccountClientRaw, isWorkspaceLoginInfo, @@ -25,7 +25,7 @@ import { } from '@hcengineering/account-client' import { RoomMetadata, TranscriptionStatus, MeetingMinutes } from '@hcengineering/love' import cors from 'cors' -import express from 'express' +import express, { type Request } from 'express' import { IncomingHttpHeaders } from 'http' import { AccessToken, @@ -151,6 +151,15 @@ export const main = async (): Promise => { const roomName = req.body.roomName const _id = req.body._id const participantName = req.body.participantName + + if (typeof roomName !== 'string') { + res.status(400).send() + return + } + if (!hasWorkspaceAccess(roomName, req)) { + res.status(401).send() + return + } res.send(await createToken(roomName, _id, participantName)) }) @@ -161,18 +170,21 @@ export const main = async (): Promise => { // eslint-disable-next-line @typescript-eslint/no-misused-promises app.post('/startRecord', async (req, res) => { - const token = extractToken(req.headers) - - if (token === undefined) { - res.status(401).send() - return - } - const roomName = req.body.roomName const room = req.body.room const meetingMinutes = req.body.meetingMinutes + if (typeof roomName !== 'string') { + res.status(400).send() + return + } + if (!hasWorkspaceAccess(roomName, req)) { + res.status(401).send() + return + } + try { + const token = extractToken(req.headers) const wsLoginInfo = await getAccountClient(token).getLoginInfoByToken() if (!isWorkspaceLoginInfo(wsLoginInfo)) { console.error('No workspace found for the token') @@ -194,39 +206,37 @@ export const main = async (): Promise => { // eslint-disable-next-line @typescript-eslint/no-misused-promises app.post('/stopRecord', async (req, res) => { - const token = extractToken(req.headers) - - if (token === undefined) { + const roomName = req.body.roomName + if (typeof roomName !== 'string') { + res.status(400).send() + return + } + if (!hasWorkspaceAccess(roomName, req)) { res.status(401).send() return } - // just check token - decodeToken(token) - await updateMetadata(roomClient, req.body.roomName, { recording: false }) - void stopEgress(egressClient, req.body.roomName) + + await updateMetadata(roomClient, roomName, { recording: false }) + void stopEgress(egressClient, roomName) res.send() }) // eslint-disable-next-line @typescript-eslint/no-misused-promises app.post('/transcription', async (req, res) => { - const token = extractToken(req.headers) - - if (token === undefined) { - res.status(401).send() - return - } - // just check token - decodeToken(token) - const roomName = req.body.roomName const language = req.body.language const transcription = req.body.transcription as TranscriptionStatus - if (roomName == null) { + if (typeof roomName !== 'string') { res.status(400).send() return } + if (!hasWorkspaceAccess(roomName, req)) { + res.status(401).send() + return + } + const metadata = language != null ? { transcription, language } : { transcription } try { await updateMetadata(roomClient, roomName, metadata) @@ -239,21 +249,19 @@ export const main = async (): Promise => { // eslint-disable-next-line @typescript-eslint/no-misused-promises app.post('/language', async (req, res) => { - const token = extractToken(req.headers) - - if (token === undefined) { - res.status(401).send() - return - } - // just check token - decodeToken(token) - const roomName = req.body.roomName const language = req.body.language - if (roomName == null || language == null) { + + if (typeof roomName !== 'string' || language == null) { res.status(400).send() return } + + if (!hasWorkspaceAccess(roomName, req)) { + res.status(401).send() + return + } + try { await updateMetadata(roomClient, roomName, { language }) res.send() @@ -360,6 +368,29 @@ const startRecord = async ( return filepath } +function hasWorkspaceAccess (roomName: string, req: Request): boolean { + const workspace = roomName.split('_')[0] + const token = extractToken(req.headers) + if (token === undefined) { + return false + } + + let decodedToken: Token | undefined + try { + decodedToken = decodeToken(token) + } catch (e) {} + + if ( + decodedToken === undefined || + decodedToken.workspace !== workspace || + decodedToken.extra?.readonly === 'true' || + decodedToken.extra?.guest === 'true' + ) { + return false + } + return true +} + function parseMetadata (metadata?: string | null): RoomMetadata { if (metadata === '' || metadata == null) return {}