From 3582b828855b926adc21c0fc896fb13752b35c09 Mon Sep 17 00:00:00 2001 From: Olivier Foucherot Date: Wed, 17 Jun 2026 09:25:42 -0400 Subject: [PATCH 1/4] New permissions system (PR 1769) Co-authored-by: Marc Bouchenoire Original commit: 2a328be73e841abca22fe463e59cd38f0a2f6abd --- packages/liveblocks-server/package.json | 2 +- tools/liveblocks-cli/package.json | 2 +- tools/liveblocks-cli/src/dev-server/auth.ts | 29 +++++------- .../liveblocks-cli/src/dev-server/db/rooms.ts | 6 +-- .../src/dev-server/lib/jwt-lite.ts | 3 +- .../src/dev-server/lib/permissions.ts | 35 --------------- .../src/dev-server/routes/auth.ts | 2 +- .../src/dev-server/routes/rest-api.ts | 8 +++- .../test/devserver/auth.test.ts | 45 +++++++++---------- 9 files changed, 44 insertions(+), 88 deletions(-) delete mode 100644 tools/liveblocks-cli/src/dev-server/lib/permissions.ts diff --git a/packages/liveblocks-server/package.json b/packages/liveblocks-server/package.json index 45a427b03c0..8c9c577920e 100644 --- a/packages/liveblocks-server/package.json +++ b/packages/liveblocks-server/package.json @@ -69,7 +69,7 @@ }, "sideEffects": false, "dependencies": { - "@liveblocks/core": "3.19.5-pre1", + "@liveblocks/core": "3.20.0-perm8", "async-mutex": "^0.4.0", "decoders": "^2.9.0", "itertools": "^2.7.1", diff --git a/tools/liveblocks-cli/package.json b/tools/liveblocks-cli/package.json index 2eeb79f6ad5..b5c9d4b587e 100644 --- a/tools/liveblocks-cli/package.json +++ b/tools/liveblocks-cli/package.json @@ -46,7 +46,7 @@ "typescript": "^5.9.3" }, "dependencies": { - "@liveblocks/core": "3.19.5-pre1", + "@liveblocks/core": "3.20.0-perm8", "@liveblocks/query-parser": "workspace:^", "@liveblocks/server": "workspace:^", "@liveblocks/zenrouter": "^1.0.18", diff --git a/tools/liveblocks-cli/src/dev-server/auth.ts b/tools/liveblocks-cli/src/dev-server/auth.ts index 9bb3c90af6c..1e122b174cb 100644 --- a/tools/liveblocks-cli/src/dev-server/auth.ts +++ b/tools/liveblocks-cli/src/dev-server/auth.ts @@ -15,14 +15,13 @@ * along with this program. If not, see . */ -import { nanoid } from "@liveblocks/core"; +import { mergeRoomPermissionScopes, nanoid, Permission } from "@liveblocks/core"; import type { CreateTicketOptions } from "@liveblocks/server"; import { ProtocolVersion } from "@liveblocks/server"; import * as Rooms from "./db/rooms"; import type { LiteAccessToken, LiteIdToken, LiteToken } from "./lib/jwt-lite"; import { verifyJwtLite } from "./lib/jwt-lite"; -import { Permission } from "./lib/permissions"; function resolvePermissions_acc( token: LiteAccessToken, @@ -54,28 +53,22 @@ function resolvePermissions_id( const room = Rooms.getRoom(roomId); if (!room) return []; - const scopes = new Set(room.defaultAccesses); + const groupsAccesses = (token.gids ?? []) + .filter((gid) => gid in room.groupsAccesses) + .map((gid) => room.groupsAccesses[gid]); - if (token.gids) { - for (const gid of token.gids) { - for (const p of room.groupsAccesses[gid] ?? []) { - scopes.add(p); - } - } - } - - for (const p of room.usersAccesses[token.uid] ?? []) { - scopes.add(p); - } - - return Array.from(scopes); + return mergeRoomPermissionScopes({ + defaultAccesses: room.defaultAccesses, + groupsAccesses, + userAccesses: room.usersAccesses[token.uid] ?? [], + }); } /** * Resolves permissions for a token against a room. * - Access tokens: match roomId against the token's explicit perms map. - * - ID tokens: look up the room in the DB and collect the union of - * defaultAccesses, groupsAccesses, and usersAccesses. + * - ID tokens: look up the room in the DB and merge defaultAccesses, + * groupsAccesses, and usersAccesses using the same semantics as production. */ function resolvePermissions(token: LiteToken, roomId: string): Permission[] { return token.k === "acc" diff --git a/tools/liveblocks-cli/src/dev-server/db/rooms.ts b/tools/liveblocks-cli/src/dev-server/db/rooms.ts index 4f10618a0cb..32a0e4b02fb 100644 --- a/tools/liveblocks-cli/src/dev-server/db/rooms.ts +++ b/tools/liveblocks-cli/src/dev-server/db/rooms.ts @@ -16,7 +16,7 @@ */ import type { JsonObject } from "@liveblocks/core"; -import { nanoid, WebsocketCloseCodes } from "@liveblocks/core"; +import { nanoid, Permission, WebsocketCloseCodes } from "@liveblocks/core"; import type { Millis } from "@liveblocks/server"; import { DefaultMap, Room } from "@liveblocks/server"; import { Database } from "bun:sqlite"; @@ -24,8 +24,6 @@ import { mkdirSync, mkdtempSync, rmSync } from "fs"; import { tmpdir } from "os"; import { dirname, join, resolve } from "path"; -import type { Permission } from "~/dev-server/lib/permissions"; - import { BunSQLiteDriver } from "./BunSQLiteDriver"; // --------------------------------------------------------------------------- @@ -196,7 +194,7 @@ function createDbRoom( const internalId = nanoid(); const now = new Date().toISOString(); const organizationId = opts?.organizationId ?? DEFAULT_ORGANIZATION_ID; - const defaultAccesses = opts?.defaultAccesses ?? ["room:write"]; + const defaultAccesses = opts?.defaultAccesses ?? [Permission.RoomWrite]; const metadata = opts?.metadata ?? {}; db.run( diff --git a/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts b/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts index feec5352207..79745f21443 100644 --- a/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts +++ b/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts @@ -16,7 +16,7 @@ */ import type { DistributiveOmit } from "@liveblocks/core"; -import { nanoid, tryParseJson } from "@liveblocks/core"; +import { nanoid, Permission, tryParseJson } from "@liveblocks/core"; import type { DecoderType } from "decoders"; import { array, @@ -31,7 +31,6 @@ import { } from "decoders"; import { userInfo } from "./decoders"; -import { Permission } from "./permissions"; const unsignedJwtHeader = object({ alg: constant("none"), diff --git a/tools/liveblocks-cli/src/dev-server/lib/permissions.ts b/tools/liveblocks-cli/src/dev-server/lib/permissions.ts deleted file mode 100644 index bad31899ff5..00000000000 --- a/tools/liveblocks-cli/src/dev-server/lib/permissions.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) Liveblocks Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * Permission values accepted by the dev server. This must stay in sync with the - * backend's canonical Permission definition in shared/common/src/security/client-auth.ts. - * NOTE: Do not import from @liveblocks/core — the published version may lag behind. - * TODO: Find a way to DRY this up with the backend's Permission definition. - */ -export const Permission = { - RoomRead: "room:read", - RoomWrite: "room:write", - CommentsWrite: "comments:write", - FeedsWrite: "feeds:write", - /** @deprecated Accepted but ignored. Presence is always writable. */ - RoomPresenceWrite: "room:presence:write", - /** @deprecated Accepted but ignored. Read access is implied by room:read. */ - CommentsRead: "comments:read", -} as const; - -export type Permission = (typeof Permission)[keyof typeof Permission]; diff --git a/tools/liveblocks-cli/src/dev-server/routes/auth.ts b/tools/liveblocks-cli/src/dev-server/routes/auth.ts index 789c7f21815..bacbe7b4dfa 100644 --- a/tools/liveblocks-cli/src/dev-server/routes/auth.ts +++ b/tools/liveblocks-cli/src/dev-server/routes/auth.ts @@ -15,13 +15,13 @@ * along with this program. If not, see . */ +import { Permission } from "@liveblocks/core"; import { ZenRouter } from "@liveblocks/zenrouter"; import { array, enum_, object, optional, record, string } from "decoders"; import { authorizeSecretKey } from "~/dev-server/lib/auth"; import { userInfo } from "~/dev-server/lib/decoders"; import { createJwtLite } from "~/dev-server/lib/jwt-lite"; -import { Permission } from "~/dev-server/lib/permissions"; const permission = enum_(Permission); diff --git a/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts b/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts index d385c19510f..d1746cd4f9d 100644 --- a/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts +++ b/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts @@ -15,7 +15,12 @@ * along with this program. If not, see . */ -import type { Json, JsonObject, PlainLsonObject } from "@liveblocks/core"; +import type { + Json, + JsonObject, + Permission, + PlainLsonObject, +} from "@liveblocks/core"; import { ServerMsgCode } from "@liveblocks/core"; import { QueryParser } from "@liveblocks/query-parser"; import type { Guid, YDocId } from "@liveblocks/server"; @@ -48,7 +53,6 @@ import * as Y from "yjs"; import type { DbRoom, RoomFilters } from "~/dev-server/db/rooms"; import * as Rooms from "~/dev-server/db/rooms"; import { authorizeSecretKey } from "~/dev-server/lib/auth"; -import type { Permission } from "~/dev-server/lib/permissions"; import { yDocToJson } from "~/dev-server/lib/ydoc"; import { DUMMY, NOT_IMPLEMENTED } from "~/dev-server/responses"; diff --git a/tools/liveblocks-cli/test/devserver/auth.test.ts b/tools/liveblocks-cli/test/devserver/auth.test.ts index 70088d15b84..069bfe11a3c 100644 --- a/tools/liveblocks-cli/test/devserver/auth.test.ts +++ b/tools/liveblocks-cli/test/devserver/auth.test.ts @@ -58,7 +58,7 @@ describe("POST /v2/authorize-user", () => { expect(payload!.k).toBe("acc"); expect(payload!.uid).toBe("user-1"); if (payload!.k === "acc") { - expect(payload!.perms).toEqual({ "room-*": [Permission.Write] }); + expect(payload!.perms).toEqual({ "room-*": [Permission.RoomWrite] }); } }); @@ -221,35 +221,35 @@ describe("authorizeWebSocket", () => { describe("access tokens", () => { test("exact room match grants scopes", () => { - const tok = accToken("user-1", { "my-room": [Permission.Write] }); + const tok = accToken("user-1", { "my-room": [Permission.RoomWrite] }); const result = authorizeWebSocket(wsReq({ roomId: "my-room", tok })); expect(result.ok).toBe(true); if (result.ok) { - expect(result.ticketData.scopes).toEqual([Permission.Write]); + expect(result.ticketData.scopes).toEqual([Permission.RoomWrite]); } }); test("wildcard pattern match grants scopes", () => { - const tok = accToken("user-1", { "project-*": [Permission.Write] }); + const tok = accToken("user-1", { "project-*": [Permission.RoomWrite] }); const result = authorizeWebSocket(wsReq({ roomId: "project-abc", tok })); expect(result.ok).toBe(true); if (result.ok) { - expect(result.ticketData.scopes).toEqual([Permission.Write]); + expect(result.ticketData.scopes).toEqual([Permission.RoomWrite]); } }); test("no matching room/pattern is denied", () => { - const tok = accToken("user-1", { "other-room": [Permission.Write] }); + const tok = accToken("user-1", { "other-room": [Permission.RoomWrite] }); const result = authorizeWebSocket(wsReq({ roomId: "my-room", tok })); expect(result.ok).toBe(false); }); test("read-only permission in token gives read-only scopes", () => { - const tok = accToken("user-1", { "my-room": [Permission.Read] }); + const tok = accToken("user-1", { "my-room": [Permission.RoomRead] }); const result = authorizeWebSocket(wsReq({ roomId: "my-room", tok })); expect(result.ok).toBe(true); if (result.ok) { - expect(result.ticketData.scopes).toEqual([Permission.Read]); + expect(result.ticketData.scopes).toEqual([Permission.RoomRead]); } }); }); @@ -259,7 +259,7 @@ describe("authorizeWebSocket", () => { describe("ID tokens", () => { test("write room grants write scopes", () => { Rooms.getOrCreateRoom("id-write-room", { - defaultAccesses: [Permission.Write], + defaultAccesses: [Permission.RoomWrite], }); const tok = idToken("user-1"); const result = authorizeWebSocket( @@ -273,7 +273,7 @@ describe("authorizeWebSocket", () => { test("read-only room grants read-only scopes", () => { Rooms.getOrCreateRoom("id-readonly-room", { - defaultAccesses: [Permission.Read], + defaultAccesses: [Permission.RoomRead], }); const tok = idToken("user-1"); const result = authorizeWebSocket( @@ -293,18 +293,16 @@ describe("authorizeWebSocket", () => { test("usersAccesses override for specific user", () => { Rooms.getOrCreateRoom("id-user-override-room", { - defaultAccesses: [Permission.Read], - usersAccesses: { "vip-user": [Permission.Write] }, + defaultAccesses: [Permission.RoomRead], + usersAccesses: { "vip-user": [Permission.RoomWrite] }, }); - // VIP user gets both read (from default) and write (from usersAccesses) const tok1 = idToken("vip-user"); const result1 = authorizeWebSocket( wsReq({ roomId: "id-user-override-room", tok: tok1 }) ); expect(result1.ok).toBe(true); if (result1.ok) { - expect(result1.ticketData.scopes).toContain(Permission.Read); - expect(result1.ticketData.scopes).toContain(Permission.Write); + expect(result1.ticketData.scopes).toEqual([Permission.Write]); } // Regular user gets read only const tok2 = idToken("regular-user"); @@ -320,7 +318,7 @@ describe("authorizeWebSocket", () => { test("groupsAccesses grants group-based permissions", () => { Rooms.getOrCreateRoom("id-group-room", { defaultAccesses: [], - groupsAccesses: { "team-a": [Permission.Write] }, + groupsAccesses: { "team-a": [Permission.RoomWrite] }, }); // Member of team-a gets write const tok1 = idToken("user-1", ["team-a"]); @@ -339,10 +337,10 @@ describe("authorizeWebSocket", () => { expect(result2.ok).toBe(false); }); - test("union of defaultAccesses + groupsAccesses", () => { + test("merged defaultAccesses and groupsAccesses", () => { Rooms.getOrCreateRoom("id-union-room", { - defaultAccesses: [Permission.Read], - groupsAccesses: { editors: [Permission.Write] }, + defaultAccesses: [Permission.RoomRead], + groupsAccesses: { editors: [Permission.RoomWrite] }, }); const tok = idToken("user-1", ["editors"]); const result = authorizeWebSocket( @@ -350,8 +348,7 @@ describe("authorizeWebSocket", () => { ); expect(result.ok).toBe(true); if (result.ok) { - expect(result.ticketData.scopes).toContain(Permission.Read); - expect(result.ticketData.scopes).toContain(Permission.Write); + expect(result.ticketData.scopes).toEqual([Permission.Write]); } }); }); @@ -365,14 +362,14 @@ describe("authorizeWebSocket", () => { ); expect(result.ok).toBe(true); if (result.ok) { - expect(result.ticketData.scopes).toEqual([Permission.Write]); + expect(result.ticketData.scopes).toEqual([Permission.RoomWrite]); } }); test("always grants write even on a read-only room", () => { // Explicitly create a room with read-only default access Rooms.getOrCreateRoom("pk-readonly-room", { - defaultAccesses: [Permission.Read], + defaultAccesses: [Permission.RoomRead], }); // Pubkey auth ignores the room's defaultAccesses and always grants write const result = authorizeWebSocket( @@ -380,7 +377,7 @@ describe("authorizeWebSocket", () => { ); expect(result.ok).toBe(true); if (result.ok) { - expect(result.ticketData.scopes).toEqual([Permission.Write]); + expect(result.ticketData.scopes).toEqual([Permission.RoomWrite]); } }); From 26c4fa345eeec4e75237087b32352677ff324758 Mon Sep 17 00:00:00 2001 From: Marc Bouchenoire Date: Wed, 17 Jun 2026 17:51:41 +0200 Subject: [PATCH 2/4] Release: 3.20 (#3513) Co-authored-by: Vincent Driessen Co-authored-by: Olivier Foucherot --- CHANGELOG.md | 18 + .../pages/api-reference/liveblocks-client.mdx | 15 +- docs/pages/api-reference/liveblocks-node.mdx | 121 +-- docs/pages/api-reference/liveblocks-react.mdx | 17 +- docs/pages/authentication.mdx | 129 ++- docs/pages/authentication/access-token.mdx | 54 +- .../authentication/access-token/express.mdx | 4 +- .../authentication/access-token/firebase.mdx | 4 +- .../authentication/access-token/nextjs.mdx | 4 +- .../authentication/access-token/nuxtjs.mdx | 4 +- .../authentication/access-token/remix.mdx | 4 +- .../authentication/access-token/sveltekit.mdx | 4 +- .../pages/authentication/id-token/express.mdx | 10 +- .../authentication/id-token/firebase.mdx | 10 +- docs/pages/authentication/id-token/nextjs.mdx | 10 +- docs/pages/authentication/id-token/nuxtjs.mdx | 10 +- docs/pages/authentication/id-token/remix.mdx | 10 +- .../authentication/id-token/sveltekit.mdx | 10 +- docs/pages/authentication/organizations.mdx | 50 +- docs/pages/authentication/permissions.mdx | 96 +++ .../notifications/concepts.mdx | 9 +- .../access-tokens-not-enough-permissions.mdx | 2 +- docs/pages/get-started/*.prompt.md | 2 +- docs/pages/get-started/nextjs-ai-chat.mdx | 2 +- docs/pages/get-started/react-ai-chat.mdx | 2 +- docs/pages/tools/dev-server.mdx | 4 +- docs/pages/upgrading/1.2.mdx | 2 +- docs/references/v2.openapi.json | 182 ++-- docs/routes.json | 28 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../nextjs-form/pages/api/liveblocks-auth.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../pages/api/liveblocks-auth.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../pages/api/liveblocks-auth.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../pages/api/liveblocks-auth.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../src/pages/api/liveblocks-auth.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../pages/api/liveblocks-auth.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../src/app/api/liveblocks-auth/route.ts | 2 +- .../server/api/liveblocks-auth.ts | 2 +- .../src/routes/api/liveblocks-auth/+server.ts | 2 +- .../src/routes/api/liveblocks-auth/+server.ts | 2 +- ...h-codemirror-yjs-nextjs-and-liveblocks.mdx | 4 +- ...-with-monaco-yjs-nextjs-and-liveblocks.mdx | 4 +- ...with-lexical-yjs-nextjs-and-liveblocks.mdx | 4 +- ...r-with-quill-yjs-nextjs-and-liveblocks.mdx | 4 +- ...r-with-slate-yjs-nextjs-and-liveblocks.mdx | 4 +- ...-with-tiptap-yjs-nextjs-and-liveblocks.mdx | 4 +- ...to-individual-rooms-with-access-tokens.mdx | 6 +- .../how-to-migrate-to-liveblocks-comments.mdx | 2 +- packages/liveblocks-core/e2e/README.md | 4 +- .../storage-notification-reconnect.test.ts | 6 +- .../__tests__/_MockWebSocketServer.setup.ts | 7 +- .../src/__tests__/auth-manager.test.ts | 390 ++++++++- .../src/__tests__/permissions.test.ts | 782 ++++++++++++++++++ .../src/__tests__/room.mockserver.test.ts | 2 - packages/liveblocks-core/src/api-client.ts | 308 +++---- packages/liveblocks-core/src/auth-manager.ts | 196 +++-- packages/liveblocks-core/src/client.ts | 17 +- .../__tests__/liveblocks-helpers.test.ts | 22 +- .../src/crdts/liveblocks-helpers.ts | 13 +- .../liveblocks-core/src/devtools/index.ts | 4 +- packages/liveblocks-core/src/index.ts | 20 +- packages/liveblocks-core/src/permissions.ts | 504 +++++++++++ .../liveblocks-core/src/protocol/AuthToken.ts | 28 +- .../liveblocks-core/src/refs/ManagedOthers.ts | 18 +- .../src/refs/__tests__/ManagedOthers.test.ts | 55 +- packages/liveblocks-core/src/room.ts | 137 +-- packages/liveblocks-node/src/Session.ts | 76 +- .../src/__tests__/Session.test.ts | 59 +- .../src/__tests__/client.test.ts | 114 +++ .../src/__tests__/comment-body.test.ts | 8 +- packages/liveblocks-node/src/client.ts | 105 ++- packages/liveblocks-node/src/index.ts | 10 +- .../templates/str_enum.py.jinja | 6 + packages/liveblocks-python/README.md | 20 +- packages/liveblocks-python/README.mdx | 20 +- .../liveblocks-python/liveblocks/client.py | 90 +- .../liveblocks/models/__init__.py | 11 +- .../models/authorize_user_request_body.py | 4 +- .../models/create_room_request_body.py | 12 +- .../liveblocks/models/get_rooms_response.py | 4 +- ..._key_request_body_new_key_expiration_in.py | 24 + .../liveblocks/models/room.py | 11 +- .../liveblocks/models/room_accesses.py | 12 +- .../room_accesses_additional_property_item.py | 8 - .../liveblocks/models/room_permission_item.py | 10 + .../models/update_room_request_body.py | 4 +- ...pdate_room_request_body_groups_accesses.py | 26 +- ...ccesses_additional_property_type_0_item.py | 8 - ...update_room_request_body_users_accesses.py | 24 +- ...ccesses_additional_property_type_0_item.py | 8 - .../models/upsert_room_request_body.py | 8 +- .../models/upsert_room_request_body_create.py | 8 +- .../liveblocks-python/liveblocks/session.py | 40 +- .../liveblocks-python/tests/test_session.py | 8 +- .../src/components/Comment.tsx | 14 +- .../src/components/Composer.tsx | 34 +- .../src/components/Thread.tsx | 14 +- .../src/__tests__/_restMocks.ts | 4 +- .../__tests__/umbrella-store/index.test.ts | 87 +- .../src/__tests__/useCreateComment.test.tsx | 8 +- .../src/__tests__/useCreateThread.test.tsx | 6 +- .../src/__tests__/useDeleteThread.test.tsx | 6 +- .../src/__tests__/useEditComment.test.tsx | 8 +- .../__tests__/useEditCommentMetadata.test.tsx | 4 +- .../__tests__/useEditThreadMetadata.test.tsx | 4 +- .../__tests__/useMarkThreadAsRead.test.tsx | 4 +- .../useMarkThreadAsResolved.test.tsx | 2 +- .../useMarkThreadAsUnresolved.test.tsx | 2 +- .../__tests__/useSubscribeToThread.test.tsx | 4 +- .../__tests__/useThreadSubscription.test.tsx | 10 +- .../src/__tests__/useThreads.test.tsx | 83 +- .../useUnsubscribeFromThread.test.tsx | 4 +- .../src/__tests__/useUserThreads.test.tsx | 25 +- packages/liveblocks-react/src/_private.ts | 1 + packages/liveblocks-react/src/liveblocks.tsx | 1 - packages/liveblocks-react/src/room.tsx | 65 +- packages/liveblocks-react/src/types/index.ts | 6 - .../liveblocks-react/src/umbrella-store.ts | 63 +- packages/liveblocks-redux/src/index.ts | 18 +- packages/liveblocks-zustand/src/index.ts | 13 +- .../lib/utils/userAllowedInRooms.ts | 2 +- .../src/devtools/contexts/CurrentRoom.tsx | 16 +- 170 files changed, 3321 insertions(+), 1405 deletions(-) create mode 100644 docs/pages/authentication/permissions.mdx create mode 100644 packages/liveblocks-core/src/__tests__/permissions.test.ts create mode 100644 packages/liveblocks-core/src/permissions.ts create mode 100644 packages/liveblocks-python/liveblocks/models/roll_project_secret_api_key_request_body_new_key_expiration_in.py delete mode 100644 packages/liveblocks-python/liveblocks/models/room_accesses_additional_property_item.py delete mode 100644 packages/liveblocks-python/liveblocks/models/update_room_request_body_groups_accesses_additional_property_type_0_item.py delete mode 100644 packages/liveblocks-python/liveblocks/models/update_room_request_body_users_accesses_additional_property_type_0_item.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b0606147f41..ee62e65f740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ ## vNEXT (not yet released) +## v3.20.0 + +### All packages + +- Add support for new resource-specific permissions. You can now start from a + `*:read` or `*:write` base, then grant or deny access per resource (storage, + comments, feeds) using new permission strings like `storage:none` or + `comments:read`. + +### `@liveblocks/node` + +- Deprecate `session.FULL_ACCESS` and `session.READ_ACCESS` in favor of + `["*:write"]` and `["*:read"]` respectively. + +### `@liveblocks/client` + +- Deprecate `room.getStorageSnapshot()` in favor of `room.getStorageOrNull()`. + ## v3.19.5 ### `@liveblocks/client` diff --git a/docs/pages/api-reference/liveblocks-client.mdx b/docs/pages/api-reference/liveblocks-client.mdx index b0ec9d456be..b69faf47bad 100644 --- a/docs/pages/api-reference/liveblocks-client.mdx +++ b/docs/pages/api-reference/liveblocks-client.mdx @@ -223,17 +223,6 @@ const client = createClient({ `"top-right"`, `"bottom-right"`, `"bottom-left"`, or `"top-left"`. [Learn more](#createClientBadgeLocation). - - Deprecated. For new rooms, use [`engine: 2`](#Client.enterRoom) instead. - Engine 2 rooms have native support for streaming. This flag will be removed - in a future version, but will continue to work for existing engine 1 rooms - for now. [Learn more](/docs/guides/the-new-storage-engine-and-its-benefits). - ### createClient with public key [#createClientPublicKey] @@ -6380,12 +6369,12 @@ const user = room.getSelf(); `true` if the user can mutate the Room’s Storage and/or YDoc, `false` if they can only read but not mutate it. Set via your [room - permissions](/docs/authentication#Room-permissions). + permissions](/docs/authentication#id-token-room-permissions). `true` if the user can leave a comment in the room, `false` if they can only read comments but not leave them. Set via your [room - permissions](/docs/authentication#Room-permissions). + permissions](/docs/authentication#id-token-room-permissions). diff --git a/docs/pages/api-reference/liveblocks-node.mdx b/docs/pages/api-reference/liveblocks-node.mdx index 6518a6df91f..9db8c519565 100644 --- a/docs/pages/api-reference/liveblocks-node.mdx +++ b/docs/pages/api-reference/liveblocks-node.mdx @@ -49,7 +49,8 @@ const { body, status } = await liveblocks.identifyUser({ ``` - Learn how to [get started with ID tokens](/docs/authentication). + Learn how to + [get started with ID tokens](/docs/authentication#id-token). A number of options are also available, enabling you to set up permissions and @@ -90,8 +91,8 @@ expired. ##### Granting ID token permissions You can pass additional options to `identifyUser`, enabling you to create -complex [workspace permissions](/docs/authentication#permissions) and -[room permissions](/docs/authentication#Room-permissions). For example, this +complex [workspace permissions](/docs/authentication#id-token-workspace-permissions) and +[room permissions](/docs/authentication#id-token-room-permissions). For example, this user can only see resources in the `acme-corp` workspace, and they’re part of a `marketing` rooms group within it. @@ -113,7 +114,7 @@ const { body, status } = await liveblocks.identifyUser({ ``` - Learn more about [ID token permissions](/docs/authentication#permissions). + Learn more about [ID token permissions](/docs/authentication#id-token-room-permissions). ##### Text editor user data @@ -183,10 +184,11 @@ console.log(currentUser.info.avatar); The purpose of this API is to help you implement your custom authentication back end (i.e. the _server_ part of the diagram). You use the `liveblocks.identifyUser()` API if you’d like to issue -[ID tokens](/docs/authentication/id-token) from your back end. An ID token does -not grant any permissions in the token directly. Instead, it only securely -identifies your user, and then uses any permissions set via the [Permissions -REST API][] to decide whether to allow the user on a room-by-room basis. +[ID tokens](/docs/authentication#id-token-authenticating) from your back end. An +ID token does not grant any permissions in the token directly. Instead, it only +securely identifies your user, and then uses any permissions set via the +[Permissions REST API][] to decide whether to allow the user on a room-by-room +basis. Use this approach if you’d like Liveblocks to be the source of truth for your user’s permissions. @@ -251,7 +253,7 @@ their avatar URL. Here’s a real-world example of ID tokens in a Next.js route handler/endpoint. You can find examples for other frameworks in our -[authentication section](/docs/authentication/id-token). +[authentication section](/docs/authentication#select-your-framework). ```ts file="Next.js" import { Liveblocks } from "@liveblocks/node"; @@ -355,14 +357,14 @@ const session = liveblocks.prepareSession( ); // Giving access to an individual rooms -session.allow("room-id-1", session.FULL_ACCESS); +session.allow("room-id-1", ["*:write"]); // Giving read-only access to this room -session.allow("room-id-2", session.READ_ACCESS); +session.allow("room-id-2", ["*:read"]); // Giving access to multiple rooms with a wildcard // `design-room-1`, `design-room-2`, etc. -session.allow("design-room:*", session.FULL_ACCESS); +session.allow("design-room:*", ["*:write"]); ``` @@ -518,10 +520,10 @@ To implement your back end, follow these steps: Decide which permissions to allow this session ```ts showLineNumbers={false} - session.allow("my-room-1", session.FULL_ACCESS); - session.allow("my-room-2", session.FULL_ACCESS); - session.allow("my-room-3", session.FULL_ACCESS); - session.allow("my-team:*", session.READ_ACCESS); + session.allow("my-room-1", ["*:write"]); + session.allow("my-room-2", ["*:write"]); + session.allow("my-room-3", ["*:write"]); + session.allow("my-team:*", ["*:read"]); ``` @@ -585,7 +587,7 @@ export async function POST(request: Request) { // Implement your own security, and give the user access to the room const { room } = await request.json(); if (room && __shouldUserHaveAccess__(user, room)) { - session.allow(room, session.FULL_ACCESS); + session.allow(room, ["*:write"]); } // Retrieve a token from the Liveblocks servers and pass it to the @@ -766,15 +768,16 @@ rooms to delete. #### Liveblocks.createRoom [#post-rooms] Programmatically creates a new room from a room ID. The `defaultAccesses` option -is required. Setting `defaultAccesses` to `["room:write"]` creates a public -room, whereas setting it to `[]` will create a private room that needs -[ID token permission to enter](/docs/authentication/id-token). This is a wrapper -around the [Create Room API](/docs/api-reference/rest-api-endpoints#post-rooms) -and returns the same response. +is required. Setting `defaultAccesses` to `["*:write"]` creates a public room, +whereas setting it to `[]` will create a private room that needs +[ID token permission to enter](/docs/authentication#id-token-room-permissions). +This is a wrapper around the +[Create Room API](/docs/api-reference/rest-api-endpoints#post-rooms) and returns +the same response. ```ts const room = await liveblocks.createRoom("my-room-id", { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], }); // { type: "room", id: "my-room-id", metadata: {...}, ... } @@ -786,18 +789,18 @@ and attach custom metadata. ```ts const room = await liveblocks.createRoom("my-room-id", { - // The default room permissions. `[]` for private, `["room:write"]` for public. + // The default room permissions. `[]` for private, `["*:write"]` for public. defaultAccesses: [], // Optional, the room's group ID permissions groupsAccesses: { - design: ["room:write"], - engineering: ["room:presence:write", "room:read"], + design: ["*:write"], + engineering: ["*:read"], }, // Optional, the room's user ID permissions usersAccesses: { - "my-user-id": ["room:write"], + "my-user-id": ["*:write"], }, // Optional, custom metadata to attach to the room @@ -812,7 +815,8 @@ const room = await liveblocks.createRoom("my-room-id", { Group and user permissions are only used with [ID token authorization](/docs/api-reference/liveblocks-node#id-tokens), learn -more about [managing permission with ID tokens](/docs/authentication/id-token). +more about +[managing permission with ID tokens](/docs/authentication#id-token-room-permissions). #### Liveblocks.getRoom [#get-rooms-roomId] @@ -831,16 +835,16 @@ console.log(room); #### Liveblocks.getOrCreateRoom [#get-or-create-rooms-roomId] Get a room by its ID. If the room doesn’t exist, create it instead. The -`defaultAccesses` option is required. Setting `defaultAccesses` to -`["room:write"]` creates a public room, whereas setting it to `[]` will create a -private room that needs -[ID token permission to enter](/docs/authentication/id-token). Returns the same -response as the +`defaultAccesses` option is required. Setting `defaultAccesses` to `["*:write"]` +creates a public room, whereas setting it to `[]` will create a private room +that needs +[ID token permission to enter](/docs/authentication#id-token-room-permissions). +Returns the same response as the [Create Room API](/docs/api-reference/rest-api-endpoints#post-rooms). ```ts const room = await liveblocks.getOrCreateRoom("my-room-id", { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], }); // { type: "room", id: "my-room-id", metadata: {...}, ... } @@ -852,18 +856,18 @@ and attach custom metadata. ```ts const room = await liveblocks.getOrCreateRoom("my-room-id", { - // The default room permissions. `[]` for private, `["room:write"]` for public. + // The default room permissions. `[]` for private, `["*:write"]` for public. defaultAccesses: [], // Optional, the room's group ID permissions groupsAccesses: { - design: ["room:write"], - engineering: ["room:presence:write", "room:read"], + design: ["*:write"], + engineering: ["*:read"], }, // Optional, the room's user ID permissions usersAccesses: { - "my-user-id": ["room:write"], + "my-user-id": ["*:write"], }, // Optional, custom metadata to attach to the room @@ -878,7 +882,8 @@ const room = await liveblocks.getOrCreateRoom("my-room-id", { Group and user permissions are only used with [ID token authorization](/docs/api-reference/liveblocks-node#id-tokens), learn -more about [managing permission with ID tokens](/docs/authentication/id-token). +more about +[managing permission with ID tokens](/docs/authentication#id-token-room-permissions). #### Liveblocks.updateRoom [#post-rooms-roomId] @@ -903,18 +908,18 @@ delete the property. ```ts const room = await liveblocks.updateRoom("my-room-id", { - // Optional, update the default room permissions. `[]` for private, `["room:write"]` for public. + // Optional, update the default room permissions. `[]` for private, `["*:write"]` for public. defaultAccesses: [], // Optional, update the room's group ID permissions groupsAccesses: { - design: ["room:write"], - engineering: ["room:presence:write", "room:read"], + design: ["*:write"], + engineering: ["*:read"], }, // Optional, update the room's user ID permissions usersAccesses: { - "my-user-id": ["room:write"], + "my-user-id": ["*:write"], }, // Optional, custom metadata to update on the room @@ -926,16 +931,17 @@ const room = await liveblocks.updateRoom("my-room-id", { Group and user permissions are only used with [ID token authorization](/docs/api-reference/liveblocks-node#id-tokens), learn -more about [managing permission with ID tokens](/docs/authentication/id-token). +more about +[managing permission with ID tokens](/docs/authentication#id-token-room-permissions). #### Liveblocks.upsertRoom [#upsert-rooms-roomId] Update a room’s properties by its ID. If the room doesn’t exist, create it instead. The `defaultAccesses` option is required. Setting `defaultAccesses` to -`["room:write"]` creates a public room, whereas setting it to `[]` will create a +`["*:write"]` creates a public room, whereas setting it to `[]` will create a private room that needs -[ID token permission to enter](/docs/authentication/id-token). Returns the same -response as the +[ID token permission to enter](/docs/authentication#id-token-room-permissions). +Returns the same response as the [Create Room API](/docs/api-reference/rest-api-endpoints#post-rooms). ```ts @@ -946,7 +952,7 @@ const room = await liveblocks.upsertRoom("my-room-id", { }, // These fields will only be set when the room will get created create: { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], }, }); @@ -960,18 +966,18 @@ permissions and attach custom metadata. ```ts const room = await liveblocks.upsertRoom("my-room-id", { update: { - // The default room permissions. `[]` for private, `["room:write"]` for public. + // The default room permissions. `[]` for private, `["*:write"]` for public. defaultAccesses: [], // Optional, the room's group ID permissions groupsAccesses: { - design: ["room:write"], - engineering: ["room:presence:write", "room:read"], + design: ["*:write"], + engineering: ["*:read"], }, // Optional, the room's user ID permissions usersAccesses: { - "my-user-id": ["room:write"], + "my-user-id": ["*:write"], }, // Optional, custom metadata to attach to the room @@ -984,7 +990,8 @@ const room = await liveblocks.upsertRoom("my-room-id", { Group and user permissions are only used with [ID token authorization](/docs/api-reference/liveblocks-node#id-tokens), learn -more about [managing permission with ID tokens](/docs/authentication/id-token). +more about +[managing permission with ID tokens](/docs/authentication#id-token-room-permissions). #### Liveblocks.deleteRoom [#delete-rooms-roomId] @@ -1438,7 +1445,7 @@ and returns the same response. ```ts // Create a new room const room = await liveblocks.createRoom("my-room-id", { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], }); // Initialize Storage @@ -1458,7 +1465,7 @@ import { toPlainLson, LiveList, LiveObject } from "@liveblocks/client"; // Create a new room const room = await liveblocks.createRoom("my-room-id", { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], }); // If this were your Storage type... @@ -1487,7 +1494,7 @@ It’s also possible to create plain LSON manually, without the helper function. ```ts highlight="9-11,17-23" // Create a new room const room = await liveblocks.createRoom("my-room-id", { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], }); // If this were your Storage type... @@ -4160,4 +4167,4 @@ if (isCustomNotificationEvent(event)) { The check is made against the event type and event data kind. [`room.getothers`]: /docs/api-reference/liveblocks-client#Room.getOthers -[Permissions REST API]: /docs/authentication/id-token +[Permissions REST API]: /docs/authentication#id-token-room-permissions diff --git a/docs/pages/api-reference/liveblocks-react.mdx b/docs/pages/api-reference/liveblocks-react.mdx index 7c326c63ad9..3bd1f68cfe5 100644 --- a/docs/pages/api-reference/liveblocks-react.mdx +++ b/docs/pages/api-reference/liveblocks-react.mdx @@ -427,17 +427,6 @@ function App() { `"top-right"`, `"bottom-right"`, `"bottom-left"`, or `"top-left"`. [Learn more](#Powered-by-Liveblocks-branding). - - Deprecated. For new rooms, use [`engine: 2`](#RoomProvider) instead. Engine - 2 rooms have native support for streaming. This flag will be removed in a - future version, but will continue to work for existing engine 1 rooms for - now. [Learn more](/docs/guides/the-new-storage-engine-and-its-benefits). - #### LiveblocksProvider with public key [#LiveblocksProviderPublicKey] @@ -3120,7 +3109,7 @@ that particular selection changes. For full details, see [how selectors work][]. It’s possible to check if a user has a specific permission by using the `canWrite` and `canComment` properties of the `User` object. This is set via -your [room permissions](/docs/authentication#Room-permissions). +your [room permissions](/docs/authentication#id-token-room-permissions). ```ts import { useSelf } from "@liveblocks/react/suspense"; @@ -6832,12 +6821,12 @@ const user = room.getSelf(); `true` if the user can mutate the Room’s Storage and/or YDoc, `false` if they can only read but not mutate it. Set via your [room - permissions](/docs/authentication#Room-permissions). + permissions](/docs/authentication#id-token-room-permissions). `true` if the user can leave a comment in the room, `false` if they can only read comments but not leave them. Set via your [room - permissions](/docs/authentication#Room-permissions). + permissions](/docs/authentication#id-token-room-permissions). diff --git a/docs/pages/authentication.mdx b/docs/pages/authentication.mdx index 1b178baea88..68e373ff532 100644 --- a/docs/pages/authentication.mdx +++ b/docs/pages/authentication.mdx @@ -45,16 +45,28 @@ prototyping and public applications. --- -## How ID token authentication works +Authentication and permissions solve two different problems: -ID token authentication allows Liveblocks to handle permissions for you. This -means that when you create or modify a room, you can set a user’s permissions on -the room itself. This means the room acts as a source of truth. Later, when a -user tries to enter a room, Liveblocks will automatically check if the user has -permission, and deny them access if the permissions aren’t set. +- **Authentication** confirms who the current user is (`userId`) and optionally + which workspace they belong to (`organizationId`). +- **Permissions** define what an authenticated user can do with Liveblocks + resources such as rooms, comments, and feeds. + [Learn how permissions work](/docs/authentication/permissions). -Permissions aren’t just for individual users, but can also be set for groups of -users, or for the whole room at once. +## Authenticate users with ID tokens [#id-token] + +For production applications, we recommend using your secret API key to +authenticate users with **ID tokens**. Your public API key is only for +prototyping and public applications. + +ID token authentication lets Liveblocks handle permissions for you. When you +create or update a room, you set permissions on the room itself, making the room +the source of truth. Later, when a user tries to enter the room, Liveblocks +checks those permissions and denies access when the user isn’t allowed in. + +Permissions can be set for individual users, groups of users, or the whole room. +For available permission formats and scopes, see the +[permissions](/docs/authentication/permissions) page.
- + If you don’t need fine-grained permissions, or if you’d prefer storing individual room permissions in your own system, you can use @@ -74,12 +86,12 @@ individual room permissions in your own system, you can use -## Authenticating +### Authenticating [#id-token-authenticating] Authenticating with ID tokens means creating a -[JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) (JWT) that’s used -to verify the identity of the current user when connecting to a Liveblocks room. -This token is created using +[JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) (JWT) that +identifies the current user when they connect to a Liveblocks room. Create this +token with [`liveblocks.identifyUser`](/docs/api-reference/liveblocks-node#id-tokens) or [`/identify-user`](/docs/api-reference/rest-api-endpoints#post-identify-user). @@ -92,7 +104,7 @@ const { body, status } = await liveblocks.identifyUser({ console.log(body); ``` -## Workspace permissions [#permissions] +### Workspace permissions [#id-token-workspace-permissions] Using [organizations](/docs/authentication/organizations), you can create workspaces in your application, compartmentalizing all resources such as inbox @@ -111,11 +123,10 @@ customers/organizations. /> -### Set up workspace permissions +#### Set up workspace permissions -To set up workspace permissions, pass an `organizationId` when authenticating a -user, ensuring that the user will only have access to resources within this -workspace. +To set up workspace permissions, pass an `organizationId` when authenticating +the user. The user will only be able to access resources in that organization. ```ts const { body, status } = await liveblocks.identifyUser({ @@ -134,7 +145,7 @@ When creating a resource on the server, such as a room, pass the ```ts const room = await liveblocks.createRoom("my-room-id", { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], // +++ organizationId: "my-organization-id", // +++ @@ -144,7 +155,7 @@ const room = await liveblocks.createRoom("my-room-id", { console.log(room); ``` -## Room permissions +### Room permissions [#id-token-room-permissions] ID token authentication allows you to set different permission types on rooms, assigned at three different levels: default, groups, and users. The system is @@ -170,44 +181,12 @@ const room = await liveblocks.createRoom("a32wQXid4A9", { // But Olivier can enter usersAccesses: { - "olivier@example.com": ["room:read"], + "olivier@example.com": ["*:read"], }, }); ``` -### Permission types [#permission-types] - -There are three permission values that you can set on rooms. - -
-
`["room:write"]`
-
- Full access. Enables people to view and edit the room, and create comments. - On the client, - [`canWrite`](/docs/api-reference/liveblocks-react#Checking-user-permissions) - is `true`. -
-
`["room:read", "room:presence:write", "comments:write"]`
-
- Read access with comment creation and presence. Enables people to create - comments and edit their presence, but only view the room’s storage. On - the client, - [`canWrite`](/docs/api-reference/liveblocks-react#Checking-user-permissions) - is `false`. -
-
`["room:read", "room:presence:write"]`
-
- Read access with presence. Enables people to edit their presence, but only - view the room’s storage. Users can view comments, but not interact - with them. On the client, - [`canWrite`](/docs/api-reference/liveblocks-react#Checking-user-permissions) - is `false`. -
-
`[]`
-
Private. No one can enter the room.
-
- -### Permission levels [#permission-types] +#### Permission levels [#id-token-permission-types] Permission types can be applied at three different levels, enabling complex entry systems. @@ -222,9 +201,9 @@ entry systems. Each level further down will override access levels defined above, for example a -room with private access will allow a user with `room:write` access to enter. +room with private access will allow a user with `*:write` access to enter. -### Default room permissions +#### Default room permissions The `defaultAccesses` level is used to set the default permissions of the entire room. @@ -247,21 +226,21 @@ access level to your room. "defaultAccesses": [] // Public - everyone can edit and view the room -"defaultAccesses": ["room:write"] +"defaultAccesses": ["*:write"] -// Read-only - everyone can view the room, but only presence can be edited -"defaultAccesses": ["room:read", "room:presence:write"] +// Read-only - everyone can view the room +"defaultAccesses": ["*:read"] ``` -#### Setting room access +##### Setting room access -We can use the +We can use [`liveblocks.createRoom`](/docs/api-reference/rest-api-endpoints#post-rooms) to create a new room with public access levels: ```ts highlight="2" const room = await liveblocks.createRoom("a32wQXid4A9", { - defaultAccesses: ["room:write"], + defaultAccesses: ["*:write"], }); ``` @@ -275,10 +254,10 @@ const room = await liveblocks.updateRoom("a32wQXid4A9", { }); ``` -### Groups permissions +#### Groups permissions The `groupsAccesses` level is used to set the default permissions of any given -group within room. +group within a room.