From b1ea021c5816e35d6cc350ca2742265b5ce985f0 Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Fri, 14 Jun 2024 18:23:48 -0700 Subject: [PATCH] Improved typing of PermissionGrant.parse() to avoid misuse (#760) 1. Improved typing of PermissionGrant.parse() to avoid misuse 2. Detected and fixed a couple of unimportant misuses in the tests after the above change (case in point). --- src/core/message.ts | 4 ++-- src/interfaces/records-delete.ts | 4 ++-- src/interfaces/records-query.ts | 4 ++-- src/interfaces/records-read.ts | 4 ++-- src/interfaces/records-subscribe.ts | 4 ++-- src/interfaces/records-write.ts | 9 +++++---- src/protocols/permission-grant.ts | 8 ++++---- src/protocols/permissions.ts | 2 +- src/types/message-types.ts | 3 ++- src/types/records-types.ts | 2 +- tests/interfaces/records-write.spec.ts | 4 ++-- 11 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/core/message.ts b/src/core/message.ts index 3b3e9bfd1..9b8bda31a 100644 --- a/src/core/message.ts +++ b/src/core/message.ts @@ -1,5 +1,5 @@ +import type { DataEncodedRecordsWriteMessage } from '../types/records-types.js'; import type { GeneralJws } from '../types/jws-types.js'; -import type { RecordsWriteMessage } from '../types/records-types.js'; import type { Signer } from '../types/signer.js'; import type { AuthorizationModel, Descriptor, GenericMessage, GenericSignaturePayload } from '../types/message-types.js'; @@ -79,7 +79,7 @@ export class Message { public static async createAuthorization(input: { descriptor: Descriptor, signer: Signer, - delegatedGrant?: RecordsWriteMessage, + delegatedGrant?: DataEncodedRecordsWriteMessage, permissionGrantId?: string, protocolRole?: string }): Promise { diff --git a/src/interfaces/records-delete.ts b/src/interfaces/records-delete.ts index aaa433010..3416eeee9 100644 --- a/src/interfaces/records-delete.ts +++ b/src/interfaces/records-delete.ts @@ -1,7 +1,7 @@ import type { KeyValues } from '../types/query-types.js'; import type { MessageStore } from '../types//message-store.js'; import type { Signer } from '../types/signer.js'; -import type { RecordsDeleteDescriptor, RecordsDeleteMessage, RecordsWriteMessage } from '../types/records-types.js'; +import type { DataEncodedRecordsWriteMessage, RecordsDeleteDescriptor, RecordsDeleteMessage, RecordsWriteMessage } from '../types/records-types.js'; import { AbstractMessage } from '../core/abstract-message.js'; import { Message } from '../core/message.js'; @@ -26,7 +26,7 @@ export type RecordsDeleteOptions = { /** * The delegated grant to sign on behalf of the logical author, which is the grantor (`grantedBy`) of the delegated grant. */ - delegatedGrant?: RecordsWriteMessage; + delegatedGrant?: DataEncodedRecordsWriteMessage; }; export class RecordsDelete extends AbstractMessage { diff --git a/src/interfaces/records-query.ts b/src/interfaces/records-query.ts index e1e47c3c6..6c12f4709 100644 --- a/src/interfaces/records-query.ts +++ b/src/interfaces/records-query.ts @@ -1,7 +1,7 @@ import type { MessageStore } from '../types//message-store.js'; import type { Pagination } from '../types/message-types.js'; import type { Signer } from '../types/signer.js'; -import type { RecordsFilter, RecordsQueryDescriptor, RecordsQueryMessage, RecordsWriteMessage } from '../types/records-types.js'; +import type { DataEncodedRecordsWriteMessage, RecordsFilter, RecordsQueryDescriptor, RecordsQueryMessage } from '../types/records-types.js'; import { AbstractMessage } from '../core/abstract-message.js'; import { DateSort } from '../types/records-types.js'; @@ -26,7 +26,7 @@ export type RecordsQueryOptions = { /** * The delegated grant to sign on behalf of the logical author, which is the grantor (`grantedBy`) of the delegated grant. */ - delegatedGrant?: RecordsWriteMessage; + delegatedGrant?: DataEncodedRecordsWriteMessage; }; /** diff --git a/src/interfaces/records-read.ts b/src/interfaces/records-read.ts index bbd1a4c89..c1ca6bda5 100644 --- a/src/interfaces/records-read.ts +++ b/src/interfaces/records-read.ts @@ -1,6 +1,6 @@ import type { MessageStore } from '../types//message-store.js'; import type { Signer } from '../types/signer.js'; -import type { RecordsFilter , RecordsReadDescriptor, RecordsReadMessage, RecordsWriteMessage } from '../types/records-types.js'; +import type { DataEncodedRecordsWriteMessage, RecordsFilter , RecordsReadDescriptor, RecordsReadMessage, RecordsWriteMessage } from '../types/records-types.js'; import { AbstractMessage } from '../core/abstract-message.js'; import { Message } from '../core/message.js'; @@ -25,7 +25,7 @@ export type RecordsReadOptions = { /** * The delegated grant to sign on behalf of the logical author, which is the grantor (`grantedBy`) of the delegated grant. */ - delegatedGrant?: RecordsWriteMessage; + delegatedGrant?: DataEncodedRecordsWriteMessage; }; export class RecordsRead extends AbstractMessage { diff --git a/src/interfaces/records-subscribe.ts b/src/interfaces/records-subscribe.ts index ef5b2ca39..2719fd375 100644 --- a/src/interfaces/records-subscribe.ts +++ b/src/interfaces/records-subscribe.ts @@ -1,6 +1,6 @@ import type { MessageStore } from '../types/message-store.js'; import type { Signer } from '../types/signer.js'; -import type { RecordsFilter, RecordsSubscribeDescriptor, RecordsSubscribeMessage, RecordsWriteMessage } from '../types/records-types.js'; +import type { DataEncodedRecordsWriteMessage, RecordsFilter, RecordsSubscribeDescriptor, RecordsSubscribeMessage } from '../types/records-types.js'; import { AbstractMessage } from '../core/abstract-message.js'; import { Message } from '../core/message.js'; @@ -22,7 +22,7 @@ export type RecordsSubscribeOptions = { /** * The delegated grant to sign on behalf of the logical author, which is the grantor (`grantedBy`) of the delegated grant. */ - delegatedGrant?: RecordsWriteMessage; + delegatedGrant?: DataEncodedRecordsWriteMessage; }; /** diff --git a/src/interfaces/records-write.ts b/src/interfaces/records-write.ts index d3b6c1583..c1fcb558f 100644 --- a/src/interfaces/records-write.ts +++ b/src/interfaces/records-write.ts @@ -5,6 +5,7 @@ import type { MessageStore } from '../types/message-store.js'; import type { PublicJwk } from '../types/jose-types.js'; import type { Signer } from '../types/signer.js'; import type { + DataEncodedRecordsWriteMessage, EncryptedKey, EncryptionProperty, InternalRecordsWriteMessage, @@ -66,7 +67,7 @@ export type RecordsWriteOptions = { /** * The delegated grant invoked to sign on behalf of the logical author, which is the grantor of the delegated grant. */ - delegatedGrant?: RecordsWriteMessage; + delegatedGrant?: DataEncodedRecordsWriteMessage; attestationSigners?: Signer[]; encryptionInput?: EncryptionInput; @@ -148,7 +149,7 @@ export type CreateFromOptions = { /** * The delegated grant to sign on behalf of the logical author, which is the grantor (`grantedBy`) of the delegated grant. */ - delegatedGrant?: RecordsWriteMessage; + delegatedGrant?: DataEncodedRecordsWriteMessage; attestationSigners?: Signer[]; encryptionInput?: EncryptionInput; @@ -491,7 +492,7 @@ export class RecordsWrite implements MessageInterface { */ public async sign(options: { signer: Signer, - delegatedGrant?: RecordsWriteMessage, + delegatedGrant?: DataEncodedRecordsWriteMessage, permissionGrantId?: string, protocolRole?: string }): Promise { @@ -576,7 +577,7 @@ export class RecordsWrite implements MessageInterface { * This is used when a DWN owner-delegate wants to retain a copy of a message that the owner did not author. * NOTE: requires the `RecordsWrite` to already have the author's signature. */ - public async signAsOwnerDelegate(signer: Signer, delegatedGrant: RecordsWriteMessage): Promise { + public async signAsOwnerDelegate(signer: Signer, delegatedGrant: DataEncodedRecordsWriteMessage): Promise { if (this._author === undefined) { throw new DwnError( DwnErrorCode.RecordsWriteSignAsOwnerDelegateUnknownAuthor, diff --git a/src/protocols/permission-grant.ts b/src/protocols/permission-grant.ts index 1ed9a7794..48f5dc328 100644 --- a/src/protocols/permission-grant.ts +++ b/src/protocols/permission-grant.ts @@ -1,4 +1,4 @@ -import type { RecordsQueryReplyEntry, RecordsWriteMessage } from '../types/records-types.js'; +import type { DataEncodedRecordsWriteMessage } from '../types/records-types.js'; import type { PermissionConditions, PermissionGrantData, PermissionScope } from '../types/permission-types.js'; @@ -61,7 +61,7 @@ export class PermissionGrant { */ public readonly conditions?: PermissionConditions; - public static async parse(message: RecordsWriteMessage): Promise { + public static async parse(message: DataEncodedRecordsWriteMessage): Promise { const permissionGrant = new PermissionGrant(message); return permissionGrant; } @@ -69,7 +69,7 @@ export class PermissionGrant { /** * Creates a Permission Grant abstraction for */ - private constructor(message: RecordsWriteMessage) { + private constructor(message: DataEncodedRecordsWriteMessage) { // properties derived from the generic DWN message properties this.id = message.recordId; this.grantor = Message.getSigner(message)!; @@ -77,7 +77,7 @@ export class PermissionGrant { this.dateGranted = message.descriptor.dateCreated; // properties from the data payload itself. - const permissionGrantEncoded = (message as RecordsQueryReplyEntry).encodedData!; + const permissionGrantEncoded = message.encodedData; const permissionGrant = Encoder.base64UrlToObject(permissionGrantEncoded) as PermissionGrantData; this.dateExpires = permissionGrant.dateExpires; this.delegated = permissionGrant.delegated; diff --git a/src/protocols/permissions.ts b/src/protocols/permissions.ts index 05ea6da63..399283db3 100644 --- a/src/protocols/permissions.ts +++ b/src/protocols/permissions.ts @@ -377,7 +377,7 @@ export class PermissionsProtocol { ); } - const permissionGrantMessage = possibleGrantMessage as RecordsWriteMessage; + const permissionGrantMessage = possibleGrantMessage as DataEncodedRecordsWriteMessage; const permissionGrant = await PermissionGrant.parse(permissionGrantMessage); return permissionGrant; diff --git a/src/types/message-types.ts b/src/types/message-types.ts index 18fe3e462..43cfb6d91 100644 --- a/src/types/message-types.ts +++ b/src/types/message-types.ts @@ -45,7 +45,7 @@ type DelegatedGrantRecordsWriteMessage = { }, recordId: string, contextId?: string; - // NOTE: This is a direct copy of `RecordsWriteDescriptor` to avoid circular references. + // NOTE: This is a direct expansion and copy of `DataEncodedRecordsWriteMessage` to avoid circular references. descriptor: { interface: DwnInterfaceName.Records; method: DwnMethodName.Write; @@ -62,6 +62,7 @@ type DelegatedGrantRecordsWriteMessage = { datePublished?: string; dataFormat: string; }; + encodedData: string; }; /** diff --git a/src/types/records-types.ts b/src/types/records-types.ts index 340d60378..4d94d0fd7 100644 --- a/src/types/records-types.ts +++ b/src/types/records-types.ts @@ -112,7 +112,7 @@ export type DataEncodedRecordsWriteMessage = RecordsWriteMessage & { /** * The encoded data of the record if the data associated with the record is equal or smaller than `DwnConstant.maxDataSizeAllowedToBeEncoded`. */ - encodedData?: string; + encodedData: string; }; export type RecordsQueryDescriptor = { diff --git a/tests/interfaces/records-write.spec.ts b/tests/interfaces/records-write.spec.ts index 354378d5d..f6b4cd3af 100644 --- a/tests/interfaces/records-write.spec.ts +++ b/tests/interfaces/records-write.spec.ts @@ -278,7 +278,7 @@ describe('RecordsWrite', () => { }); const createPromise = RecordsWrite.create({ - delegatedGrant : grantToBob.recordsWrite.message, + delegatedGrant : grantToBob.dataEncodedMessage, dataFormat : 'application/octet-stream', data : TestDataGenerator.randomBytes(10), }); @@ -452,7 +452,7 @@ describe('RecordsWrite', () => { scope }); - await expect(recordsWrite.signAsOwnerDelegate(Jws.createSigner(bob), ownerDelegatedGrant.recordsWrite.message)) + await expect(recordsWrite.signAsOwnerDelegate(Jws.createSigner(bob), ownerDelegatedGrant.dataEncodedMessage)) .to.be.rejectedWith(DwnErrorCode.RecordsWriteSignAsOwnerDelegateUnknownAuthor); expect(recordsWrite.owner).to.be.undefined;