From 3f20a3a439209a497286e5d9968bbb10ea625c14 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 21 Jun 2023 08:33:58 +0100 Subject: [PATCH] Enable collaboration for public users and fix collaboration for non-devs --- packages/frontend-core/src/api/index.js | 8 ++++ .../src/components/grid/lib/websocket.js | 5 ++- packages/server/src/websockets/grid.ts | 45 +++++++++++++++---- packages/server/src/websockets/websocket.ts | 14 ++++-- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js index 05abded61c..7b823d28c2 100644 --- a/packages/frontend-core/src/api/index.js +++ b/packages/frontend-core/src/api/index.js @@ -234,6 +234,14 @@ export const createAPIClient = config => { invalidateCache: () => { cache = {} }, + + // Generic utility to extract the current app ID. Assumes that any client + // that exists in an app context will be attaching our app ID header. + getAppID: () => { + let headers = {} + config?.attachHeaders(headers) + return headers?.["x-budibase-app-id"] + }, } // Attach all endpoints diff --git a/packages/frontend-core/src/components/grid/lib/websocket.js b/packages/frontend-core/src/components/grid/lib/websocket.js index 9de0199cfc..66863ca1f2 100644 --- a/packages/frontend-core/src/components/grid/lib/websocket.js +++ b/packages/frontend-core/src/components/grid/lib/websocket.js @@ -3,7 +3,7 @@ import { createWebsocket } from "../../../utils" import { SocketEvent, GridSocketEvent } from "@budibase/shared-core" export const createGridWebsocket = context => { - const { rows, tableId, users, focusedCellId, table } = context + const { rows, tableId, users, focusedCellId, table, API } = context const socket = createWebsocket("/socket/grid") const connectToTable = tableId => { @@ -11,9 +11,10 @@ export const createGridWebsocket = context => { return } // Identify which table we are editing + const appId = API.getAppID() socket.emit( GridSocketEvent.SelectTable, - { tableId }, + { tableId, appId }, ({ users: gridUsers }) => { users.set(gridUsers) } diff --git a/packages/server/src/websockets/grid.ts b/packages/server/src/websockets/grid.ts index 6731c2d899..a1f790fc68 100644 --- a/packages/server/src/websockets/grid.ts +++ b/packages/server/src/websockets/grid.ts @@ -1,6 +1,6 @@ import authorized from "../middleware/authorized" import { BaseSocket } from "./websocket" -import { permissions } from "@budibase/backend-core" +import { context, permissions } from "@budibase/backend-core" import http from "http" import Koa from "koa" import { getTableId } from "../api/controllers/row/utils" @@ -8,20 +8,49 @@ import { Row, Table } from "@budibase/types" import { Socket } from "socket.io" import { GridSocketEvent } from "@budibase/shared-core" +const { PermissionType, PermissionLevel } = permissions + export default class GridSocket extends BaseSocket { constructor(app: Koa, server: http.Server) { - super(app, server, "/socket/grid", [authorized(permissions.BUILDER)]) + super(app, server, "/socket/grid") } async onConnect(socket: Socket) { // Initial identification of connected spreadsheet - socket.on(GridSocketEvent.SelectTable, async ({ tableId }, callback) => { - await this.joinRoom(socket, tableId) + socket.on( + GridSocketEvent.SelectTable, + async ({ tableId, appId }, callback) => { + // Check if the user has permission to read this resource + const middleware = authorized( + PermissionType.TABLE, + PermissionLevel.READ + ) + const ctx = { + appId, + resourceId: tableId, + roleId: socket.data.roleId, + user: { _id: socket.data._id }, + isAuthenticated: socket.data.isAuthenticated, + request: { + url: "/fake", + }, + get: () => null, + throw: () => { + // If they don't have access, immediately disconnect them + socket.disconnect(true) + }, + } + await context.doInAppContext(appId, async () => { + await middleware(ctx, async () => { + await this.joinRoom(socket, tableId) - // Reply with all users in current room - const sessions = await this.getRoomSessions(tableId) - callback({ users: sessions }) - }) + // Reply with all users in current room + const sessions = await this.getRoomSessions(tableId) + callback({ users: sessions }) + }) + }) + } + ) // Handle users selecting a new cell socket.on(GridSocketEvent.SelectCell, ({ cellId }) => { diff --git a/packages/server/src/websockets/websocket.ts b/packages/server/src/websockets/websocket.ts index d8cc10bda4..52351aea36 100644 --- a/packages/server/src/websockets/websocket.ts +++ b/packages/server/src/websockets/websocket.ts @@ -4,12 +4,18 @@ import Koa from "koa" import Cookies from "cookies" import { userAgent } from "koa-useragent" import { auth, Header, redis } from "@budibase/backend-core" -import currentApp from "../middleware/currentapp" import { createAdapter } from "@socket.io/redis-adapter" import { Socket } from "socket.io" import { getSocketPubSubClients } from "../utilities/redis" import { SocketEvent, SocketSessionTTL } from "@budibase/shared-core" import { SocketSession } from "@budibase/types" +import { v4 as uuid } from "uuid" + +const anonUser = () => ({ + _id: uuid(), + email: "user@mail.com", + firstName: "Anonymous", +}) export class BaseSocket { io: Server @@ -34,7 +40,6 @@ export class BaseSocket { const middlewares = [ userAgent, authenticate, - currentApp, ...(additionalMiddlewares || []), ] @@ -70,7 +75,8 @@ export class BaseSocket { // Middlewares are finished // Extract some data from our enriched koa context to persist // as metadata for the socket - const { _id, email, firstName, lastName } = ctx.user + const user = ctx.user?._id ? ctx.user : anonUser() + const { _id, email, firstName, lastName } = user socket.data = { _id, email, @@ -78,6 +84,8 @@ export class BaseSocket { lastName, sessionId: socket.id, connectedAt: Date.now(), + isAuthenticated: ctx.isAuthenticated, + roleId: ctx.roleId, } next() }