Skip to content

Commit

Permalink
feat(lib): check permissions before sending signals (#161)
Browse files Browse the repository at this point in the history
* feat(lib): check permissions before sending signals

* feat(lib): be more aggressive towards error

* PubNub SDK v0.6.0 release.

---------

Co-authored-by: PubNub Release Bot <[email protected]>
  • Loading branch information
piotr-suwala and pubnub-release-bot authored Mar 11, 2024
1 parent 7816cb9 commit 2c47233
Show file tree
Hide file tree
Showing 6 changed files with 433 additions and 7 deletions.
9 changes: 8 additions & 1 deletion .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
---
name: pubnub-js-chat
version: v0.5.2
version: v0.6.0
scm: github.com/pubnub/js-chat
schema: 1
files:
- lib/dist/index.js
changelog:
- date: 2024-03-11
version: v0.6.0
changes:
- type: feature
text: "Check PAM permissions before sending signals."
- type: feature
text: "Allow custom payloads while sending and receiving messages."
- date: 2024-01-16
version: v0.5.2
changes:
Expand Down
2 changes: 1 addition & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pubnub/chat",
"version": "0.5.2",
"version": "0.6.0",
"description": "PubNub JavaScript Chat SDK",
"author": "PubNub <[email protected]>",
"license": "SEE LICENSE IN LICENSE",
Expand Down
43 changes: 43 additions & 0 deletions lib/src/access-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { GrantTokenPermissions } from "pubnub"
import type { Chat } from "./entities/chat"

export class AccessManager {
chat: Chat

constructor(chat: Chat) {
this.chat = chat
}

canI({
permission,
resourceType,
resourceName,
}: {
permission: keyof GrantTokenPermissions
resourceName: string
resourceType: "channels" | "uuids"
}) {
const authKey = this.chat.config.authKey
// we assume PAM is not enabled
if (!authKey) {
return true
}

const parsedToken = this.chat.sdk.parseToken(authKey)
const resourcePermission = parsedToken.resources?.[resourceType]?.[resourceName]?.[permission]
if (typeof resourcePermission === "boolean") {
return resourcePermission
}
const resourcePatterns = parsedToken.patterns?.[resourceType] || {}
const resourcePatternsKeys = Object.keys(resourcePatterns)
for (const pattern of resourcePatternsKeys) {
const regexp = new RegExp(pattern)
const matches = regexp.test(resourceName)
if (matches) {
return resourcePatterns[pattern][permission] || false
}
}

return false
}
}
22 changes: 21 additions & 1 deletion lib/src/entities/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getErrorProxiedEntity, ErrorLogger } from "../error-logging"
import { cyrb53a } from "../hash"
import { uuidv4 } from "../uuidv4"
import { defaultEditActionName, defaultDeleteActionName } from "../default-values"
import { AccessManager } from "../access-manager"

export type ChatConfig = {
saveDebugLog: boolean
Expand All @@ -47,6 +48,7 @@ export type ChatConfig = {
editMessageActionName?: string
deleteMessageActionName?: string
}
authKey?: string
}

type ChatConstructor = Partial<ChatConfig> & PubNub.PubnubConfig
Expand All @@ -69,6 +71,8 @@ export class Chat {
readonly editMessageActionName: string
/** @internal */
readonly deleteMessageActionName: string
/** @internal */
readonly accessManager: AccessManager

/** @internal */
private constructor(params: ChatConstructor) {
Expand Down Expand Up @@ -136,7 +140,10 @@ export class Chat {
getMessagePublishBody: customPayloads?.getMessagePublishBody,
getMessageResponseBody: customPayloads?.getMessageResponseBody,
},
authKey: pubnubConfig.authKey,
} as ChatConfig

this.accessManager = new AccessManager(this)
}

static async init(params: ChatConstructor) {
Expand Down Expand Up @@ -185,7 +192,20 @@ export class Chat {

/* @internal */
signal(params: { channel: string; message: any }) {
return this.sdk.signal(params)
const canISendSignal = this.accessManager.canI({
permission: "write",
resourceName: params.channel,
resourceType: "channels",
})
if (canISendSignal) {
return this.sdk.signal(params)
}

throw new Error(
`You tried to send a signal containing message: ${JSON.stringify(
params.message
)} to channel: ${params.channel} but PubNub Access Manager prevented you from doing so.`
)
}

/**
Expand Down
20 changes: 16 additions & 4 deletions lib/src/entities/membership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,23 @@ export class Membership {
custom: { ...this.custom, lastReadMessageTimetoken: timetoken },
})

await this.chat.emitEvent({
channel: this.channel.id,
type: "receipt",
payload: { messageTimetoken: timetoken },
const canISendSignal = this.chat.accessManager.canI({
permission: "write",
resourceName: this.channel.id,
resourceType: "channels",
})
if (canISendSignal) {
await this.chat.emitEvent({
channel: this.channel.id,
type: "receipt",
payload: { messageTimetoken: timetoken },
})
}
if (!canISendSignal && this.chat.config.saveDebugLog) {
console.warn(
`'receipt' event was not sent to channel '${this.channel.id}' because PAM did not allow it.`
)
}

return response
} catch (error) {
Expand Down
Loading

0 comments on commit 2c47233

Please sign in to comment.