diff --git a/packages/agent/src/utils.ts b/packages/agent/src/utils.ts index 2ab38757f..a72c9f467 100644 --- a/packages/agent/src/utils.ts +++ b/packages/agent/src/utils.ts @@ -1,5 +1,5 @@ import type { DidUrlDereferencer } from '@web5/dids'; -import { PaginationCursor, RecordsDeleteMessage, RecordsWriteMessage } from '@tbd54566975/dwn-sdk-js'; +import { Jws, PaginationCursor, RecordsDeleteMessage, RecordsWriteMessage } from '@tbd54566975/dwn-sdk-js'; import { Readable } from '@web5/common'; import { utils as didUtils } from '@web5/dids'; @@ -42,6 +42,14 @@ export function getRecordAuthor(record: RecordsWriteMessage | RecordsDeleteMessa return Message.getAuthor(record); } +/** + * Get the `protocolRole` string from the signature payload of the given RecordsWriteMessage or RecordsDeleteMessage. + */ +export function getRecordProtocolRole(message: RecordsWriteMessage | RecordsDeleteMessage): string | undefined { + const signaturePayload = Jws.decodePlainObjectPayload(message.authorization.signature); + return signaturePayload?.protocolRole; +} + export function isRecordsWrite(obj: unknown): obj is RecordsWrite { // Validate that the given value is an object. if (!obj || typeof obj !== 'object' || obj === null) return false; diff --git a/packages/agent/tests/utils.spec.ts b/packages/agent/tests/utils.spec.ts index ebefcf424..f2c21f34d 100644 --- a/packages/agent/tests/utils.spec.ts +++ b/packages/agent/tests/utils.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { DateSort, Message, TestDataGenerator } from '@tbd54566975/dwn-sdk-js'; -import { getPaginationCursor, getRecordAuthor, getRecordMessageCid } from '../src/utils.js'; +import { getPaginationCursor, getRecordAuthor, getRecordMessageCid, getRecordProtocolRole } from '../src/utils.js'; describe('Utils', () => { describe('getPaginationCursor', () => { @@ -84,4 +84,28 @@ describe('Utils', () => { expect(deleteAuthorFromFunction!).to.equal(recordsDeleteAuthor.did); }); }); + + describe('getRecordProtocolRole', () => { + it('gets a protocol role from a RecordsWrite', async () => { + const recordsWrite = await TestDataGenerator.generateRecordsWrite({ protocolRole: 'some-role' }); + const role = getRecordProtocolRole(recordsWrite.message); + expect(role).to.equal('some-role'); + }); + + it('gets a protocol role from a RecordsDelete', async () => { + const recordsDelete = await TestDataGenerator.generateRecordsDelete({ protocolRole: 'some-role' }); + const role = getRecordProtocolRole(recordsDelete.message); + expect(role).to.equal('some-role'); + }); + + it('returns undefined if no role is defined', async () => { + const recordsWrite = await TestDataGenerator.generateRecordsWrite(); + const writeRole = getRecordProtocolRole(recordsWrite.message); + expect(writeRole).to.be.undefined; + + const recordsDelete = await TestDataGenerator.generateRecordsDelete(); + const deleteRole = getRecordProtocolRole(recordsDelete.message); + expect(deleteRole).to.be.undefined; + }); + }); }); \ No newline at end of file diff --git a/packages/api/src/dwn-api.ts b/packages/api/src/dwn-api.ts index bcc481358..96d0e62a6 100644 --- a/packages/api/src/dwn-api.ts +++ b/packages/api/src/dwn-api.ts @@ -699,7 +699,7 @@ export class DwnApi { */ remoteOrigin : request.from, delegateDid : this.delegateDid, - protocolRole : request.message.protocolRole, + protocolRole : agentRequest.messageParams.protocolRole, ...entry as DwnMessage[DwnInterface.RecordsWrite] }; const record = new Record(this.agent, recordOptions, this.permissionsApi); diff --git a/packages/api/src/record.ts b/packages/api/src/record.ts index ffb5451d8..bef0c5f9c 100644 --- a/packages/api/src/record.ts +++ b/packages/api/src/record.ts @@ -21,6 +21,7 @@ import { SendDwnRequest, PermissionsApi, AgentPermissionsApi, + getRecordProtocolRole } from '@web5/agent'; import { Convert, isEmptyObject, NodeStream, removeUndefinedProperties, Stream } from '@web5/common'; @@ -150,7 +151,7 @@ export type RecordUpdateParams = { datePublished?: DwnMessageDescriptor[DwnInterface.RecordsWrite]['datePublished']; /** The protocol role under which this record is written. */ - protocolRole?: string; + protocolRole?: RecordOptions['protocolRole']; /** The published status of the record. */ published?: DwnMessageDescriptor[DwnInterface.RecordsWrite]['published']; @@ -224,8 +225,6 @@ export class Record implements RecordModel { private _readableStream?: Readable; /** The origin DID if the record was fetched from a remote DWN. */ private _remoteOrigin?: string; - /** The protocolRole to use when reading the record */ - private _protocolRole?: string; // Private variables for DWN `RecordsWrite` message properties. @@ -251,6 +250,8 @@ export class Record implements RecordModel { private _initialWriteSigned: boolean; /** Unique identifier of the record. */ private _recordId: string; + /** Role under which the record is written. */ + private _protocolRole?: RecordOptions['protocolRole']; /** The `RecordsWriteMessage` descriptor unless the record is in a deleted state */ private get _recordsWriteDescriptor() { @@ -330,6 +331,9 @@ export class Record implements RecordModel { /** Record's signatures attestation */ get attestation(): DwnMessage[DwnInterface.RecordsWrite]['attestation'] | undefined { return this._attestation; } + /** Role under which the author is writing the record */ + get protocolRole() { return this._protocolRole; } + /** Record's deleted state (true/false) */ get deleted() { return isDwnMessage(DwnInterface.RecordsDelete, this.rawMessage); } @@ -782,6 +786,7 @@ export class Record implements RecordModel { // Only update the local Record instance mutable properties if the record was successfully (over)written. this._authorization = responseMessage.authorization; + this._protocolRole = updateMessage.protocolRole; mutableDescriptorProperties.forEach(property => { this._descriptor[property] = responseMessage.descriptor[property]; }); @@ -831,8 +836,7 @@ export class Record implements RecordModel { // Check to see if the provided protocolRole is different from the current protocolRole // If so we need to construct a delete message with the new protocolRole, otherwise we can use the existing - // NOTE: currently this is testing the instance _protocolRole, not the actual signature payload. - const differentRole = deleteParams?.protocolRole ? this._protocolRole !== deleteParams.protocolRole : false; + const differentRole = deleteParams?.protocolRole ? getRecordProtocolRole(this.rawMessage) !== deleteParams.protocolRole : false; if (this.deleted && !differentRole) { // if we have a delete message we can just use it deleteOptions.rawMessage = this.rawMessage as DwnMessage[DwnInterface.RecordsDelete];