-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Add `shareSpace()` Method to Allow Sharing Spaces with Other Accounts ## Summary This PR introduces a new `shareSpace()` method that allows users to delegate access to an existing space with another Storacha account via email. This feature enhances collaboration by enabling multiple accounts to share access to a space, making data sharing more flexible. By default, the following capabilities/permissions are set: - space/* - for managing space metadata - store/* - for managing stores - upload/*- for registering uploads - access/* - for re-delegating access to other devices - filecoin/* - for submitting to the filecoin pipeline - usage/* - for querying usage ## Changes ### New Feature: **Space Sharing** - **Added `shareSpace()` Method**: - The `shareSpace()` method allows users to share an existing space with another Storacha account by delegating access to the specified email address. - The method takes in the following options: - `space`: The space to be shared, identified by its DID. - `delegateEmail`: The email address of the account to share the space with. - The sharing process involves: 1. **Creating a delegation for the delegate account**: This ensures that the delegate has access to the space. 2. **Delegating access**: Space access is delegated to the provided email account, allowing the delegate to manage and access the space. - If the sharing process fails, the method throws an error detailing the issue. ## How to Test 1. Run the following commands: ```bash npm run build && npm run test ``` 2. Ensure all existing tests pass. 3. Verify that the new test cases for the `shareSpace()` method function correctly. ## Related Issues - [Issue #130](storacha/project-tracking#130): Enable space sharing between accounts. ## Additional Notes This implementation opens the door for future enhancements, such as specifying different permission levels when sharing spaces.
- Loading branch information
Showing
2 changed files
with
221 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,20 @@ | ||
import assert from 'assert' | ||
import { parseLink } from '@ucanto/server' | ||
import { AgentData } from '@web3-storage/access/agent' | ||
import { | ||
Agent, | ||
AgentData, | ||
claimAccess, | ||
requestAccess, | ||
} from '@web3-storage/access/agent' | ||
import { randomBytes, randomCAR } from './helpers/random.js' | ||
import { toCAR } from './helpers/car.js' | ||
import { File } from './helpers/shims.js' | ||
import { Client } from '../src/client.js' | ||
import * as Test from './test.js' | ||
import { receiptsEndpoint } from './helpers/utils.js' | ||
import { Absentee } from '@ucanto/principal' | ||
import { DIDMailto } from '../src/capability/access.js' | ||
import { confirmConfirmationUrl } from '../../upload-api/test/helpers/utils.js' | ||
|
||
/** @type {Test.Suite} */ | ||
export const testClient = { | ||
|
@@ -393,6 +401,132 @@ export const testClient = { | |
client.capability.access.delegate = originalDelegate | ||
}, | ||
}), | ||
shareSpace: Test.withContext({ | ||
'should share the space with another account': async ( | ||
assert, | ||
{ client: aliceClient, mail, grantAccess, connection } | ||
) => { | ||
// Step 1: Create a client for Alice and login | ||
const aliceEmail = '[email protected]' | ||
const aliceLogin = aliceClient.login(aliceEmail) | ||
const message = await mail.take() | ||
assert.deepEqual(message.to, aliceEmail) | ||
await grantAccess(message) | ||
const aliceAccount = await aliceLogin | ||
|
||
// Step 2: Alice creates a space | ||
const space = await aliceClient.createSpace('share-space-test', { | ||
account: aliceAccount, | ||
}) | ||
assert.ok(space) | ||
|
||
// Step 3: Alice shares the space with Bob | ||
const bobEmail = '[email protected]' | ||
await aliceClient.shareSpace(bobEmail, space.did()) | ||
|
||
// Step 4: Bob access his device and his device gets authorized | ||
const bobAccount = Absentee.from({ id: DIDMailto.fromEmail(bobEmail) }) | ||
const bobAgentData = await AgentData.create() | ||
const bobClient = await Agent.create(bobAgentData, { | ||
connection, | ||
}) | ||
|
||
// Authorization | ||
await requestAccess(bobClient, bobAccount, [{ can: '*' }]) | ||
await confirmConfirmationUrl(bobClient.connection, await mail.take()) | ||
|
||
// Step 5: Claim Access to the shared space | ||
await claimAccess(bobClient, bobClient.issuer.did(), { | ||
addProofs: true, | ||
}) | ||
|
||
// Step 6: Bob verifies access to the space | ||
const spaceInfo = await bobClient.getSpaceInfo(space.did()) | ||
assert.ok(spaceInfo) | ||
assert.equal(spaceInfo.did, space.did()) | ||
|
||
// Step 7: The shared space should be part of Bob's spaces | ||
const spaces = bobClient.spaces | ||
assert.equal(spaces.size, 1) | ||
assert.equal(spaces.get(space.did())?.name, space.name) | ||
|
||
// Step 8: Make sure Alice and Bob's clients/devices are different | ||
assert.notEqual(aliceClient.did(), bobClient.did()) | ||
}, | ||
|
||
'should fail to share the space if the delegate call returns an error': | ||
async (assert, { client, mail, grantAccess }) => { | ||
// Step 1: Create a client for Alice and login | ||
const aliceEmail = '[email protected]' | ||
const aliceLogin = client.login(aliceEmail) | ||
const message = await mail.take() | ||
assert.deepEqual(message.to, aliceEmail) | ||
await grantAccess(message) | ||
const aliceAccount = await aliceLogin | ||
|
||
// Step 2: Alice creates a space | ||
const space = await client.createSpace( | ||
'share-space-delegate-fail-test', | ||
{ | ||
account: aliceAccount, | ||
} | ||
) | ||
assert.ok(space) | ||
|
||
// Step 3: Mock the delegate call to return an error | ||
const originalDelegate = client.capability.access.delegate | ||
// @ts-ignore | ||
client.capability.access.delegate = async () => { | ||
return { error: { message: 'Delegate failed' } } | ||
} | ||
|
||
// Step 4: Attempt to share the space with Bob and expect failure | ||
const bobEmail = '[email protected]' | ||
await assert.rejects(client.shareSpace(bobEmail, space.did()), { | ||
message: `failed to share space with ${bobEmail}: Delegate failed`, | ||
}) | ||
|
||
// Restore the original delegate method | ||
client.capability.access.delegate = originalDelegate | ||
}, | ||
|
||
'should reset current space when sharing': async ( | ||
assert, | ||
{ client, mail, grantAccess } | ||
) => { | ||
// Step 1: Create a client for Alice and login | ||
const aliceEmail = '[email protected]' | ||
const aliceLogin = client.login(aliceEmail) | ||
const message = await mail.take() | ||
assert.deepEqual(message.to, aliceEmail) | ||
await grantAccess(message) | ||
const aliceAccount = await aliceLogin | ||
|
||
// Step 2: Alice creates a space | ||
const spaceA = await client.createSpace('test-space-a', { | ||
account: aliceAccount, | ||
}) | ||
assert.ok(spaceA) | ||
|
||
// Step 3: Alice creates another space to share with a friend | ||
const spaceB = await client.createSpace('test-space-b', { | ||
account: aliceAccount, | ||
}) | ||
assert.ok(spaceB) | ||
|
||
// Step 4: Alice set the current space to space A and shares the space B with Bob | ||
await client.setCurrentSpace(spaceA.did()) | ||
await client.shareSpace('[email protected]', spaceB.did()) | ||
|
||
// Step 5: Check that current space from Alice is still space A | ||
const currentSpace = client.currentSpace() | ||
assert.equal( | ||
currentSpace?.did(), | ||
spaceA.did(), | ||
'current space is not space A' | ||
) | ||
}, | ||
}), | ||
proofs: { | ||
'should get proofs': async (assert) => { | ||
const alice = new Client(await AgentData.create()) | ||
|