From f076455fe999f8d9cdc561cc23217bab4a5016b9 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Wed, 27 Sep 2023 16:15:48 -0700 Subject: [PATCH 1/6] Authorize RecordsWrites with protocol roles --- README.md | 2 +- .../records-write-authorization-payload.json | 3 + src/core/dwn-error.ts | 1 + src/core/protocol-authorization.ts | 25 ++-- src/interfaces/records-write.ts | 16 +- src/types/records-types.ts | 1 + tests/handlers/records-write.spec.ts | 137 ++++++++++++++++++ tests/utils/test-data-generator.ts | 2 + 8 files changed, 165 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index a7590b93d..f07f23665 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉 # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-97.78%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.05%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.78%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-97.78%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.92%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.78%25-brightgreen.svg?style=flat) - [Introduction](#introduction) - [Installation](#installation) diff --git a/json-schemas/authorization-payloads/records-write-authorization-payload.json b/json-schemas/authorization-payloads/records-write-authorization-payload.json index 2c8e1e5f4..07fca6584 100644 --- a/json-schemas/authorization-payloads/records-write-authorization-payload.json +++ b/json-schemas/authorization-payloads/records-write-authorization-payload.json @@ -25,6 +25,9 @@ }, "permissionsGrantId": { "type": "string" + }, + "protocolRole": { + "type": "string" } } } diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index 07e80043f..cf7a47f3b 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -43,6 +43,7 @@ export enum DwnErrorCode { ProtocolAuthorizationMissingRole = 'ProtocolAuthorizationMissingRole', ProtocolAuthorizationMissingRuleSet = 'ProtocolAuthorizationMissingRuleSet', ProtocolAuthorizationNotARole = 'ProtocolAuthorizationNotARole', + ProtocolAuthorizationRoleMissingRecipient = 'ProtocolAuthorizationRoleMissingRecipient', ProtocolsConfigureGlobalRoleAtProhibitedProtocolPath = 'ProtocolsConfigureGlobalRoleAtProhibitedProtocolPath', ProtocolsConfigureInvalidRole = 'ProtocolsConfigureInvalidRole', ProtocolsConfigureInvalidActionMissingOf = 'ProtocolsConfigureInvalidActionMissingOf', diff --git a/src/core/protocol-authorization.ts b/src/core/protocol-authorization.ts index a548253cc..dc6761dea 100644 --- a/src/core/protocol-authorization.ts +++ b/src/core/protocol-authorization.ts @@ -271,11 +271,6 @@ export class ProtocolAuthorization { protocolDefinition: ProtocolDefinition, messageStore: MessageStore, ): Promise { - // Currently only RecordsReads may invoke a role - if (incomingMessage.message.descriptor.method !== DwnMethodName.Read) { - return; - } - const protocolRole = (incomingMessage as RecordsRead).authorizationPayload?.protocolRole; // Only verify role if there is a role being invoked @@ -347,10 +342,7 @@ export class ProtocolAuthorization { } // Get role being invoked. Currently only Reads support role-based authorization - let invokedRole: string | undefined; - if (incomingMessage.message.descriptor.method === DwnMethodName.Read) { - invokedRole = (incomingMessage as RecordsRead).authorizationPayload?.protocolRole; - } + const invokedRole = incomingMessage.authorizationPayload?.protocolRole; for (const actionRule of actionRules) { if (actionRule.can !== inboundMessageAction) { @@ -391,19 +383,20 @@ export class ProtocolAuthorization { inboundMessageRuleSet: ProtocolRuleSet, messageStore: MessageStore, ): Promise { - if (incomingMessage.message.descriptor.method !== DwnMethodName.Write) { - return; - } - const incomingRecordsWrite = incomingMessage as RecordsWrite; if (!inboundMessageRuleSet.$globalRole) { return; } - // FIXME(diehuxx): do we enforce presence of recipient for protocol records? I thought we required it - const recipient = incomingRecordsWrite.message.descriptor.recipient!; + const recipient = incomingRecordsWrite.message.descriptor.recipient; + if (recipient === undefined) { + throw new DwnError( + DwnErrorCode.ProtocolAuthorizationRoleMissingRecipient, + 'Role records must have a recipient' + ); + } const protocolPath = incomingRecordsWrite.message.descriptor.protocolPath!; - const filter = { + const filter: Filter = { interface : DwnInterfaceName.Records, method : DwnMethodName.Write, isLatestBaseState : true, diff --git a/src/interfaces/records-write.ts b/src/interfaces/records-write.ts index a5e9603a1..33dc7b26c 100644 --- a/src/interfaces/records-write.ts +++ b/src/interfaces/records-write.ts @@ -36,6 +36,7 @@ export type RecordsWriteOptions = { recipient?: string; protocol?: string; protocolPath?: string; + protocolRole?: string; contextId?: string; schema?: string; recordId?: string; @@ -267,7 +268,7 @@ export class RecordsWrite { const recordsWrite = new RecordsWrite(message); if (options.authorizationSigner !== undefined) { - await recordsWrite.sign(options.authorizationSigner, options.permissionsGrantId); + await recordsWrite.sign(options.authorizationSigner, { permissionsGrantId: options.permissionsGrantId, protocolRole: options.protocolRole }); } return recordsWrite; @@ -361,7 +362,7 @@ export class RecordsWrite { /** * Signs the RecordsWrite. */ - public async sign(signer: Signer, permissionsGrantId?: string): Promise { + public async sign(signer: Signer, options?: { permissionsGrantId?: string, protocolRole?: string }): Promise { const author = Jws.extractDid(signer.keyId); const descriptor = this._message.descriptor; @@ -383,7 +384,7 @@ export class RecordsWrite { this._message.attestation, this._message.encryption, signer, - permissionsGrantId + options ); this._message.authorization = authorization; @@ -650,7 +651,7 @@ export class RecordsWrite { attestation: GeneralJws | undefined, encryption: EncryptionProperty | undefined, signer: Signer, - permissionsGrantId: string | undefined, + additionalProperties?: { permissionsGrantId?: string, protocolRole?: string }, ): Promise { const authorizationPayload: RecordsWriteAuthorSignaturePayload = { recordId, @@ -663,7 +664,12 @@ export class RecordsWrite { if (contextId !== undefined) { authorizationPayload.contextId = contextId; } // assign `contextId` only if it is defined if (attestationCid !== undefined) { authorizationPayload.attestationCid = attestationCid; } // assign `attestationCid` only if it is defined if (encryptionCid !== undefined) { authorizationPayload.encryptionCid = encryptionCid; } // assign `encryptionCid` only if it is defined - if (permissionsGrantId !== undefined) { authorizationPayload.permissionsGrantId = permissionsGrantId; } + if (additionalProperties?.permissionsGrantId !== undefined) { + authorizationPayload.permissionsGrantId = additionalProperties?.permissionsGrantId; + } + if (additionalProperties?.protocolRole !== undefined) { + authorizationPayload.protocolRole = additionalProperties?.protocolRole; + } const authorizationPayloadBytes = Encoder.objectToBytes(authorizationPayload); diff --git a/src/types/records-types.ts b/src/types/records-types.ts index 8f5beff8c..6ce76a0dc 100644 --- a/src/types/records-types.ts +++ b/src/types/records-types.ts @@ -13,6 +13,7 @@ export type RecordsWriteDescriptor = { method: DwnMethodName.Write; protocol?: string; protocolPath?: string; + protocolRole?: string; recipient?: string; schema?: string; parentId?: string; diff --git a/tests/handlers/records-write.spec.ts b/tests/handlers/records-write.spec.ts index 0ee9af77e..aac201c70 100644 --- a/tests/handlers/records-write.spec.ts +++ b/tests/handlers/records-write.spec.ts @@ -1070,6 +1070,32 @@ export function testRecordsWriteHandler(): void { expect(updateFriendReply.status.code).to.equal(202); }); + it('rejects writes to a $globalRole is recipient is undefined', async () => { + // scenario: Alice writes a global role record with no recipient and it is rejected + + const alice = await DidKeyResolver.generate(); + + const protocolDefinition = friendRoleProtocolDefinition; + + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + author: alice, + protocolDefinition + }); + const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message); + expect(protocolWriteReply.status.code).to.equal(202); + + // Alice writes a 'friend' $globalRole record with no recipient + const friendRoleRecord = await TestDataGenerator.generateRecordsWrite({ + author : alice, + protocol : protocolDefinition.protocol, + protocolPath : 'friend', + data : new TextEncoder().encode('Bob is my friend'), + }); + const friendRoleReply = await dwn.processMessage(alice.did, friendRoleRecord.message, friendRoleRecord.dataStream); + expect(friendRoleReply.status.code).to.equal(401); + expect(friendRoleReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationRoleMissingRecipient); + }); + it('rejects writes to a $globalRole if there is already a record with the same role and recipient', async () => { // scenario: Alice adds Bob to the 'friend' role. Then she tries and fails to write another separate record // adding Bob as a 'friend' again. @@ -1156,6 +1182,117 @@ export function testRecordsWriteHandler(): void { expect(duplicateFriendReply.status.code).to.equal(202); }); }); + + describe('protocolRole based writes', () => { + it('uses a protocolRole to authorize a write', async () => { + // scenario: Alice gives Bob a friend role. Bob invokes his + // friend role in order to write a chat message + + const alice = await DidKeyResolver.generate(); + const bob = await DidKeyResolver.generate(); + + const protocolDefinition = friendRoleProtocolDefinition; + + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + author: alice, + protocolDefinition + }); + const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message); + expect(protocolWriteReply.status.code).to.equal(202); + + // Alice writes a 'friend' $globalRole record with Bob as recipient + const friendRoleRecord = await TestDataGenerator.generateRecordsWrite({ + author : alice, + recipient : bob.did, + protocol : protocolDefinition.protocol, + protocolPath : 'friend', + data : new TextEncoder().encode('Bob is my friend'), + }); + const friendRoleReply = await dwn.processMessage(alice.did, friendRoleRecord.message, friendRoleRecord.dataStream); + expect(friendRoleReply.status.code).to.equal(202); + + // Bob writes a 'chat' record + const chatRecord = await TestDataGenerator.generateRecordsWrite({ + author : bob, + recipient : alice.did, + protocol : protocolDefinition.protocol, + protocolPath : 'chat', + data : new TextEncoder().encode('Bob can write this cuz he is Alices friend'), + protocolRole : 'friend' + }); + const chatReply = await dwn.processMessage(alice.did, chatRecord.message, chatRecord.dataStream); + expect(chatReply.status.code).to.equal(202); + }); + + it('rejects role-authorized writes if the protocolRole is not a valid protocol path to a role record', async () => { + // scenario: Bob tries to invoke the 'chat' role to write to Alice's DWN, but 'chat' is not a role. + + const alice = await DidKeyResolver.generate(); + const bob = await DidKeyResolver.generate(); + + const protocolDefinition = friendRoleProtocolDefinition; + + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + author: alice, + protocolDefinition + }); + const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message); + expect(protocolWriteReply.status.code).to.equal(202); + + // Alice writes a 'chat' record with Bob as recipient + const chatRecord = await TestDataGenerator.generateRecordsWrite({ + author : alice, + recipient : bob.did, + protocol : protocolDefinition.protocol, + protocolPath : 'chat', + data : new TextEncoder().encode('Blah blah blah'), + }); + const chatReply = await dwn.processMessage(alice.did, chatRecord.message, chatRecord.dataStream); + expect(chatReply.status.code).to.equal(202); + + // Bob tries to invoke a 'chat' role but 'chat' is not a role + const writeChatRecord = await TestDataGenerator.generateRecordsWrite({ + author : bob, + recipient : bob.did, + protocol : protocolDefinition.protocol, + protocolPath : 'chat', + data : new TextEncoder().encode('Blah blah blah'), + protocolRole : 'chat', + }); + const chatReadReply = await dwn.processMessage(alice.did, writeChatRecord.message, writeChatRecord.dataStream); + expect(chatReadReply.status.code).to.equal(401); + expect(chatReadReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationNotARole); + }); + + it('rejects role-authorized writes if there is no active role for the recipient', async () => { + // scenario: Bob tries to invoke a role to write, but he has not been given one. + + const alice = await DidKeyResolver.generate(); + const bob = await DidKeyResolver.generate(); + + const protocolDefinition = friendRoleProtocolDefinition; + + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + author: alice, + protocolDefinition + }); + const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message); + expect(protocolWriteReply.status.code).to.equal(202); + + // Bob writes a 'chat' record invoking a friend role that he does not have + const chatRecord = await TestDataGenerator.generateRecordsWrite({ + author : bob, + recipient : bob.did, + protocol : protocolDefinition.protocol, + protocolPath : 'chat', + data : new TextEncoder().encode('Blah blah blah'), + protocolRole : 'friend' + }); + const chatReply = await dwn.processMessage(alice.did, chatRecord.message, chatRecord.dataStream); + expect(chatReply.status.code).to.equal(401); + expect(chatReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationMissingRole); + }); + }); }); it('should allow overwriting records by the same author', async () => { diff --git a/tests/utils/test-data-generator.ts b/tests/utils/test-data-generator.ts index 2b3a82e52..d8853ad94 100644 --- a/tests/utils/test-data-generator.ts +++ b/tests/utils/test-data-generator.ts @@ -110,6 +110,7 @@ export type GenerateRecordsWriteInput = { recipient?: string; protocol?: string; protocolPath?: string; + protocolRole?: string; contextId?: string; schema?: string; recordId?: string; @@ -399,6 +400,7 @@ export class TestDataGenerator { recipient : input?.recipient, protocol : input?.protocol, protocolPath : input?.protocolPath, + protocolRole : input?.protocolRole, contextId : input?.contextId, schema : input?.schema ?? `http://${TestDataGenerator.randomString(20)}`, recordId : input?.recordId, From 4ceb76d61e13dcb895e4ce0e31d7afc0196a4191 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Tue, 3 Oct 2023 14:37:04 -0700 Subject: [PATCH 2/6] Remove protocolRole from descriptor --- src/types/records-types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/records-types.ts b/src/types/records-types.ts index 6ce76a0dc..8f5beff8c 100644 --- a/src/types/records-types.ts +++ b/src/types/records-types.ts @@ -13,7 +13,6 @@ export type RecordsWriteDescriptor = { method: DwnMethodName.Write; protocol?: string; protocolPath?: string; - protocolRole?: string; recipient?: string; schema?: string; parentId?: string; From d1b6108b1b5b33bcbbe7ba6f52f1b9449ed3f927 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Tue, 3 Oct 2023 16:06:31 -0700 Subject: [PATCH 3/6] Remote outdated comment --- src/core/protocol-authorization.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/protocol-authorization.ts b/src/core/protocol-authorization.ts index dc6761dea..22afbefdd 100644 --- a/src/core/protocol-authorization.ts +++ b/src/core/protocol-authorization.ts @@ -341,7 +341,6 @@ export class ProtocolAuthorization { throw new Error(`no action rule defined for ${incomingMessage.message.descriptor.method}, ${author} is unauthorized`); } - // Get role being invoked. Currently only Reads support role-based authorization const invokedRole = incomingMessage.authorizationPayload?.protocolRole; for (const actionRule of actionRules) { From 206b4c430028675eaac8066ea8e1c5158b1b67d0 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Tue, 3 Oct 2023 16:20:38 -0700 Subject: [PATCH 4/6] Boost test coverage --- README.md | 2 +- src/interfaces/records-write.ts | 26 ++++++++----------- .../protocol-definitions/friend-role.json | 7 +++++ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index f07f23665..5e524cf56 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉 # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-97.78%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.92%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.78%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-97.8%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.12%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.8%25-brightgreen.svg?style=flat) - [Introduction](#introduction) - [Installation](#installation) diff --git a/src/interfaces/records-write.ts b/src/interfaces/records-write.ts index 33dc7b26c..cd19f9d65 100644 --- a/src/interfaces/records-write.ts +++ b/src/interfaces/records-write.ts @@ -400,7 +400,7 @@ export class RecordsWrite { await ProtocolAuthorization.authorize(tenant, this, this, messageStore); } else if (this.author === tenant) { // if author is the same as the target tenant, we can directly grant access - } else if (this.author !== undefined && this.authorizationPayload?.permissionsGrantId !== undefined) { + } else if (this.author !== undefined && this.authorizationPayload!.permissionsGrantId !== undefined) { await RecordsGrantAuthorization.authorizeWrite(tenant, this, this.author, messageStore); } else { throw new Error('message failed authorization'); @@ -653,23 +653,19 @@ export class RecordsWrite { signer: Signer, additionalProperties?: { permissionsGrantId?: string, protocolRole?: string }, ): Promise { - const authorizationPayload: RecordsWriteAuthorSignaturePayload = { - recordId, - descriptorCid - }; - const attestationCid = attestation ? await Cid.computeCid(attestation) : undefined; const encryptionCid = encryption ? await Cid.computeCid(encryption) : undefined; - if (contextId !== undefined) { authorizationPayload.contextId = contextId; } // assign `contextId` only if it is defined - if (attestationCid !== undefined) { authorizationPayload.attestationCid = attestationCid; } // assign `attestationCid` only if it is defined - if (encryptionCid !== undefined) { authorizationPayload.encryptionCid = encryptionCid; } // assign `encryptionCid` only if it is defined - if (additionalProperties?.permissionsGrantId !== undefined) { - authorizationPayload.permissionsGrantId = additionalProperties?.permissionsGrantId; - } - if (additionalProperties?.protocolRole !== undefined) { - authorizationPayload.protocolRole = additionalProperties?.protocolRole; - } + const authorizationPayload: RecordsWriteAuthorSignaturePayload = { + recordId, + descriptorCid, + contextId, + attestationCid, + encryptionCid, + permissionsGrantId : additionalProperties?.permissionsGrantId, + protocolRole : additionalProperties?.protocolRole + }; + removeUndefinedProperties(authorizationPayload); const authorizationPayloadBytes = Encoder.objectToBytes(authorizationPayload); diff --git a/tests/vectors/protocol-definitions/friend-role.json b/tests/vectors/protocol-definitions/friend-role.json index c9eb92ab2..128e91341 100644 --- a/tests/vectors/protocol-definitions/friend-role.json +++ b/tests/vectors/protocol-definitions/friend-role.json @@ -9,8 +9,15 @@ "friend": { "$globalRole": true }, + "fan": { + "$globalRole": true + }, "chat": { "$actions": [ + { + "role": "fan", + "can": "read" + }, { "role": "friend", "can": "write" From 4b417e56d0eef7d0a8eb8572bbc07743f7be381e Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Tue, 3 Oct 2023 16:33:15 -0700 Subject: [PATCH 5/6] Test coverage badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e524cf56..9767f770d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉 # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-97.8%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.12%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.8%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-97.8%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.18%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.8%25-brightgreen.svg?style=flat) - [Introduction](#introduction) - [Installation](#installation) From ad4b547240ac3e4808ddc2f1b91fa9e081a8ad26 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Wed, 4 Oct 2023 10:41:17 -0700 Subject: [PATCH 6/6] Slight test coverage boost --- README.md | 2 +- src/interfaces/protocols-query.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1a9aec625..e5822d6e9 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉 # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-97.8%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.19%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.8%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-97.8%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.25%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.8%25-brightgreen.svg?style=flat) - [Introduction](#introduction) - [Installation](#installation) diff --git a/src/interfaces/protocols-query.ts b/src/interfaces/protocols-query.ts index 7e2a99e62..69483def9 100644 --- a/src/interfaces/protocols-query.ts +++ b/src/interfaces/protocols-query.ts @@ -78,8 +78,8 @@ export class ProtocolsQuery extends Message { // if author is the same as the target tenant, we can directly grant access if (this.author === tenant) { return; - } else if (this.authorizationPayload?.permissionsGrantId) { - await GrantAuthorization.authorizeGenericMessage(tenant, this, this.author!, messageStore); + } else if (this.author !== undefined && this.authorizationPayload!.permissionsGrantId) { + await GrantAuthorization.authorizeGenericMessage(tenant, this, this.author, messageStore); } else { throw new DwnError( DwnErrorCode.ProtocolsQueryUnauthorized,