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.