Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 0 additions & 106 deletions .coderabbit.yaml

This file was deleted.

4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# PubNub JavaScript SDK (V4)
<img width="1920" height="600" alt="image" src="https://github.com/user-attachments/assets/b60b4e27-a140-4784-8b34-85ae421023e2" />

# PubNub JavaScript SDK

[![Codacy Badge](https://api.codacy.com/project/badge/Grade/2859917905c549b8bfa27630ff276fce)](https://www.codacy.com/app/PubNub/javascript?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=pubnub/javascript&amp;utm_campaign=Badge_Grade)
[![npm](https://img.shields.io/npm/v/pubnub.svg)]()
Expand Down
23 changes: 23 additions & 0 deletions lib/core/components/token_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ class TokenManager {
const patternDataSync = this.extractDataSyncScopes(parsed.pat);
if (patternDataSync)
((_b = result.patterns) !== null && _b !== void 0 ? _b : (result.patterns = {})).dataSync = patternDataSync;
const categories = this.extractCategories(parsed.cat);
if (categories)
result.categories = categories;
if (parsed.meta && Object.keys(parsed.meta).length > 0)
result.meta = parsed.meta;
return result;
Expand Down Expand Up @@ -156,6 +159,26 @@ class TokenManager {
permissionsResult.read = true;
return permissionsResult;
}
/**
* Extract category-level permissions from the token `cat` section.
*
* `chan` maps to `channels` and `uuid` maps to `uuids`. `get` is set from the `32` bit. The
* section is omitted when `cat` is absent or empty.
*
* @param cat - Raw `cat` section decoded from the token.
*
* @returns Human-readable category permissions, or `undefined` when none are present.
*/
extractCategories(cat) {
if (!cat)
return undefined;
const result = {};
if (typeof cat.chan === 'number')
result.channels = { get: (cat.chan & 32) === 32 };
if (typeof cat.uuid === 'number')
result.uuids = { get: (cat.uuid & 32) === 32 };
return Object.keys(result).length > 0 ? result : undefined;
}
/**
* Extract DataSync permission scopes from a token permissions section.
*
Expand Down
41 changes: 26 additions & 15 deletions lib/core/endpoints/access_manager/grant.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,28 @@ const JOIN_PERMISSION = false;
*/
class GrantRequest extends request_1.AbstractRequest {
constructor(parameters) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
var _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
var _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
super();
this.parameters = parameters;
// Apply defaults.
(_a = (_l = this.parameters).channels) !== null && _a !== void 0 ? _a : (_l.channels = []);
(_b = (_m = this.parameters).channelGroups) !== null && _b !== void 0 ? _b : (_m.channelGroups = []);
(_c = (_o = this.parameters).uuids) !== null && _c !== void 0 ? _c : (_o.uuids = []);
(_d = (_p = this.parameters).read) !== null && _d !== void 0 ? _d : (_p.read = READ_PERMISSION);
(_e = (_q = this.parameters).write) !== null && _e !== void 0 ? _e : (_q.write = WRITE_PERMISSION);
(_f = (_r = this.parameters).delete) !== null && _f !== void 0 ? _f : (_r.delete = DELETE_PERMISSION);
(_g = (_s = this.parameters).get) !== null && _g !== void 0 ? _g : (_s.get = GET_PERMISSION);
(_h = (_t = this.parameters).update) !== null && _h !== void 0 ? _h : (_t.update = UPDATE_PERMISSION);
(_j = (_u = this.parameters).manage) !== null && _j !== void 0 ? _j : (_u.manage = MANAGE_PERMISSION);
(_k = (_v = this.parameters).join) !== null && _k !== void 0 ? _k : (_v.join = JOIN_PERMISSION);
(_a = (_m = this.parameters).channels) !== null && _a !== void 0 ? _a : (_m.channels = []);
(_b = (_o = this.parameters).channelGroups) !== null && _b !== void 0 ? _b : (_o.channelGroups = []);
(_c = (_p = this.parameters).uuids) !== null && _c !== void 0 ? _c : (_p.uuids = []);
(_d = (_q = this.parameters).categories) !== null && _d !== void 0 ? _d : (_q.categories = []);
(_e = (_r = this.parameters).read) !== null && _e !== void 0 ? _e : (_r.read = READ_PERMISSION);
(_f = (_s = this.parameters).write) !== null && _f !== void 0 ? _f : (_s.write = WRITE_PERMISSION);
(_g = (_t = this.parameters).delete) !== null && _g !== void 0 ? _g : (_t.delete = DELETE_PERMISSION);
(_h = (_u = this.parameters).get) !== null && _h !== void 0 ? _h : (_u.get = GET_PERMISSION);
(_j = (_v = this.parameters).update) !== null && _j !== void 0 ? _j : (_v.update = UPDATE_PERMISSION);
(_k = (_w = this.parameters).manage) !== null && _k !== void 0 ? _k : (_w.manage = MANAGE_PERMISSION);
(_l = (_x = this.parameters).join) !== null && _l !== void 0 ? _l : (_x.join = JOIN_PERMISSION);
}
operation() {
return operations_1.default.PNAccessManagerGrant;
}
validate() {
const { keySet: { subscribeKey, publishKey, secretKey }, uuids = [], channels = [], channelGroups = [], authKeys = [], } = this.parameters;
const { keySet: { subscribeKey, publishKey, secretKey }, uuids = [], channels = [], channelGroups = [], categories = [], authKeys = [], read, write, manage, delete: del, update, join, get, ttl, } = this.parameters;
if (!subscribeKey)
return 'Missing Subscribe Key';
if (!publishKey)
Expand All @@ -91,6 +92,16 @@ class GrantRequest extends request_1.AbstractRequest {
return 'authKeys are required for grant request on uuids';
if (uuids.length && (channels.length !== 0 || channelGroups.length !== 0))
return 'Both channel/channel group and uuid cannot be used in the same request';
if (categories.length > 0) {
if (authKeys.length === 0)
return 'authKeys are required for grant request on categories';
if (categories.some((category) => category !== 'channels' && category !== 'uuids'))
return 'Invalid category: only channels and uuids are supported';
if (read || write || manage || del || update || join)
return 'Category permissions must be exactly the get permission';
if (ttl === 1 && get)
return 'One-minute category grants are not supported';
}
}
parse(response) {
return __awaiter(this, void 0, void 0, function* () {
Expand All @@ -101,8 +112,8 @@ class GrantRequest extends request_1.AbstractRequest {
return `/v2/auth/grant/sub-key/${this.parameters.keySet.subscribeKey}`;
}
get queryParameters() {
const { channels, channelGroups, authKeys, uuids, read, write, manage, delete: del, get, join, update, ttl, } = this.parameters;
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (channels && (channels === null || channels === void 0 ? void 0 : channels.length) > 0 ? { channel: channels.join(',') } : {})), (channelGroups && (channelGroups === null || channelGroups === void 0 ? void 0 : channelGroups.length) > 0 ? { 'channel-group': channelGroups.join(',') } : {})), (authKeys && (authKeys === null || authKeys === void 0 ? void 0 : authKeys.length) > 0 ? { auth: authKeys.join(',') } : {})), (uuids && (uuids === null || uuids === void 0 ? void 0 : uuids.length) > 0 ? { 'target-uuid': uuids.join(',') } : {})), { r: read ? '1' : '0', w: write ? '1' : '0', m: manage ? '1' : '0', d: del ? '1' : '0', g: get ? '1' : '0', j: join ? '1' : '0', u: update ? '1' : '0' }), (ttl || ttl === 0 ? { ttl } : {}));
const { channels, channelGroups, categories, authKeys, uuids, read, write, manage, delete: del, get, join, update, ttl, } = this.parameters;
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (channels && (channels === null || channels === void 0 ? void 0 : channels.length) > 0 ? { channel: channels.join(',') } : {})), (channelGroups && (channelGroups === null || channelGroups === void 0 ? void 0 : channelGroups.length) > 0 ? { 'channel-group': channelGroups.join(',') } : {})), (authKeys && (authKeys === null || authKeys === void 0 ? void 0 : authKeys.length) > 0 ? { auth: authKeys.join(',') } : {})), (uuids && (uuids === null || uuids === void 0 ? void 0 : uuids.length) > 0 ? { 'target-uuid': uuids.join(',') } : {})), (categories && categories.length > 0 ? { category: categories.join(',') } : {})), { r: read ? '1' : '0', w: write ? '1' : '0', m: manage ? '1' : '0', d: del ? '1' : '0', g: get ? '1' : '0', j: join ? '1' : '0', u: update ? '1' : '0' }), (ttl || ttl === 0 ? { ttl } : {}));
}
}
exports.GrantRequest = GrantRequest;
35 changes: 31 additions & 4 deletions lib/core/endpoints/access_manager/grant_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,17 @@ class GrantTokenRequest extends request_1.AbstractRequest {
}
validate() {
const { keySet: { subscribeKey, publishKey, secretKey }, resources, patterns, } = this.parameters;
// DataSync projections are a standalone grant target — a request carrying only projections
// (no resources / patterns permissions) is still valid.
// DataSync projections and category grants are standalone grant targets — a request carrying
// only projections or only categories (no resources / patterns permissions) is still valid.
const hasProjections = this.buildProjections() !== undefined;
const hasCategories = this.buildCategories() !== undefined;
if (!subscribeKey)
return 'Missing Subscribe Key';
if (!publishKey)
return 'Missing Publish Key';
if (!secretKey)
return 'Missing Secret Key';
if (!resources && !patterns && !hasProjections)
if (!resources && !patterns && !hasProjections && !hasCategories)
return 'Missing either Resources or Patterns';
// A single token can grant DataSync `users`, `channels`, `groups`, and `dataSync` together.
// together. `uuids` / `spaces` / `authorized_uuid` are the deprecated App Context terminology;
Expand All @@ -77,7 +78,7 @@ class GrantTokenRequest extends request_1.AbstractRequest {
}
});
});
if (permissionsEmpty && !hasProjections)
if (permissionsEmpty && !hasProjections && !hasCategories)
return 'Missing values for either Resources or Patterns';
}
parse(response) {
Expand Down Expand Up @@ -153,6 +154,11 @@ class GrantTokenRequest extends request_1.AbstractRequest {
permissions.uuid = `${uuid}`;
permissions.resources = resourcePermissions;
permissions.patterns = patternPermissions;
// Category grants are a single integer per resource type. Omit the key entirely when none are
// set so tokens that don't use categories stay byte-for-byte identical.
const categories = this.buildCategories();
if (categories)
permissions.categories = categories;
// Merge DataSync projections into `meta` under `pn-projections`, preserving user-supplied meta.
// `pn-projections` is omitted entirely when no projections are set.
const projections = this.buildProjections();
Expand Down Expand Up @@ -219,6 +225,27 @@ class GrantTokenRequest extends request_1.AbstractRequest {
result.pat = pat;
return Object.keys(result).length > 0 ? result : undefined;
}
/**
* Build the `permissions.categories` payload.
*
* Category grants are a single integer per resource type, not a per-id bitmask. Only `get: true`
* is encoded, as `32`. Any other runtime flag is ignored so a non-TypeScript caller cannot send a
* value other than `32`. The payload is omitted entirely when no category is granted.
*
* @returns Encoded categories payload, or `undefined` when no category `get` is set.
*/
buildCategories() {
var _a, _b;
const categories = 'categories' in this.parameters ? this.parameters.categories : undefined;
if (!categories)
return undefined;
const result = {};
if (((_a = categories.channels) === null || _a === void 0 ? void 0 : _a.get) === true)
result.channels = 32;
if (((_b = categories.uuids) === null || _b === void 0 ? void 0 : _b.get) === true)
result.uuids = 32;
return Object.keys(result).length > 0 ? result : undefined;
}
/**
* Extract permissions bit from permission configuration object.
*
Expand Down
Loading
Loading