diff --git a/src/handlers/records-read.ts b/src/handlers/records-read.ts index 2f6b5185c..144855637 100644 --- a/src/handlers/records-read.ts +++ b/src/handlers/records-read.ts @@ -129,8 +129,10 @@ export class RecordsReadHandler implements MethodHandler { } else if (descriptor.published === true) { // authentication is not required for published data return; - } else if (recordsRead.author !== undefined && recordsRead.author === descriptor.recipient) { - // The recipient of a message may always read it + } else if (recordsRead.author !== undefined && + (recordsRead.author === descriptor.recipient || recordsRead.author === matchedRecordsWrite.author) + ) { + // The recipient or author of a message may always read it return; } else if (recordsRead.author !== undefined && recordsRead.signaturePayload!.permissionGrantId !== undefined) { const permissionGrant = await PermissionsProtocol.fetchGrant(tenant, messageStore, recordsRead.signaturePayload!.permissionGrantId); diff --git a/tests/handlers/records-read.spec.ts b/tests/handlers/records-read.spec.ts index 74cf790fe..092be215a 100644 --- a/tests/handlers/records-read.spec.ts +++ b/tests/handlers/records-read.spec.ts @@ -106,7 +106,7 @@ export function testRecordsReadHandler(): void { expect(ArrayUtility.byteArraysEqual(dataFetched, dataBytes!)).to.be.true; }); - it('should not allow non-tenant to RecordsRead their a record data', async () => { + it('should not allow non-tenant to RecordsRead a record', async () => { const alice = await TestDataGenerator.generateDidKeyPersona(); // insert data @@ -205,6 +205,72 @@ export function testRecordsReadHandler(): void { expect(ArrayUtility.byteArraysEqual(dataFetched, dataBytes!)).to.be.true; }); + it('should allow a non-tenant to read RecordsRead data they have authored', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + const carol = await TestDataGenerator.generateDidKeyPersona(); + + // Alice installs a protocol that allows anyone to write foo record + const protocolDefinition:ProtocolDefinition = { + published : true, + protocol : 'https://example.com/foo', + types : { + foo: {} + }, + structure: { + foo: { + $actions: [{ + who : 'anyone', + can : ['create'] + }] + } + } + }; + + const configureProtocol = await TestDataGenerator.generateProtocolsConfigure({ + author : alice, + protocolDefinition : protocolDefinition, + }); + const configureProtocolReply = await dwn.processMessage(alice.did, configureProtocol.message); + expect(configureProtocolReply.status.code).to.equal(202); + + // Bob writes a foo record to Alice's DWN + const { message, dataStream, dataBytes } = await TestDataGenerator.generateRecordsWrite({ + author : bob, + protocol : protocolDefinition.protocol, + protocolPath : 'foo', + }); + const writeReply = await dwn.processMessage(alice.did, message, { dataStream }); + expect(writeReply.status.code).to.equal(202); + + // Bob reads the record he sent to Alice from Alice's DWN + const recordsRead = await RecordsRead.create({ + filter: { + recordId: message.recordId, + }, + signer: Jws.createSigner(bob) + }); + + const readReply = await dwn.processMessage(alice.did, recordsRead.message); + expect(readReply.status.code).to.equal(200); + expect(readReply.record).to.exist; + expect(readReply.record?.descriptor).to.exist; + + const dataFetched = await DataStream.toBytes(readReply.record!.data!); + expect(ArrayUtility.byteArraysEqual(dataFetched, dataBytes!)).to.be.true; + + // carol attempts to read Bob's record + const carolRecordsRead = await RecordsRead.create({ + filter: { + recordId: message.recordId, + }, + signer: Jws.createSigner(carol) + }); + + const carolReadReply = await dwn.processMessage(alice.did, carolRecordsRead.message); + expect(carolReadReply.status.code).to.equal(401); + }); + it('should include `initialWrite` property if RecordsWrite is not initial write', async () => { const alice = await TestDataGenerator.generateDidKeyPersona(); const write = await TestDataGenerator.generateRecordsWrite({ author: alice, published: false });