@liveblocks/node 1.2.0-internal3 → 1.2.0-internal5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,418 @@
1
+ // src/authorize.ts
2
+ import fetch from "node-fetch";
3
+
4
+ // src/utils.ts
5
+ function isNonEmpty(value) {
6
+ return typeof value === "string" && value.length > 0;
7
+ }
8
+ function assertNonEmpty(value, field) {
9
+ if (!isNonEmpty(value)) {
10
+ throw new Error(
11
+ `Invalid value for field "${field}". Please provide a non-empty string. For more information: https://liveblocks.io/docs/api-reference/liveblocks-node#authorize`
12
+ );
13
+ }
14
+ }
15
+ function assertSecretKey(value, field) {
16
+ if (!isNonEmpty(value) || !value.startsWith("sk_")) {
17
+ throw new Error(
18
+ `Invalid value for field "${field}". Secret keys must start with "sk_". Please provide the secret key from your Liveblocks dashboard at https://liveblocks.io/dashboard/apikeys.`
19
+ );
20
+ }
21
+ }
22
+ function normalizeStatusCode(statusCode) {
23
+ if (statusCode >= 200 && statusCode < 300) {
24
+ return 200;
25
+ } else if (statusCode >= 500) {
26
+ return 503;
27
+ } else {
28
+ return 403;
29
+ }
30
+ }
31
+ function urljoin(baseUrl, path) {
32
+ const url = new URL(baseUrl);
33
+ url.pathname = path;
34
+ return url.toString();
35
+ }
36
+
37
+ // src/authorize.ts
38
+ async function authorize(options) {
39
+ try {
40
+ const { room, secret, userId, userInfo, groupIds } = (
41
+ // Ensure we'll validate inputs at runtime
42
+ options
43
+ );
44
+ assertNonEmpty(secret, "secret");
45
+ assertNonEmpty(room, "room");
46
+ assertNonEmpty(userId, "userId");
47
+ const resp = await fetch(buildLiveblocksAuthorizeEndpoint(options, room), {
48
+ method: "POST",
49
+ headers: {
50
+ Authorization: `Bearer ${secret}`,
51
+ "Content-Type": "application/json"
52
+ },
53
+ body: JSON.stringify({
54
+ userId,
55
+ userInfo,
56
+ groupIds
57
+ })
58
+ });
59
+ return {
60
+ status: normalizeStatusCode(resp.status),
61
+ body: await resp.text()
62
+ };
63
+ } catch (er) {
64
+ return {
65
+ status: 503,
66
+ body: 'Call to "https://api.liveblocks.io/v2/rooms/:roomId/authorize" failed. See "error" for more information.',
67
+ error: er
68
+ };
69
+ }
70
+ }
71
+ function buildLiveblocksAuthorizeEndpoint(options, roomId) {
72
+ if (options.liveblocksAuthorizeEndpoint) {
73
+ return options.liveblocksAuthorizeEndpoint.replace("{roomId}", roomId);
74
+ }
75
+ return `https://api.liveblocks.io/v2/rooms/${encodeURIComponent(
76
+ roomId
77
+ )}/authorize`;
78
+ }
79
+
80
+ // src/new-auth.ts
81
+ import fetch2 from "node-fetch";
82
+
83
+ // src/Session.ts
84
+ var ALL_PERMISSIONS = Object.freeze([
85
+ "room:write",
86
+ "room:read",
87
+ "room:presence:write",
88
+ "comments:write",
89
+ "comments:read"
90
+ ]);
91
+ function isPermission(value) {
92
+ return ALL_PERMISSIONS.includes(value);
93
+ }
94
+ var MAX_PERMS_PER_SET = 10;
95
+ var READ_ACCESS = Object.freeze([
96
+ "room:read",
97
+ "room:presence:write",
98
+ "comments:read"
99
+ ]);
100
+ var FULL_ACCESS = Object.freeze(["room:write", "comments:write"]);
101
+ var roomPatternRegex = /^[^*]{1,50}[*]?$/;
102
+ var Session = class {
103
+ /** @internal */
104
+ constructor(postFn, userId, userInfo) {
105
+ this.FULL_ACCESS = FULL_ACCESS;
106
+ this.READ_ACCESS = READ_ACCESS;
107
+ /** @internal */
108
+ this._sealed = false;
109
+ /** @internal */
110
+ this._permissions = /* @__PURE__ */ new Map();
111
+ assertNonEmpty(userId, "userId");
112
+ this._postFn = postFn;
113
+ this._userId = userId;
114
+ this._userInfo = userInfo;
115
+ }
116
+ /** @internal */
117
+ getOrCreate(roomId) {
118
+ if (this._sealed) {
119
+ throw new Error("You can no longer change these permissions.");
120
+ }
121
+ let perms = this._permissions.get(roomId);
122
+ if (perms) {
123
+ return perms;
124
+ } else {
125
+ if (this._permissions.size >= MAX_PERMS_PER_SET) {
126
+ throw new Error(
127
+ "You cannot add permissions for more than 10 rooms in a single token"
128
+ );
129
+ }
130
+ perms = /* @__PURE__ */ new Set();
131
+ this._permissions.set(roomId, perms);
132
+ return perms;
133
+ }
134
+ }
135
+ allow(roomIdOrPattern, newPerms) {
136
+ if (!roomPatternRegex.test(roomIdOrPattern)) {
137
+ throw new Error("Invalid room name or pattern");
138
+ }
139
+ if (newPerms.length === 0) {
140
+ throw new Error("Permission list cannot be empty");
141
+ }
142
+ const existingPerms = this.getOrCreate(roomIdOrPattern);
143
+ for (const perm of newPerms) {
144
+ if (!isPermission(perm)) {
145
+ throw new Error(`Not a valid permission: ${perm}`);
146
+ }
147
+ existingPerms.add(perm);
148
+ }
149
+ return this;
150
+ }
151
+ /** @internal - For unit tests only */
152
+ hasPermissions() {
153
+ return this._permissions.size > 0;
154
+ }
155
+ /** @internal - For unit tests only */
156
+ seal() {
157
+ if (this._sealed) {
158
+ throw new Error(
159
+ "You cannot reuse Session instances. Please create a new session every time."
160
+ );
161
+ }
162
+ this._sealed = true;
163
+ }
164
+ /** @internal - For unit tests only */
165
+ serializePermissions() {
166
+ return Object.fromEntries(
167
+ Array.from(this._permissions.entries()).map(([pat, perms]) => [
168
+ pat,
169
+ Array.from(perms)
170
+ ])
171
+ );
172
+ }
173
+ /**
174
+ * Call this to authorize the session to access Liveblocks. Note that this
175
+ * will return a Liveblocks "access token". Anyone that obtains such access
176
+ * token will have access to the allowed resources.
177
+ */
178
+ async authorize() {
179
+ this.seal();
180
+ if (!this.hasPermissions()) {
181
+ return {
182
+ status: 403,
183
+ body: "Forbidden"
184
+ };
185
+ }
186
+ try {
187
+ const resp = await this._postFn("/v2/authorize-user", {
188
+ // Required
189
+ userId: this._userId,
190
+ permissions: this.serializePermissions(),
191
+ // Optional metadata
192
+ userInfo: this._userInfo
193
+ });
194
+ return {
195
+ status: normalizeStatusCode(resp.status),
196
+ body: await resp.text()
197
+ };
198
+ } catch (er) {
199
+ return {
200
+ status: 503,
201
+ body: 'Call to /v2/authorize-user failed. See "error" for more information.',
202
+ error: er
203
+ };
204
+ }
205
+ }
206
+ };
207
+
208
+ // src/new-auth.ts
209
+ var DEFAULT_BASE_URL = "https://api.liveblocks.io";
210
+ var Liveblocks = class {
211
+ /**
212
+ * Interact with the Liveblocks API from your Node.js backend.
213
+ */
214
+ constructor(options) {
215
+ const options_ = options;
216
+ const secret = options_.secret;
217
+ assertSecretKey(secret, "secret");
218
+ this._secret = secret;
219
+ this._baseUrl = new URL(
220
+ typeof options_.liveblocksBaseUrl === "string" ? options_.liveblocksBaseUrl : DEFAULT_BASE_URL
221
+ );
222
+ }
223
+ /** @internal */
224
+ async post(path, json) {
225
+ const url = urljoin(this._baseUrl, path);
226
+ const headers = {
227
+ Authorization: `Bearer ${this._secret}`,
228
+ "Content-Type": "application/json"
229
+ };
230
+ return fetch2(url, { method: "POST", headers, body: JSON.stringify(json) });
231
+ }
232
+ /**
233
+ * Prepares a new session to authorize a user to access Liveblocks.
234
+ *
235
+ * IMPORTANT:
236
+ * Always make sure that you trust the user making the request to your
237
+ * backend before calling .prepareSession()!
238
+ *
239
+ * @param userId Tell Liveblocks the user ID of the user to authorize. Must
240
+ * uniquely identify the user account in your system. The uniqueness of this
241
+ * value will determine how many MAUs will be counted/billed.
242
+ *
243
+ * @param options.userInfo Custom metadata to attach to this user. Data you
244
+ * add here will be visible to all other clients in the room, through the
245
+ * `other.info` property.
246
+ *
247
+ */
248
+ prepareSession(userId, options) {
249
+ return new Session(this.post.bind(this), userId, options?.userInfo);
250
+ }
251
+ /**
252
+ * Call this to authenticate the user as an actor you want to allow to use
253
+ * Liveblocks.
254
+ *
255
+ * You should use this method only if you want to manage your permissions
256
+ * through the Liveblocks Permissions API. This method is more complicated to
257
+ * set up, but allows for finer-grained specification of permissions.
258
+ *
259
+ * Calling `.identifyUser()` only lets you securely identify a user (and what
260
+ * groups they belong to). What permissions this user will end up having is
261
+ * determined by whatever permissions you assign the user/group in your
262
+ * Liveblocks account, through the Permissions API:
263
+ * https://liveblocks.io/docs/rooms/permissions
264
+ *
265
+ * IMPORTANT:
266
+ * Always verify that you trust the user making the request before calling
267
+ * .identifyUser()!
268
+ *
269
+ * @param identity Tell Liveblocks the user ID of the user to authenticate.
270
+ * Must uniquely identify the user account in your system. The uniqueness of
271
+ * this value will determine how many MAUs will be counted/billed.
272
+ *
273
+ * If you also want to assign which groups this user belongs to, use the
274
+ * object form and specify the `groupIds` property. Those `groupIds` should
275
+ * match the groupIds you assigned permissions to via the Liveblocks
276
+ * Permissions API, see
277
+ * https://liveblocks.io/docs/rooms/permissions#permissions-levels-groups-accesses-example
278
+ *
279
+ * @param options.userInfo Custom metadata to attach to this user. Data you
280
+ * add here will be visible to all other clients in the room, through the
281
+ * `other.info` property.
282
+ */
283
+ // These fields define the security identity of the user. Whatever you pass in here will define which
284
+ async identifyUser(identity, options) {
285
+ const userId = typeof identity === "string" ? identity : identity.userId;
286
+ const groupIds = typeof identity === "string" ? void 0 : identity.groupIds;
287
+ try {
288
+ assertNonEmpty(userId, "userId");
289
+ const resp = await this.post("/v2/identify-user", {
290
+ userId,
291
+ groupIds,
292
+ // Optional metadata
293
+ userInfo: options?.userInfo
294
+ });
295
+ return {
296
+ status: normalizeStatusCode(resp.status),
297
+ body: await resp.text()
298
+ };
299
+ } catch (er) {
300
+ return {
301
+ status: 503,
302
+ body: `Call to ${urljoin(
303
+ this._baseUrl,
304
+ "/v2/identify"
305
+ )} failed. See "error" for more information.`,
306
+ error: er
307
+ };
308
+ }
309
+ }
310
+ };
311
+
312
+ // src/webhooks.ts
313
+ import crypto from "crypto";
314
+ var _WebhookHandler = class _WebhookHandler {
315
+ constructor(secret) {
316
+ if (!secret)
317
+ throw new Error("Secret is required");
318
+ if (typeof secret !== "string")
319
+ throw new Error("Secret must be a string");
320
+ if (secret.startsWith(_WebhookHandler.secretPrefix) === false)
321
+ throw new Error("Invalid secret, must start with whsec_");
322
+ const secretKey = secret.slice(_WebhookHandler.secretPrefix.length);
323
+ this.secretBuffer = Buffer.from(secretKey, "base64");
324
+ }
325
+ /**
326
+ * Verifies a webhook request and returns the event
327
+ */
328
+ verifyRequest(request) {
329
+ const { webhookId, timestamp, rawSignatures } = this.verifyHeaders(
330
+ request.headers
331
+ );
332
+ this.verifyTimestamp(timestamp);
333
+ const signature = this.sign(`${webhookId}.${timestamp}.${request.rawBody}`);
334
+ const expectedSignatures = rawSignatures.split(" ").map((rawSignature) => {
335
+ const [, parsedSignature] = rawSignature.split(",");
336
+ return parsedSignature;
337
+ }).filter(isNotUndefined);
338
+ if (expectedSignatures.includes(signature) === false)
339
+ throw new Error(
340
+ `Invalid signature, expected one of ${expectedSignatures.join(
341
+ ", "
342
+ )}, got ${signature}`
343
+ );
344
+ const event = JSON.parse(request.rawBody);
345
+ this.verifyWebhookEventType(event);
346
+ return event;
347
+ }
348
+ /**
349
+ * Verifies the headers and returns the webhookId, timestamp and rawSignatures
350
+ */
351
+ verifyHeaders(headers) {
352
+ const sanitizedHeaders = {};
353
+ Object.keys(headers).forEach((key) => {
354
+ sanitizedHeaders[key.toLowerCase()] = headers[key];
355
+ });
356
+ const webhookId = sanitizedHeaders["webhook-id"];
357
+ if (typeof webhookId !== "string")
358
+ throw new Error("Invalid webhook-id header");
359
+ const timestamp = sanitizedHeaders["webhook-timestamp"];
360
+ if (typeof timestamp !== "string")
361
+ throw new Error("Invalid webhook-timestamp header");
362
+ const rawSignatures = sanitizedHeaders["webhook-signature"];
363
+ if (typeof rawSignatures !== "string")
364
+ throw new Error("Invalid webhook-signature header");
365
+ return { webhookId, timestamp, rawSignatures };
366
+ }
367
+ /**
368
+ * Signs the content with the secret
369
+ * @param content
370
+ * @returns `string`
371
+ */
372
+ sign(content) {
373
+ return crypto.createHmac("sha256", this.secretBuffer).update(content).digest("base64");
374
+ }
375
+ /**
376
+ * Verifies that the timestamp is not too old or in the future
377
+ */
378
+ verifyTimestamp(timestampHeader) {
379
+ const now = Math.floor(Date.now() / 1e3);
380
+ const timestamp = parseInt(timestampHeader, 10);
381
+ if (isNaN(timestamp)) {
382
+ throw new Error("Invalid timestamp");
383
+ }
384
+ if (timestamp < now - WEBHOOK_TOLERANCE_IN_SECONDS) {
385
+ throw new Error("Timestamp too old");
386
+ }
387
+ if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {
388
+ throw new Error("Timestamp in the future");
389
+ }
390
+ }
391
+ /**
392
+ * Ensures that the event is a known event type
393
+ * or throws and prompts the user to upgrade to a higher version of @liveblocks/node
394
+ */
395
+ verifyWebhookEventType(event) {
396
+ if (event && event.type && [
397
+ "storageUpdated",
398
+ "userEntered",
399
+ "userLeft",
400
+ "roomCreated",
401
+ "roomDeleted"
402
+ ].includes(event.type))
403
+ return;
404
+ throw new Error(
405
+ "Unknown event type, please upgrade to a higher version of @liveblocks/node"
406
+ );
407
+ }
408
+ };
409
+ _WebhookHandler.secretPrefix = "whsec_";
410
+ var WebhookHandler = _WebhookHandler;
411
+ var WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60;
412
+ var isNotUndefined = (value) => value !== void 0;
413
+ export {
414
+ Liveblocks,
415
+ WebhookHandler,
416
+ authorize
417
+ };
418
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/authorize.ts","../src/utils.ts","../src/new-auth.ts","../src/Session.ts","../src/webhooks.ts"],"sourcesContent":["import fetch from \"node-fetch\";\n\nimport { assertNonEmpty, normalizeStatusCode } from \"./utils\";\n\n/**\n * TODO Officially mark as DEPRECATED, point to migration guide.\n */\ntype AuthorizeOptions = {\n /**\n * The secret API key for your Liveblocks account. You can find it on\n * https://liveblocks.io/dashboard/apikeys\n */\n secret: string;\n /**\n * The room ID for which to authorize the user. This will authorize the user\n * to enter the Liveblocks room.\n */\n room: string;\n /**\n * Associates a user ID to the session that is being authorized. The user ID\n * is typically set to the user ID from your own database.\n *\n * It can also be used to generate a token that gives access to a private\n * room where the userId is configured in the room accesses.\n *\n * This user ID will be used as the unique identifier to compute your\n * Liveblocks account's Monthly Active Users.\n */\n userId: string;\n /**\n * Arbitrary metadata associated to this user session.\n *\n * You can use it to store a small amount of static metadata for a user\n * session. It is public information, that will be visible to other users in\n * the same room, like name, avatar URL, etc.\n *\n * It's only suitable for static info that won't change during a session. If\n * you want to store dynamic metadata on a user session, don't keep that in\n * the session token, but use Presence instead.\n *\n * Can't exceed 1KB when serialized as JSON.\n */\n userInfo?: unknown;\n /**\n * Tell Liveblocks which group IDs this user belongs to. This will authorize\n * the user session to access private rooms that have at least one of these\n * group IDs listed in their room access configuration.\n *\n * See https://liveblocks.io/docs/guides/managing-rooms-users-permissions#permissions\n * for how to configure your room's permissions to use this feature.\n */\n groupIds?: string[];\n\n /**\n * @internal\n * Can be overriden for testing purposes only.\n */\n liveblocksAuthorizeEndpoint?: string;\n};\n\n/**\n * TODO Officially mark as DEPRECATED, point to migration guide.\n */\ntype AuthorizeResponse = {\n status: number;\n body: string;\n error?: Error;\n};\n\n/**\n * TODO Officially mark as DEPRECATED, point to migration guide.\n *\n * Tells Liveblocks that a user should be allowed access to a room, which user\n * this session is for, and what metadata to associate with the user (like\n * name, avatar, etc.)\n *\n * @example\n * export default async function auth(req, res) {\n *\n * // Implement your own security here.\n *\n * const room = req.body.room;\n * const response = await authorize({\n * room,\n * secret,\n * userId: \"123\",\n * userInfo: { // Optional\n * name: \"Ada Lovelace\"\n * },\n * groupIds: [\"group1\"] // Optional\n * });\n * return res.status(response.status).end(response.body);\n * }\n */\nexport async function authorize(\n options: AuthorizeOptions\n): Promise<AuthorizeResponse> {\n try {\n const { room, secret, userId, userInfo, groupIds } =\n // Ensure we'll validate inputs at runtime\n options as Record<string, unknown>;\n\n assertNonEmpty(secret, \"secret\");\n assertNonEmpty(room, \"room\");\n assertNonEmpty(userId, \"userId\");\n\n const resp = await fetch(buildLiveblocksAuthorizeEndpoint(options, room), {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${secret}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n userId,\n userInfo,\n groupIds,\n }),\n });\n\n return {\n status: normalizeStatusCode(resp.status),\n body: await resp.text(),\n };\n } catch (er) {\n return {\n status: 503 /* Service Unavailable */,\n body: 'Call to \"https://api.liveblocks.io/v2/rooms/:roomId/authorize\" failed. See \"error\" for more information.',\n error: er as Error | undefined,\n };\n }\n}\n\nfunction buildLiveblocksAuthorizeEndpoint(\n options: AuthorizeOptions,\n roomId: string\n): string {\n if (options.liveblocksAuthorizeEndpoint) {\n return options.liveblocksAuthorizeEndpoint.replace(\"{roomId}\", roomId);\n }\n\n return `https://api.liveblocks.io/v2/rooms/${encodeURIComponent(\n roomId\n )}/authorize`;\n}\n","export function isNonEmpty(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n\nexport function assertNonEmpty(\n value: unknown,\n field: string\n): asserts value is string {\n if (!isNonEmpty(value)) {\n throw new Error(\n `Invalid value for field \"${field}\". Please provide a non-empty string. For more information: https://liveblocks.io/docs/api-reference/liveblocks-node#authorize`\n );\n }\n}\n\nexport function assertSecretKey(\n value: unknown,\n field: string\n): asserts value is string {\n if (!isNonEmpty(value) || !value.startsWith(\"sk_\")) {\n throw new Error(\n `Invalid value for field \"${field}\". Secret keys must start with \"sk_\". Please provide the secret key from your Liveblocks dashboard at https://liveblocks.io/dashboard/apikeys.`\n );\n }\n}\n\nexport function normalizeStatusCode(statusCode: number): number {\n if (statusCode >= 200 && statusCode < 300) {\n return 200; /* OK */\n } else if (statusCode >= 500) {\n return 503; /* Service Unavailable */\n } else {\n return 403; /* Forbidden */\n }\n}\n\n/**\n * Concatenates a path to a URL.\n */\nexport function urljoin(baseUrl: string | URL, path: string): string {\n const url = new URL(baseUrl);\n url.pathname = path;\n return url.toString();\n}\n","import type { Response } from \"node-fetch\";\nimport fetch from \"node-fetch\";\n\nimport { Session } from \"./Session\";\nimport {\n assertNonEmpty,\n assertSecretKey,\n normalizeStatusCode,\n urljoin,\n} from \"./utils\";\n\nexport type LiveblocksOptions = {\n /**\n * The Liveblocks secret key. Must start with \"sk_\".\n * Get it from https://liveblocks.io/dashboard/apikeys\n */\n secret: string;\n\n /**\n * @internal\n * Allow overriding the base URL for testing purposes only.\n * Default value is https://api.liveblocks.io\n */\n liveblocksBaseUrl?: string;\n};\n\nexport type CreateSessionOptions = {\n userInfo: unknown;\n};\n\nconst DEFAULT_BASE_URL = \"https://api.liveblocks.io\";\n\nexport type AuthResponse = {\n status: number;\n body: string;\n error?: Error;\n};\n\ntype Identity = {\n userId: string;\n groupIds: string[];\n};\n\n/**\n * Interact with the Liveblocks API from your Node.js backend.\n */\nexport class Liveblocks {\n /** @internal */\n private readonly _secret: string;\n /** @internal */\n private readonly _baseUrl: URL;\n\n /**\n * Interact with the Liveblocks API from your Node.js backend.\n */\n constructor(options: LiveblocksOptions) {\n const options_ = options as Record<string, unknown>;\n const secret = options_.secret;\n assertSecretKey(secret, \"secret\");\n this._secret = secret;\n this._baseUrl = new URL(\n typeof options_.liveblocksBaseUrl === \"string\"\n ? options_.liveblocksBaseUrl\n : DEFAULT_BASE_URL\n );\n }\n\n /** @internal */\n private async post(\n path: `/${string}`,\n json: Record<string, unknown>\n ): Promise<Response> {\n const url = urljoin(this._baseUrl, path);\n const headers = {\n Authorization: `Bearer ${this._secret}`,\n \"Content-Type\": \"application/json\",\n };\n return fetch(url, { method: \"POST\", headers, body: JSON.stringify(json) });\n }\n\n /**\n * Prepares a new session to authorize a user to access Liveblocks.\n *\n * IMPORTANT:\n * Always make sure that you trust the user making the request to your\n * backend before calling .prepareSession()!\n *\n * @param userId Tell Liveblocks the user ID of the user to authorize. Must\n * uniquely identify the user account in your system. The uniqueness of this\n * value will determine how many MAUs will be counted/billed.\n *\n * @param options.userInfo Custom metadata to attach to this user. Data you\n * add here will be visible to all other clients in the room, through the\n * `other.info` property.\n *\n */\n prepareSession(userId: string, options?: CreateSessionOptions): Session {\n return new Session(this.post.bind(this), userId, options?.userInfo);\n }\n\n /**\n * Call this to authenticate the user as an actor you want to allow to use\n * Liveblocks.\n *\n * You should use this method only if you want to manage your permissions\n * through the Liveblocks Permissions API. This method is more complicated to\n * set up, but allows for finer-grained specification of permissions.\n *\n * Calling `.identifyUser()` only lets you securely identify a user (and what\n * groups they belong to). What permissions this user will end up having is\n * determined by whatever permissions you assign the user/group in your\n * Liveblocks account, through the Permissions API:\n * https://liveblocks.io/docs/rooms/permissions\n *\n * IMPORTANT:\n * Always verify that you trust the user making the request before calling\n * .identifyUser()!\n *\n * @param identity Tell Liveblocks the user ID of the user to authenticate.\n * Must uniquely identify the user account in your system. The uniqueness of\n * this value will determine how many MAUs will be counted/billed.\n *\n * If you also want to assign which groups this user belongs to, use the\n * object form and specify the `groupIds` property. Those `groupIds` should\n * match the groupIds you assigned permissions to via the Liveblocks\n * Permissions API, see\n * https://liveblocks.io/docs/rooms/permissions#permissions-levels-groups-accesses-example\n *\n * @param options.userInfo Custom metadata to attach to this user. Data you\n * add here will be visible to all other clients in the room, through the\n * `other.info` property.\n */\n // These fields define the security identity of the user. Whatever you pass in here will define which\n public async identifyUser(\n identity:\n | string // Shorthand for userId\n | Identity,\n options?: {\n userInfo: unknown;\n // ....\n }\n ): Promise<AuthResponse> {\n const userId = typeof identity === \"string\" ? identity : identity.userId;\n const groupIds =\n typeof identity === \"string\" ? undefined : identity.groupIds;\n\n try {\n assertNonEmpty(userId, \"userId\"); // TODO: Check if this is a legal userId value too\n // assertStringArrayOrUndefined(groupsIds, \"groupIds\"); // TODO: Check if this is a legal userId value too\n\n const resp = await this.post(\"/v2/identify-user\", {\n userId,\n groupIds,\n\n // Optional metadata\n userInfo: options?.userInfo,\n });\n\n return {\n status: normalizeStatusCode(resp.status),\n body: await resp.text(),\n };\n } catch (er) {\n return {\n status: 503 /* Service Unavailable */,\n body: `Call to ${urljoin(\n this._baseUrl,\n \"/v2/identify\"\n )} failed. See \"error\" for more information.`,\n error: er as Error | undefined,\n };\n }\n }\n}\n","import type { Response } from \"node-fetch\";\n\nimport type { AuthResponse } from \"./new-auth\";\nimport { assertNonEmpty, normalizeStatusCode } from \"./utils\";\n\n// As defined in the source of truth in ApiScope in\n// https://github.com/liveblocks/liveblocks-cloudflare/blob/main/src/security.ts\nconst ALL_PERMISSIONS = Object.freeze([\n \"room:write\",\n \"room:read\",\n \"room:presence:write\",\n \"comments:write\",\n \"comments:read\",\n] as const);\n\nexport type Permission = (typeof ALL_PERMISSIONS)[number];\n\nfunction isPermission(value: string): value is Permission {\n return (ALL_PERMISSIONS as readonly unknown[]).includes(value);\n}\n\nconst MAX_PERMS_PER_SET = 10;\n\n/**\n * Assign this to a room (or wildcard pattern) if you want to grant the user\n * read permissions to the storage and comments data for this room. (Note that\n * the user will still have permissions to update their own presence.)\n */\nconst READ_ACCESS = Object.freeze([\n \"room:read\",\n \"room:presence:write\",\n \"comments:read\",\n] as const);\n\n/**\n * Assign this to a room (or wildcard pattern) if you want to grant the user\n * permissions to read and write to the room's storage and comments.\n */\nconst FULL_ACCESS = Object.freeze([\"room:write\", \"comments:write\"] as const);\n\nconst roomPatternRegex = /^[^*]{1,50}[*]?$/;\n\ntype PostFn = (\n path: `/${string}`,\n json: Record<string, unknown>\n) => Promise<Response>;\n\n/**\n * Class to help you construct the exact permission set to grant a user, used\n * when making `.authorizeUser()` calls.\n *\n * Usage:\n *\n * const session = liveblocks.prepareSession();\n * session.allow(roomId, permissions) // or...\n *\n * For the `permissions` argument, you can pass a list of specific permissions,\n * or use one of our presets:\n *\n * session.allow('my-room', session.FULL_ACCESS) // Read + write access to room storage and comments\n * session.allow('my-room', session.READ_ACCESS) // Read-only access to room storage and comments\n *\n * Rooms can be specified with a prefix match, if the name ends in an asterisk.\n * In that case, access is granted to *all* rooms that start with that prefix:\n *\n * // Read + write access to *all* rooms that start with \"abc:\"\n * session.allow('abc:*', session.FULL_ACCESS)\n *\n * You can define at most 10 room IDs (or patterns) in a single token,\n * otherwise the token would become too large and unwieldy.\n *\n * All permissions granted are additive. You cannot \"remove\" permissions once\n * you grant them. For example:\n *\n * session\n * .allow('abc:*', session.FULL_ACCESS)\n * .allow('abc:123', session.READ_ACCESS)\n *\n * Here, room `abc:123` would have full access. The second .allow() call only\n * _adds_ read permissions, but that has no effect since full access\n * permissions were already added to the set.\n */\nexport class Session {\n public readonly FULL_ACCESS = FULL_ACCESS;\n public readonly READ_ACCESS = READ_ACCESS;\n\n /** @internal */\n private _postFn: PostFn;\n /** @internal */\n private _userId: string;\n /** @internal */\n private _userInfo?: unknown;\n /** @internal */\n private _sealed = false;\n /** @internal */\n private readonly _permissions: Map<string, Set<Permission>> = new Map();\n\n /** @internal */\n constructor(postFn: PostFn, userId: string, userInfo?: unknown) {\n assertNonEmpty(userId, \"userId\"); // TODO: Check if this is a legal userId value too\n\n this._postFn = postFn;\n this._userId = userId;\n this._userInfo = userInfo;\n }\n\n /** @internal */\n private getOrCreate(roomId: string): Set<Permission> {\n if (this._sealed) {\n throw new Error(\"You can no longer change these permissions.\");\n }\n\n let perms = this._permissions.get(roomId);\n if (perms) {\n return perms;\n } else {\n if (this._permissions.size >= MAX_PERMS_PER_SET) {\n throw new Error(\n \"You cannot add permissions for more than 10 rooms in a single token\"\n );\n }\n\n perms = new Set<Permission>();\n this._permissions.set(roomId, perms);\n return perms;\n }\n }\n\n public allow(roomIdOrPattern: string, newPerms: readonly Permission[]): this {\n if (!roomPatternRegex.test(roomIdOrPattern)) {\n throw new Error(\"Invalid room name or pattern\");\n }\n\n if (newPerms.length === 0) {\n throw new Error(\"Permission list cannot be empty\");\n }\n\n const existingPerms = this.getOrCreate(roomIdOrPattern);\n for (const perm of newPerms) {\n if (!isPermission(perm as string)) {\n throw new Error(`Not a valid permission: ${perm}`);\n }\n existingPerms.add(perm);\n }\n return this; // To allow chaining multiple allow calls\n }\n\n /** @internal - For unit tests only */\n public hasPermissions(): boolean {\n return this._permissions.size > 0;\n }\n\n /** @internal - For unit tests only */\n public seal(): void {\n if (this._sealed) {\n throw new Error(\n \"You cannot reuse Session instances. Please create a new session every time.\"\n );\n }\n this._sealed = true;\n }\n\n /** @internal - For unit tests only */\n public serializePermissions(): Record<string, unknown> {\n return Object.fromEntries(\n Array.from(this._permissions.entries()).map(([pat, perms]) => [\n pat,\n Array.from(perms),\n ])\n );\n }\n\n /**\n * Call this to authorize the session to access Liveblocks. Note that this\n * will return a Liveblocks \"access token\". Anyone that obtains such access\n * token will have access to the allowed resources.\n */\n public async authorize(): Promise<AuthResponse> {\n this.seal();\n if (!this.hasPermissions()) {\n return {\n status: 403,\n body: \"Forbidden\",\n };\n }\n\n try {\n const resp = await this._postFn(\"/v2/authorize-user\", {\n // Required\n userId: this._userId,\n permissions: this.serializePermissions(),\n\n // Optional metadata\n userInfo: this._userInfo,\n });\n\n return {\n status: normalizeStatusCode(resp.status),\n body: await resp.text(),\n };\n } catch (er) {\n return {\n status: 503 /* Service Unavailable */,\n body: 'Call to /v2/authorize-user failed. See \"error\" for more information.',\n error: er as Error | undefined,\n };\n }\n }\n}\n","import crypto from \"crypto\";\nimport type { IncomingHttpHeaders } from \"http\";\n\nexport class WebhookHandler {\n private secretBuffer: Buffer;\n private static secretPrefix = \"whsec_\";\n constructor(\n /**\n * The signing secret provided on the dashboard's webhooks page\n * @example \"whsec_wPbvQ+u3VtN2e2tRPDKchQ1tBZ3svaHLm\"\n */\n secret: string\n ) {\n if (!secret) throw new Error(\"Secret is required\");\n if (typeof secret !== \"string\") throw new Error(\"Secret must be a string\");\n\n if (secret.startsWith(WebhookHandler.secretPrefix) === false)\n throw new Error(\"Invalid secret, must start with whsec_\");\n\n const secretKey = secret.slice(WebhookHandler.secretPrefix.length);\n this.secretBuffer = Buffer.from(secretKey, \"base64\");\n }\n\n /**\n * Verifies a webhook request and returns the event\n */\n public verifyRequest(request: WebhookRequest): WebhookEvent {\n const { webhookId, timestamp, rawSignatures } = this.verifyHeaders(\n request.headers\n );\n\n this.verifyTimestamp(timestamp);\n\n const signature = this.sign(`${webhookId}.${timestamp}.${request.rawBody}`);\n\n const expectedSignatures = rawSignatures\n .split(\" \")\n .map((rawSignature) => {\n const [, parsedSignature] = rawSignature.split(\",\");\n return parsedSignature;\n })\n .filter(isNotUndefined);\n\n if (expectedSignatures.includes(signature) === false)\n throw new Error(\n `Invalid signature, expected one of ${expectedSignatures.join(\n \", \"\n )}, got ${signature}`\n );\n\n const event: WebhookEvent = JSON.parse(request.rawBody) as WebhookEvent;\n\n this.verifyWebhookEventType(event);\n\n return event;\n }\n\n /**\n * Verifies the headers and returns the webhookId, timestamp and rawSignatures\n */\n private verifyHeaders(headers: IncomingHttpHeaders) {\n const sanitizedHeaders: IncomingHttpHeaders = {};\n Object.keys(headers).forEach((key) => {\n sanitizedHeaders[key.toLowerCase()] = headers[key];\n });\n\n const webhookId = sanitizedHeaders[\"webhook-id\"];\n if (typeof webhookId !== \"string\")\n throw new Error(\"Invalid webhook-id header\");\n\n const timestamp = sanitizedHeaders[\"webhook-timestamp\"];\n if (typeof timestamp !== \"string\")\n throw new Error(\"Invalid webhook-timestamp header\");\n\n const rawSignatures = sanitizedHeaders[\"webhook-signature\"];\n if (typeof rawSignatures !== \"string\")\n throw new Error(\"Invalid webhook-signature header\");\n\n return { webhookId, timestamp, rawSignatures };\n }\n\n /**\n * Signs the content with the secret\n * @param content\n * @returns `string`\n */\n private sign(content: string): string {\n return crypto\n .createHmac(\"sha256\", this.secretBuffer)\n .update(content)\n .digest(\"base64\");\n }\n\n /**\n * Verifies that the timestamp is not too old or in the future\n */\n private verifyTimestamp(timestampHeader: string) {\n const now = Math.floor(Date.now() / 1000);\n const timestamp = parseInt(timestampHeader, 10);\n\n if (isNaN(timestamp)) {\n throw new Error(\"Invalid timestamp\");\n }\n\n // Check if timestamp is too old\n if (timestamp < now - WEBHOOK_TOLERANCE_IN_SECONDS) {\n throw new Error(\"Timestamp too old\");\n }\n\n // Check if timestamp is in the future\n if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {\n throw new Error(\"Timestamp in the future\");\n }\n }\n\n /**\n * Ensures that the event is a known event type\n * or throws and prompts the user to upgrade to a higher version of @liveblocks/node\n */\n private verifyWebhookEventType(\n event: WebhookEvent\n ): asserts event is WebhookEvent {\n if (\n event &&\n event.type &&\n [\n \"storageUpdated\",\n \"userEntered\",\n \"userLeft\",\n \"roomCreated\",\n \"roomDeleted\",\n ].includes(event.type)\n )\n return;\n\n throw new Error(\n \"Unknown event type, please upgrade to a higher version of @liveblocks/node\"\n );\n }\n}\n\nconst WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60; // 5 minutes\n\nconst isNotUndefined = <T>(value: T | undefined): value is T =>\n value !== undefined;\n\ntype WebhookRequest = {\n /**\n * Headers of the request\n * @example\n * {\n * \"webhook-id\": \"123\",\n * \"webhook-timestamp\": \"1614588800000\",\n * \"webhook-signature\": \"v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=\"\n * }\n */\n headers: IncomingHttpHeaders;\n /**\n * Raw body of the request, do not parse it\n * @example '{\"type\":\"storageUpdated\",\"data\":{\"roomId\":\"my-room-id\",\"appId\":\"my-app-id\",\"updatedAt\":\"2021-03-01T12:00:00.000Z\"}}'\n */\n rawBody: string;\n};\n\ntype WebhookEvent =\n | StorageUpdatedEvent\n | UserEnteredEvent\n | UserLeftEvent\n | RoomCreatedEvent\n | RoomDeletedEvent;\n\ntype StorageUpdatedEvent = {\n type: \"storageUpdated\";\n data: {\n roomId: string;\n appId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n updatedAt: string;\n };\n};\n\ntype UserEnteredEvent = {\n type: \"userEntered\";\n data: {\n appId: string;\n roomId: string;\n connectionId: number;\n userId: string | null;\n userInfo: Record<string, unknown> | null;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n * @description The time when the user entered the room.\n */\n enteredAt: string;\n numActiveUsers: number;\n };\n};\n\ntype UserLeftEvent = {\n type: \"userLeft\";\n data: {\n appId: string;\n roomId: string;\n connectionId: number;\n userId: string | null;\n userInfo: Record<string, unknown> | null;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n * @description The time when the user left the room.\n */\n leftAt: string;\n numActiveUsers: number;\n };\n};\n\ntype RoomCreatedEvent = {\n type: \"roomCreated\";\n data: {\n appId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n createdAt: string;\n };\n};\n\ntype RoomDeletedEvent = {\n type: \"roomDeleted\";\n data: {\n appId: string;\n roomId: string;\n /**\n * ISO 8601 datestring\n * @example \"2021-03-01T12:00:00.000Z\"\n */\n deletedAt: string;\n };\n};\n\nexport type {\n RoomCreatedEvent,\n RoomDeletedEvent,\n StorageUpdatedEvent,\n UserEnteredEvent,\n UserLeftEvent,\n WebhookEvent,\n WebhookRequest,\n};\n"],"mappings":"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","names":["fetch","fetch"]}
package/package.json CHANGED
@@ -1,10 +1,23 @@
1
1
  {
2
2
  "name": "@liveblocks/node",
3
- "version": "1.2.0-internal3",
3
+ "version": "1.2.0-internal5",
4
4
  "description": "A server-side utility that lets you set up a Liveblocks authentication endpoint. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "module": "./dist/index.mjs",
17
+ "default": "./dist/index.js"
18
+ }
19
+ }
20
+ },
8
21
  "files": [
9
22
  "dist/**",
10
23
  "README.md"