From 263d9fdaec8a4a96cb80ab3bd79d991064b8b1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20B=C3=A9k=C3=A9si?= Date: Wed, 27 May 2026 15:07:46 +0200 Subject: [PATCH 1/3] feat: add access revoke command --- src/command/access/index.ts | 3 +- src/command/access/revoke.ts | 70 ++++++++++++++++++++++++++++++++++++ test/command/access.spec.ts | 65 +++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/command/access/revoke.ts diff --git a/src/command/access/index.ts b/src/command/access/index.ts index 50abfde3..834a32a6 100644 --- a/src/command/access/index.ts +++ b/src/command/access/index.ts @@ -1,11 +1,12 @@ import { GroupCommand } from 'furious-commander' import { Grant } from './grant' import { Init } from './init' +import { Revoke } from './revoke' export class Access implements GroupCommand { public readonly name = 'access' public readonly description = 'Share access to your uploaded files/folders' - public subCommandClasses = [Init, Grant] + public subCommandClasses = [Init, Grant, Revoke] } diff --git a/src/command/access/revoke.ts b/src/command/access/revoke.ts new file mode 100644 index 00000000..66023b32 --- /dev/null +++ b/src/command/access/revoke.ts @@ -0,0 +1,70 @@ +import { LeafCommand, Option } from 'furious-commander' +import { exit } from 'process' +import { AccessHistory } from '../../service/access' +import { AccessHistoryOperation } from '../../service/access/types/history-event' +import { createKeyValue, errorText, successText } from '../../utils/text' +import { AccessCommand } from './access-command' + +export class Revoke extends AccessCommand implements LeafCommand { + public readonly name = 'revoke' + + public readonly description = 'Remove grantees from an existing grantee list' + + @Option({ + key: 'list-name', + alias: 'n', + description: 'Name of the grantee list', + required: true, + type: 'string', + }) + public listName!: string + + @Option({ + key: 'grantee', + alias: 'g', + description: 'Public address of the grantee(s)', + type: 'string', + array: true, + }) + public grantees!: string[] + + public async run(): Promise { + super.init() + + if (this.grantees.length === 0) { + this.console.error(errorText('At least one grantee must be specified!')) + + exit(1) + } + + const accessHistory = new AccessHistory(this.commandConfig, this.console) + const lastHistoryEvent = accessHistory.getEvents(this.listName).sort((a, b) => b.createdAt - a.createdAt)[0] + + if (!lastHistoryEvent) { + this.console.error(errorText(`Grantee list with name '${this.listName}' does not exist!`)) + + exit(1) + } + const stampId = lastHistoryEvent.stampId + const granteeListRef = lastHistoryEvent.granteeListRef + const historyAddress = lastHistoryEvent.historyAddress + const response = await this.bee.patchGrantees(stampId, granteeListRef, historyAddress, { revoke: this.grantees }) + + if (response.status === 200) { + this.console.log(successText(`Access revoked from ${this.grantees.join(', ')}!`)) + } + + accessHistory.addEvent(this.listName, { + stampId: stampId, + historyAddress: response.historyref.toHex(), + granteeListRef: response.ref.toHex(), + operation: AccessHistoryOperation.Revoke, + createdAt: Date.now(), + }) + + if (this.verbose) { + this.console.log(createKeyValue('Grantee list reference', response.ref.toHex())) + this.console.log(createKeyValue('History address', response.historyref.toHex())) + } + } +} diff --git a/test/command/access.spec.ts b/test/command/access.spec.ts index 4b00ac30..b0d3c013 100644 --- a/test/command/access.spec.ts +++ b/test/command/access.spec.ts @@ -97,6 +97,71 @@ describeCommand( }) }) }) + + describe('revoke', () => { + it('should revoke access from a grantee', async () => { + const pssAddress = await getPssAddress('http://localhost:21633') + await invokeTestCli([ + 'access', + 'init', + ...getStampOption(), + '-n', + 'test-access', + '--grantee', + pssAddress.toHex(), + ]) + await System.sleepMillis(1000) + await invokeTestCli(['access', 'revoke', '--list-name', 'test-access', '--grantee', pssAddress.toHex()]) + expect(getLastMessage()).toContain(`Access revoked from ${pssAddress.toHex()}!`) + }) + + describe('when grantee list does not exist', () => { + it('should show error message', async () => { + await invokeTestCli(['access', 'revoke', '-n', 'nonexistent-list', '-g', '0x123']) + expect(consoleMessages[0]).toContain("Grantee list with name 'nonexistent-list' does not exist!") + expect(consoleMessages[1]).toContain('process.exit() was called with code 1') + }) + }) + + describe('when no grantees are specified', () => { + it('should show error message', async () => { + await invokeTestCli(['access', 'init', ...getStampOption(), '-n', 'test-access']) + await System.sleepMillis(1000) + await invokeTestCli(['access', 'revoke', '--list-name', 'test-access']) + expect(consoleMessages[1]).toContain('At least one grantee must be specified!') + expect(consoleMessages[2]).toContain('process.exit() was called with code 1') + }) + }) + + describe('when verbose option is used', () => { + it('should show grantee list reference and history address', async () => { + const pssAddress = await getPssAddress('http://localhost:21633') + await invokeTestCli([ + 'access', + 'init', + ...getStampOption(), + '-n', + 'test-access', + '--grantee', + pssAddress.toHex(), + ]) + await System.sleepMillis(1000) + await invokeTestCli([ + 'access', + 'revoke', + '--list-name', + 'test-access', + '--grantee', + pssAddress.toHex(), + '--verbose', + ]) + expect(getNthLastMessage(2)).toContain('Grantee list reference') + expect(getNthLastMessage(2)).toMatch(/[a-f0-9]{64}/g) + expect(getLastMessage()).toContain('History address') + expect(getLastMessage()).toMatch(/[a-f0-9]{64}/g) + }) + }) + }) }, { configFileName: 'access' }, ) From 19ce83c755a35dc4e7c17131a04f9f6a33a57f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20B=C3=A9k=C3=A9si?= Date: Thu, 28 May 2026 11:49:26 +0200 Subject: [PATCH 2/3] fix: typo --- src/command/access/grant.ts | 2 +- src/command/access/revoke.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/command/access/grant.ts b/src/command/access/grant.ts index 3aabc5b5..e053bee0 100644 --- a/src/command/access/grant.ts +++ b/src/command/access/grant.ts @@ -51,7 +51,7 @@ export class Grant extends AccessCommand implements LeafCommand { const response = await this.bee.patchGrantees(stampId, granteeListRef, historyAddress, { add: this.grantees }) if (response.status === 200) { - this.console.log(successText(`Access granted to ${this.grantees.join(', ')}!`)) + this.console.log(successText(`Access granted to ${this.grantees.join(', ')}`)) } accessHistory.addEvent(this.listName, { diff --git a/src/command/access/revoke.ts b/src/command/access/revoke.ts index 66023b32..97482898 100644 --- a/src/command/access/revoke.ts +++ b/src/command/access/revoke.ts @@ -51,7 +51,7 @@ export class Revoke extends AccessCommand implements LeafCommand { const response = await this.bee.patchGrantees(stampId, granteeListRef, historyAddress, { revoke: this.grantees }) if (response.status === 200) { - this.console.log(successText(`Access revoked from ${this.grantees.join(', ')}!`)) + this.console.log(successText(`Access revoked from ${this.grantees.join(', ')}`)) } accessHistory.addEvent(this.listName, { From fc7b5a63d729c179e866b80142e369a86ebb7390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20B=C3=A9k=C3=A9si?= Date: Thu, 28 May 2026 13:29:50 +0200 Subject: [PATCH 3/3] fix: spec update --- test/command/access.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/command/access.spec.ts b/test/command/access.spec.ts index b0d3c013..85557f87 100644 --- a/test/command/access.spec.ts +++ b/test/command/access.spec.ts @@ -55,7 +55,7 @@ describeCommand( await System.sleepMillis(1000) const pssAddress = await getPssAddress('http://localhost:21633') await invokeTestCli(['access', 'grant', '--list-name', 'test-access', '--grantee', pssAddress.toHex()]) - expect(getLastMessage()).toContain(`Access granted to ${pssAddress.toHex()}!`) + expect(getLastMessage()).toContain(`Access granted to ${pssAddress.toHex()}`) }) describe('when grantee list does not exist', () => { @@ -112,7 +112,7 @@ describeCommand( ]) await System.sleepMillis(1000) await invokeTestCli(['access', 'revoke', '--list-name', 'test-access', '--grantee', pssAddress.toHex()]) - expect(getLastMessage()).toContain(`Access revoked from ${pssAddress.toHex()}!`) + expect(getLastMessage()).toContain(`Access revoked from ${pssAddress.toHex()}`) }) describe('when grantee list does not exist', () => {