From 03ef6b7e6f5389f1517d754de48019933fdde3ca Mon Sep 17 00:00:00 2001 From: upalinski Date: Thu, 7 Nov 2024 17:16:59 +0300 Subject: [PATCH 1/2] add access sharing (private bucket) example --- .../7-private-bucket-access-sharing/index.ts | 57 +++++++++++++++++++ examples/node/README.md | 3 +- examples/node/package.json | 3 +- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 examples/node/7-private-bucket-access-sharing/index.ts diff --git a/examples/node/7-private-bucket-access-sharing/index.ts b/examples/node/7-private-bucket-access-sharing/index.ts new file mode 100644 index 00000000..37906317 --- /dev/null +++ b/examples/node/7-private-bucket-access-sharing/index.ts @@ -0,0 +1,57 @@ +import {DdcClient, File, TESTNET, AuthToken, UriSigner, AuthTokenOperation} from '@cere-ddc-sdk/ddc-client'; +import path from 'path'; +import {fileURLToPath} from 'url'; +import * as fs from 'fs'; + +// The Bob's wallet should have enough CERE to pay for the transaction fees +const bob = 'hybrid label reunion only dawn maze asset draft cousin height flock nation'; +const bobSigner = new UriSigner(bob) +/** + * The Alice wallet doesn't require any tokens because it accesses Bob's bucket so Bob pays for it. + * Actually the Alice doesn't need to have a wallet on blockchain, it can be a simple key pair used to sign access tokens. + */ +const alice = 'system visit notice before step medal top theme oblige river inner bracket'; +const aliceSigner = new UriSigner(alice) + +// The DDC cluster where the bucket will be created (mainnet cluster id is '0x0059f5ada35eee46802d80750d5ca4a490640511') +const clusterId = '0x825c4b2352850de9986d9d28568db6f0c023a1e3'; + +// Create a DDC client instance +const client = await DdcClient.create(bob, TESTNET); + +// Create a private bucket +const bucketId = await client.createBucket(clusterId, {isPublic: false}); +console.log('Private bucket created', bucketId); + +// Detect the current directory +const dir = path.dirname(fileURLToPath(import.meta.url)); + +const pathToFileToUpload = path.resolve(dir, '../assets/nature.jpg'); + +// Upload file +const fileSize = fs.statSync(pathToFileToUpload).size; +const fileToUpload = new File(fs.createReadStream(pathToFileToUpload), {size: fileSize}); +const uploadedFileUri = await client.store(bucketId, fileToUpload); +console.log('File stored into bucket', bucketId, 'with CID', uploadedFileUri.cid); +console.log('The file can\'t be accessed by this URL because bucket is private and access token required', `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}`); + +// Create an access token that is signed by a Bob and can be shared so that anyone having this token can access a bucket (or specific file) +const bobToken = new AuthToken({ + bucketId, + pieceCid: uploadedFileUri.cid, + operations: [AuthTokenOperation.GET], +}) +await bobToken.sign(bobSigner) +console.log('The file can be accessed by this URL (Bob\'s token passed in query parameters)', `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}?token=${bobToken.toString()}`); + +/** + * Create an access token that is signed by a Bob and is granted to Alice so that only Alice can use this token to access a content. + * The Bobs token can't be used directly because he has specified an Alice in a token 'subject' so this token should be wrapped into another token that is signed by Alice + */ +const bobTokenGrantedToAlice = await client.grantAccess(aliceSigner.address, {bucketId, operations: [AuthTokenOperation.GET], pieceCid: uploadedFileUri.cid }) +// Alice wraps Bob's token (aka token chain) and sign wrapped token by Alice key pair. The token expiration time is 5 minutes. +const aliceToken = new AuthToken({operations: [AuthTokenOperation.GET], pieceCid: uploadedFileUri.cid, prev: bobTokenGrantedToAlice, expiresIn: 5 * 60 * 1000 }) +await aliceToken.sign(aliceSigner); +console.log('The file can be accessed by this URL (Alice\'s token passed in query parameters)', `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}?token=${aliceToken.toString()}`); + +await client.disconnect(); diff --git a/examples/node/README.md b/examples/node/README.md index fd196eb2..f44b0ff4 100644 --- a/examples/node/README.md +++ b/examples/node/README.md @@ -8,6 +8,7 @@ This examples directory contains several common DDC SDK use-cases for NodeJs app - [How to stream events](./4-store-read-events/index.ts) - [How to use exported account (Cere Wallet)](./5-use-exported-account/index.ts) - [How to index files so that they are visible in Developer Console](./6-developer-console-compatibility/index.ts) +- [How to share access to a content in private bucket](./7-private-bucket-access-sharing/index.ts) ## Quick start @@ -40,7 +41,7 @@ Run the following commands from the project root 5. Run an example ```bash - npm run example:1 # can be 1-6 + npm run example:1 # can be 1-7 ``` diff --git a/examples/node/package.json b/examples/node/package.json index 9fbfcccd..db22f8ea 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -9,7 +9,8 @@ "example:3": "ts-node --esm ./3-upload-website/index.ts", "example:4": "ts-node --esm ./4-store-read-events/index.ts", "example:5": "ts-node --esm ./5-use-exported-account/index.ts", - "example:6": "ts-node --esm ./6-developer-console-compatibility/index.ts" + "example:6": "ts-node --esm ./6-developer-console-compatibility/index.ts", + "example:7": "ts-node --esm ./7-private-bucket-access-sharing/index.ts" }, "dependencies": { "@cere-ddc-sdk/ddc-client": "2.13.0", From f5f0e00918b88673761a1ccc8760e4c6e13b5880 Mon Sep 17 00:00:00 2001 From: upalinski Date: Thu, 7 Nov 2024 17:17:47 +0300 Subject: [PATCH 2/2] lint fix --- .../7-private-bucket-access-sharing/index.ts | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/examples/node/7-private-bucket-access-sharing/index.ts b/examples/node/7-private-bucket-access-sharing/index.ts index 37906317..badbe70b 100644 --- a/examples/node/7-private-bucket-access-sharing/index.ts +++ b/examples/node/7-private-bucket-access-sharing/index.ts @@ -1,17 +1,17 @@ -import {DdcClient, File, TESTNET, AuthToken, UriSigner, AuthTokenOperation} from '@cere-ddc-sdk/ddc-client'; +import { DdcClient, File, TESTNET, AuthToken, UriSigner, AuthTokenOperation } from '@cere-ddc-sdk/ddc-client'; import path from 'path'; -import {fileURLToPath} from 'url'; +import { fileURLToPath } from 'url'; import * as fs from 'fs'; // The Bob's wallet should have enough CERE to pay for the transaction fees const bob = 'hybrid label reunion only dawn maze asset draft cousin height flock nation'; -const bobSigner = new UriSigner(bob) +const bobSigner = new UriSigner(bob); /** * The Alice wallet doesn't require any tokens because it accesses Bob's bucket so Bob pays for it. * Actually the Alice doesn't need to have a wallet on blockchain, it can be a simple key pair used to sign access tokens. - */ + */ const alice = 'system visit notice before step medal top theme oblige river inner bracket'; -const aliceSigner = new UriSigner(alice) +const aliceSigner = new UriSigner(alice); // The DDC cluster where the bucket will be created (mainnet cluster id is '0x0059f5ada35eee46802d80750d5ca4a490640511') const clusterId = '0x825c4b2352850de9986d9d28568db6f0c023a1e3'; @@ -20,7 +20,7 @@ const clusterId = '0x825c4b2352850de9986d9d28568db6f0c023a1e3'; const client = await DdcClient.create(bob, TESTNET); // Create a private bucket -const bucketId = await client.createBucket(clusterId, {isPublic: false}); +const bucketId = await client.createBucket(clusterId, { isPublic: false }); console.log('Private bucket created', bucketId); // Detect the current directory @@ -30,28 +30,46 @@ const pathToFileToUpload = path.resolve(dir, '../assets/nature.jpg'); // Upload file const fileSize = fs.statSync(pathToFileToUpload).size; -const fileToUpload = new File(fs.createReadStream(pathToFileToUpload), {size: fileSize}); +const fileToUpload = new File(fs.createReadStream(pathToFileToUpload), { size: fileSize }); const uploadedFileUri = await client.store(bucketId, fileToUpload); console.log('File stored into bucket', bucketId, 'with CID', uploadedFileUri.cid); -console.log('The file can\'t be accessed by this URL because bucket is private and access token required', `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}`); +console.log( + "The file can't be accessed by this URL because bucket is private and access token required", + `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}`, +); // Create an access token that is signed by a Bob and can be shared so that anyone having this token can access a bucket (or specific file) const bobToken = new AuthToken({ bucketId, pieceCid: uploadedFileUri.cid, operations: [AuthTokenOperation.GET], -}) -await bobToken.sign(bobSigner) -console.log('The file can be accessed by this URL (Bob\'s token passed in query parameters)', `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}?token=${bobToken.toString()}`); +}); +await bobToken.sign(bobSigner); +console.log( + "The file can be accessed by this URL (Bob's token passed in query parameters)", + `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}?token=${bobToken.toString()}`, +); /** * Create an access token that is signed by a Bob and is granted to Alice so that only Alice can use this token to access a content. * The Bobs token can't be used directly because he has specified an Alice in a token 'subject' so this token should be wrapped into another token that is signed by Alice */ -const bobTokenGrantedToAlice = await client.grantAccess(aliceSigner.address, {bucketId, operations: [AuthTokenOperation.GET], pieceCid: uploadedFileUri.cid }) +const bobTokenGrantedToAlice = await client.grantAccess(aliceSigner.address, { + bucketId, + operations: [AuthTokenOperation.GET], + pieceCid: uploadedFileUri.cid, +}); // Alice wraps Bob's token (aka token chain) and sign wrapped token by Alice key pair. The token expiration time is 5 minutes. -const aliceToken = new AuthToken({operations: [AuthTokenOperation.GET], pieceCid: uploadedFileUri.cid, prev: bobTokenGrantedToAlice, expiresIn: 5 * 60 * 1000 }) +const aliceToken = new AuthToken({ + operations: [AuthTokenOperation.GET], + pieceCid: uploadedFileUri.cid, + prev: bobTokenGrantedToAlice, + expiresIn: 5 * 60 * 1000, +}); await aliceToken.sign(aliceSigner); -console.log('The file can be accessed by this URL (Alice\'s token passed in query parameters)', `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}?token=${aliceToken.toString()}`); +console.log( + "The file can be accessed by this URL (Alice's token passed in query parameters)", + `https://cdn.testnet.cere.network/${bucketId}/${uploadedFileUri.cid}?token=${aliceToken.toString()}`, +); await client.disconnect();