diff --git a/.nx/version-plans/version-plan-1765995084329.md b/.nx/version-plans/version-plan-1765995084329.md new file mode 100644 index 000000000..090317fbd --- /dev/null +++ b/.nx/version-plans/version-plan-1765995084329.md @@ -0,0 +1,5 @@ +--- +'@storacha/encrypt-upload-client': major +--- + +Add Lit v8 support and delegation revocation checks to Lit Actions diff --git a/packages/encrypt-upload-client/README.md b/packages/encrypt-upload-client/README.md index 2d72d0556..7fd9aad5e 100644 --- a/packages/encrypt-upload-client/README.md +++ b/packages/encrypt-upload-client/README.md @@ -1,13 +1,27 @@

🐔 + 🔑
Encrypt Upload Client

-

Use Lit Protocol and Storacha Network to enable private decentralized hot storage.

## About -This library leverages `@storacha/cli` and `@lit-protocol/lit-node-client` to provide a simple interface for encrypting files with Lit Protocol and uploading them to the Storacha Network. It also enables anyone with a valid `space/content/decrypt` UCAN delegation to decrypt the file. With Lit Protocol, encryption keys are managed in a decentralized way, so you don't have to handle them yourself. +This library provides client-side encryption and key management solution integrations for files stored on Storacha. + +**The library is strategy-agnostic**, meaning it supports different key management solutions without changing your integration code: + +- **Lit Protocol** - Decentralized encryption with UCAN-friendly validation inside programmable Lit Actions +- **Google KMS** - Centralized key management for enterprise compliance and auditability + +Different apps have different needs: some prioritize decentralization and user sovereignty, while others need to satisfy enterprise compliance or data residency rules. The library unifies the flow so you can switch from a centralized to a decentralized key management solution (or vice versa) without rewriting your entire logic. + +**Key features:** + +- **Client-side streaming encryption** - memory-efficient for large files +- **Decentralized key management** via Lit Protocol's threshold cryptography network +- **UCAN-based access control** - grant and revoke decrypt permissions without re-encrypting +- **Pluggable crypto adapters** - use Lit Protocol, Google KMS, or implement your own +- **Composable architecture** - integrate seamlessly with existing Storacha workflows ## Install -You can add the `@storacha/encrypt-upload-client` package to your JavaScript or TypeScript project with `npm`: +You can add the `@storacha/encrypt-upload-client` package to your project with `npm`: ```sh npm install @storacha/encrypt-upload-client @@ -15,7 +29,7 @@ npm install @storacha/encrypt-upload-client ## Usage -To use this library, you'll need to install `@storacha/cli` and `@lit-protocol/lit-node-client`, as they are required for initialization—though the Lit client is optional. You must also provide a crypto adapter that implements the `CryptoAdapter` interface. A ready-to-use Node.js & Browser crypto adapters are available. +To use this library, you'll need to install `@storacha/client`. If using the Lit Protocol adapter, you'll also need to install `@lit-protocol/lit-client` and `@lit-protocol/auth`, as they are required for initialization. You must also provide a crypto adapter that implements the `CryptoAdapter` interface. ### CryptoAdapter Interface @@ -32,60 +46,65 @@ interface EncryptOutput { } ``` -### Node Usage - -```js -const encryptedClient = await EncryptClient.create({ - storachaClient, - cryptoAdapter: new NodeCryptoAdapter(), -}) -``` - -### Browser Usage - -For browser apps, use the `BrowserCryptoAdapter`: +### Usage ```js -import { BrowserCryptoAdapter } from '@storacha/encrypt-upload-client/crypto-adapters/browser-crypto-adapter.js' - -const encryptedClient = await EncryptClient.create({ +// Using the Lit adapter +const encryptedClient = await create({ storachaClient: client, - cryptoAdapter: new BrowserCryptoAdapter(), + cryptoAdapter: createGenericLitAdapter(litClient, authManager), }) ``` ### Encryption -The encryption process automatically generates a custom Access Control Condition (ACC) based on the current space setup in your Storacha client. It then creates a symmetric key to encrypt the file and uses Lit Protocol to encrypt that key, so you don't have to manage it yourself. Once encrypted, both the file and the generated encrypted metadata are uploaded to Storacha. +The encryption process with Lit Adapter automatically generates a custom Access Control Condition (ACC) based on the current space in your Storacha client. It then creates a symmetric key to encrypt the file and uses the Lit Protocol to encrypt that key, so you don't have to manage it yourself. Once encrypted, both the file and the generated encrypted metadata are uploaded to Storacha. #### Encryption Example ```js const fileContent = await fs.promises.readFile('./README.md') const blob = new Blob([fileContent]) -const cid = await encryptedClient.uploadEncryptedFile(blob) + +const encryptionConfig = { + issuer: principal, + spaceDID: space.did(), +} + +const cidLink = await encryptedClient.encryptAndUploadFile( + blob, + encryptionConfig +) ``` You can find a full example in `examples/encrypt-test.js`. ### Decryption -To decrypt a file, you'll need the CID returned from `uploadEncryptedFile`, a UCAN delegation CAR with the `space/content/decrypt` capability (proving that the file owner has granted you decryption access), and an Ethereum wallet with available Capacity Credits on the Lit Network to cover the decryption cost. - -For details on minting Capacity Credits, check out the [official documentation](https://developer.litprotocol.com/concepts/capacity-credits-concept). +To decrypt a file, you'll need the CID returned from `encryptAndUploadFile`, a UCAN delegation with the `space/content/decrypt` capability (proving that the file owner has granted you decryption access), and any other parameters specific to the selected adapter. #### Decryption Example ```js -const decryptedContent = await encryptedClient.retrieveAndDecryptFile( +// Lit adapter using an EOA wallet +const decryptionConfig = { wallet, + decryptDelegation, + spaceDID, +} + +const decryptedContent = await encryptedClient.retrieveAndDecryptFile( cid, - delegationCarBuffer + decryptionConfig ) ``` You can find a full example in `examples/decrypt-test.js`. +## Using PKP (Programmable Key Pairs) + +If you want to use the Lit Protocol adapter without requiring a wallet (EOA account) for decryption, you can use a PKP (Programmable Key Pair). Check out the [demo code using PKP](https://github.com/storacha/lit-pkp-demo). + ## Contributing Feel free to join in. All welcome. Please [open an issue](https://github.com/storacha/upload-service/issues)! diff --git a/packages/encrypt-upload-client/examples/decrypt-test.js b/packages/encrypt-upload-client/examples/decrypt-test.js index 559e4eb08..94d7e40e6 100644 --- a/packages/encrypt-upload-client/examples/decrypt-test.js +++ b/packages/encrypt-upload-client/examples/decrypt-test.js @@ -11,7 +11,7 @@ import { privateKeyToAccount } from 'viem/accounts' import { create } from '../src/index.js' import { serviceConf, receiptsEndpoint } from '../src/config/service.js' -import { createGenericLitAdapter } from '../src/crypto/factories.node.js' +import { createGenericLitAdapter } from '../src/crypto/factories.js' import { extract } from '@ucanto/core/delegation' import { createAuthManager, storagePlugins } from '@lit-protocol/auth' diff --git a/packages/encrypt-upload-client/examples/encrypt-test.js b/packages/encrypt-upload-client/examples/encrypt-test.js index e9c45a818..f1a696f20 100644 --- a/packages/encrypt-upload-client/examples/encrypt-test.js +++ b/packages/encrypt-upload-client/examples/encrypt-test.js @@ -12,7 +12,7 @@ import { createLitClient } from '@lit-protocol/lit-client' import * as EncryptClient from '../src/index.js' import { serviceConf, receiptsEndpoint } from '../src/config/service.js' -import { createGenericLitAdapter } from '../src/crypto/factories.node.js' +import { createGenericLitAdapter } from '../src/crypto/factories.js' // import { CID } from 'multiformats' import { createAuthManager, storagePlugins } from '@lit-protocol/auth' diff --git a/packages/encrypt-upload-client/package.json b/packages/encrypt-upload-client/package.json index dba349663..23d1f3f5e 100644 --- a/packages/encrypt-upload-client/package.json +++ b/packages/encrypt-upload-client/package.json @@ -37,21 +37,9 @@ "import": "./dist/index.js", "require": "./dist/index.js" }, - "./factories.node": { - "import": "./dist/crypto/factories.node.js", - "require": "./dist/crypto/factories.node.js" - }, - "./factories.browser": { - "import": "./dist/crypto/factories.browser.js", - "require": "./dist/crypto/factories.browser.js" - }, - "./node": { - "import": "./dist/crypto/symmetric/node-aes-cbc-crypto.js", - "require": "./dist/crypto/symmetric/node-aes-cbc-crypto.js" - }, - "./browser": { - "import": "./dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.js", - "require": "./dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.js" + "./factories": { + "import": "./dist/crypto/factories.js", + "require": "./dist/crypto/factories.js" }, "./types": "./dist/types.js" }, diff --git a/packages/encrypt-upload-client/src/config/constants.js b/packages/encrypt-upload-client/src/config/constants.js index a1426e34a..d4cbad319 100644 --- a/packages/encrypt-upload-client/src/config/constants.js +++ b/packages/encrypt-upload-client/src/config/constants.js @@ -1,4 +1,5 @@ export const STORACHA_LIT_ACTION_CID = 'QmbJJX7nBZafj4kUGPc9LPSdBvHACxhPWdZSVHcAHP9rik' +// TODO: update lit action to use Lit runOnce export const GATEWAY_URL = new URL('https://storacha.link') diff --git a/packages/encrypt-upload-client/src/crypto/factories.browser.js b/packages/encrypt-upload-client/src/crypto/factories.browser.js deleted file mode 100644 index 223635ef2..000000000 --- a/packages/encrypt-upload-client/src/crypto/factories.browser.js +++ /dev/null @@ -1,26 +0,0 @@ -import { GenericAesCtrStreamingCrypto } from './symmetric/generic-aes-ctr-streaming-crypto.js' -import { KMSCryptoAdapter } from './adapters/kms-crypto-adapter.js' - -/** - * Create a KMS crypto adapter for browser environments - * Uses the generic AES-CTR streaming crypto implementation - * Works in browser and Node.js environments - * - * @param {URL|string} keyManagerServiceURL - * @param {string} keyManagerServiceDID - * @param {object} [options] - Optional configuration - * @param {boolean} [options.allowInsecureHttp] - Allow HTTP for testing (NOT for production) - */ -export function createGenericKMSAdapter( - keyManagerServiceURL, - keyManagerServiceDID, - options = {} -) { - const symmetricCrypto = new GenericAesCtrStreamingCrypto() - return new KMSCryptoAdapter( - symmetricCrypto, - keyManagerServiceURL, - /** @type {`did:${string}:${string}`} */ (keyManagerServiceDID), - options - ) -} diff --git a/packages/encrypt-upload-client/src/crypto/factories.node.js b/packages/encrypt-upload-client/src/crypto/factories.js similarity index 71% rename from packages/encrypt-upload-client/src/crypto/factories.node.js rename to packages/encrypt-upload-client/src/crypto/factories.js index 2729711cf..837d32e8f 100644 --- a/packages/encrypt-upload-client/src/crypto/factories.node.js +++ b/packages/encrypt-upload-client/src/crypto/factories.js @@ -4,26 +4,32 @@ import { LitCryptoAdapter } from './adapters/lit-crypto-adapter.js' import * as Type from '../types.js' /** - * Create a KMS crypto adapter for Node.js using the generic AES-CTR streaming crypto. - * Works in Node.js & browser environments. + * Create a KMS crypto adapter + * Uses the generic AES-CTR streaming crypto implementation + * Works in browser and Node.js environments * * @param {URL|string} keyManagerServiceURL * @param {string} keyManagerServiceDID + * @param {object} [options] - Optional configuration + * @param {boolean} [options.allowInsecureHttp] - Allow HTTP for testing (NOT for production) */ export function createGenericKMSAdapter( keyManagerServiceURL, - keyManagerServiceDID + keyManagerServiceDID, + options = {} ) { const symmetricCrypto = new GenericAesCtrStreamingCrypto() return new KMSCryptoAdapter( symmetricCrypto, keyManagerServiceURL, - /** @type {`did:${string}:${string}`} */ (keyManagerServiceDID) + /** @type {`did:${string}:${string}`} */ (keyManagerServiceDID), + options ) } /** - * Create a Lit crypto adapter for Node.js using the generic AES-CTR streaming crypto. + * Create a Lit crypto adapter + * Uses the generic AES-CTR streaming crypto. * Works in Node.js & browser environments. * * @param {import('@lit-protocol/lit-client').LitClientType} litClient diff --git a/packages/encrypt-upload-client/test/factories.spec.js b/packages/encrypt-upload-client/test/factories.spec.js index e4e3c2687..f97e32767 100644 --- a/packages/encrypt-upload-client/test/factories.spec.js +++ b/packages/encrypt-upload-client/test/factories.spec.js @@ -5,7 +5,7 @@ import assert from 'node:assert' import { createGenericKMSAdapter, createGenericLitAdapter, -} from '../src/crypto/factories.node.js' +} from '../src/crypto/factories.js' import { GenericAesCtrStreamingCrypto } from '../src/crypto/symmetric/generic-aes-ctr-streaming-crypto.js' import { LitCryptoAdapter } from '../src/crypto/adapters/lit-crypto-adapter.js' import { KMSCryptoAdapter } from '../src/crypto/adapters/kms-crypto-adapter.js' diff --git a/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js b/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js index e6d8cbffc..ac8b84bbd 100644 --- a/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js +++ b/packages/encrypt-upload-client/test/lit-crypto-adapter.spec.js @@ -144,7 +144,7 @@ await describe('LitCryptoAdapter', async () => { await test('should verify factory function behavior', async () => { const { createGenericLitAdapter } = await import( - '../src/crypto/factories.node.js' + '../src/crypto/factories.js' ) const genericAdapter = createGenericLitAdapter( diff --git a/packages/ui/packages/react/src/hooks.ts b/packages/ui/packages/react/src/hooks.ts index 962747033..a962da32d 100644 --- a/packages/ui/packages/react/src/hooks.ts +++ b/packages/ui/packages/react/src/hooks.ts @@ -55,26 +55,33 @@ export function useKMSConfig(initialConfig?: KMSConfig): { } { // Merge initial config with environment variable fallbacks const defaultConfig = { - keyManagerServiceURL: process.env.NEXT_PUBLIC_UCAN_KMS_URL ?? 'https://kms.storacha.network', - keyManagerServiceDID: process.env.NEXT_PUBLIC_UCAN_KMS_DID ?? 'did:web:kms.storacha.network', + keyManagerServiceURL: + process.env.NEXT_PUBLIC_UCAN_KMS_URL ?? 'https://kms.storacha.network', + keyManagerServiceDID: + process.env.NEXT_PUBLIC_UCAN_KMS_DID ?? 'did:web:kms.storacha.network', location: process.env.NEXT_PUBLIC_UCAN_KMS_LOCATION, keyring: process.env.NEXT_PUBLIC_UCAN_KMS_KEYRING, - allowInsecureHttp: process.env.NEXT_PUBLIC_UCAN_KMS_ALLOW_INSECURE_HTTP === 'true', + allowInsecureHttp: + process.env.NEXT_PUBLIC_UCAN_KMS_ALLOW_INSECURE_HTTP === 'true', // Override with any provided initial config values - ...initialConfig + ...initialConfig, } - - const [kmsConfig, setKmsConfig] = useState(defaultConfig) - + + const [kmsConfig, setKmsConfig] = useState( + defaultConfig + ) + // Helper function to create KMS adapter with fallbacks const createKMSAdapter = useCallback(async (): Promise => { if (!kmsConfig?.keyManagerServiceURL || !kmsConfig?.keyManagerServiceDID) { return null } - + try { - const { createGenericKMSAdapter } = await import('@storacha/encrypt-upload-client/factories.browser') - + const { createGenericKMSAdapter } = await import( + '@storacha/encrypt-upload-client/factories' + ) + return createGenericKMSAdapter( kmsConfig.keyManagerServiceURL, kmsConfig.keyManagerServiceDID, @@ -87,12 +94,14 @@ export function useKMSConfig(initialConfig?: KMSConfig): { return null } }, [kmsConfig]) - + return { kmsConfig, setKmsConfig, createKMSAdapter, - isConfigured: Boolean(kmsConfig?.keyManagerServiceURL && kmsConfig?.keyManagerServiceDID) + isConfigured: Boolean( + kmsConfig?.keyManagerServiceURL && kmsConfig?.keyManagerServiceDID + ), } } @@ -114,14 +123,14 @@ export function useDatamodel({ connection, receiptsEndpoint, }) - + startTransition(() => { setClient(client) setEvents(events) setAccounts(Object.values(client.accounts())) setSpaces(client.spaces()) }) - + if (!skipInitialClaim) { await client.capability.access.claim() } diff --git a/packages/ui/packages/react/test/Uploader.encrypted.spec.tsx b/packages/ui/packages/react/test/Uploader.encrypted.spec.tsx index a5536bbdf..bc1c4ee26 100644 --- a/packages/ui/packages/react/test/Uploader.encrypted.spec.tsx +++ b/packages/ui/packages/react/test/Uploader.encrypted.spec.tsx @@ -12,20 +12,20 @@ import { } from '../src/providers/Provider.js' import { Uploader, UploaderContext } from '../src/Uploader.js' -const SpaceDID = "did:key:z6Mkit3tepJFA1Em9S1BTLVNdJ6rmXTrBaTTbt55dxyQvKZF" -const AccountDID = "did:mailto:storacha.network:test" +const SpaceDID = 'did:key:z6Mkit3tepJFA1Em9S1BTLVNdJ6rmXTrBaTTbt55dxyQvKZF' +const AccountDID = 'did:mailto:storacha.network:test' // Mock the encrypt-upload-client module vi.mock('@storacha/encrypt-upload-client', () => ({ create: vi.fn(), })) -vi.mock('@storacha/encrypt-upload-client/factories.browser', () => ({ +vi.mock('@storacha/encrypt-upload-client/factories', () => ({ createGenericKMSAdapter: vi.fn(), })) vi.mock('@ucanto/core', async (importOriginal) => { - const actual = await importOriginal() as any + const actual = (await importOriginal()) as any return { ...actual, delegate: vi.fn(), @@ -46,31 +46,37 @@ test('encrypted upload with private space', async () => { const cid = Link.parse( 'bafybeibrqc2se2p3k4kfdwg7deigdggamlumemkiggrnqw3edrjosqhvnm' ) - + const mockEncryptedClient = { encryptAndUploadFile: vi.fn().mockResolvedValue(cid), } as any - + const mockCryptoAdapter = { encrypt: vi.fn(), decrypt: vi.fn(), } as any // Mock the encrypted client creation - const { create: createEncryptedClient } = await import('@storacha/encrypt-upload-client') + const { create: createEncryptedClient } = await import( + '@storacha/encrypt-upload-client' + ) vi.mocked(createEncryptedClient).mockResolvedValue(mockEncryptedClient) // Set up delegate mock to return proper delegation const { delegate } = await import('@ucanto/core') vi.mocked(delegate).mockResolvedValue({ cid: { toString: () => 'bafyreiabc123' }, - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, audience: { did: () => 'did:web:kms.storacha.network' }, capabilities: [{ can: 'plan/get', with: AccountDID }], expiration: Math.floor(Date.now() / 1000) + 3600, } as any) - const { createGenericKMSAdapter } = await import('@storacha/encrypt-upload-client/factories.browser') + const { createGenericKMSAdapter } = await import( + '@storacha/encrypt-upload-client/factories' + ) vi.mocked(createGenericKMSAdapter).mockReturnValue(mockCryptoAdapter) const space = { @@ -92,7 +98,9 @@ test('encrypted upload with private space', async () => { const client = { currentSpace: vi.fn().mockReturnValue(space), agent: { - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, proofs: vi.fn().mockReturnValue([]), }, proofs: vi.fn().mockReturnValue([]), @@ -131,7 +139,9 @@ test('encrypted upload with private space', async () => { ) - const file = new File(['encrypted content'], 'secret.txt', { type: 'text/plain' }) + const file = new File(['encrypted content'], 'secret.txt', { + type: 'text/plain', + }) const fileInput = screen.getByTestId('file-upload') await user.upload(fileInput, file) @@ -140,7 +150,7 @@ test('encrypted upload with private space', async () => { await user.click(submitButton) // Wait for async operations to complete - await new Promise(resolve => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 100)) // Verify encrypted client was created expect(createEncryptedClient).toHaveBeenCalledWith({ @@ -169,10 +179,10 @@ test('encrypted upload with private space', async () => { issuer: expect.objectContaining({ did: expect.any(Function) }), audience: expect.objectContaining({ did: expect.any(Function) }), capabilities: expect.arrayContaining([ - expect.objectContaining({ can: 'plan/get', with: AccountDID }) + expect.objectContaining({ can: 'plan/get', with: AccountDID }), ]), expiration: expect.any(Number), - }) + }), ]), fileMetadata: { name: 'secret.txt', @@ -198,30 +208,36 @@ test('encrypted upload with custom KMS config', async () => { const cid = Link.parse( 'bafybeibrqc2se2p3k4kfdwg7deigdggamlumemkiggrnqw3edrjosqhvnm' ) - + const mockEncryptedClient = { encryptAndUploadFile: vi.fn().mockResolvedValue(cid), } as any - + const mockCryptoAdapter = { encrypt: vi.fn(), decrypt: vi.fn(), } as any - const { create: createEncryptedClient } = await import('@storacha/encrypt-upload-client') + const { create: createEncryptedClient } = await import( + '@storacha/encrypt-upload-client' + ) vi.mocked(createEncryptedClient).mockResolvedValue(mockEncryptedClient) // Set up delegate mock to return proper delegation const { delegate } = await import('@ucanto/core') vi.mocked(delegate).mockResolvedValue({ cid: { toString: () => 'bafyreiabc123' }, - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, audience: { did: () => 'did:web:kms.storacha.network' }, capabilities: [{ can: 'plan/get', with: AccountDID }], expiration: Math.floor(Date.now() / 1000) + 3600, } as any) - const { createGenericKMSAdapter } = await import('@storacha/encrypt-upload-client/factories.browser') + const { createGenericKMSAdapter } = await import( + '@storacha/encrypt-upload-client/factories' + ) vi.mocked(createGenericKMSAdapter).mockReturnValue(mockCryptoAdapter) const space = { @@ -235,7 +251,7 @@ test('encrypted upload with custom KMS config', async () => { }, }), } - + const account = { did: vi.fn().mockReturnValue(AccountDID), } as any @@ -243,7 +259,9 @@ test('encrypted upload with custom KMS config', async () => { const client = { currentSpace: vi.fn().mockReturnValue(space), agent: { - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, proofs: vi.fn().mockReturnValue([]), }, proofs: vi.fn().mockReturnValue([]), @@ -283,7 +301,9 @@ test('encrypted upload with custom KMS config', async () => { ) - const file = new File(['encrypted content'], 'secret.txt', { type: 'text/plain' }) + const file = new File(['encrypted content'], 'secret.txt', { + type: 'text/plain', + }) const fileInput = screen.getByTestId('file-upload') await user.upload(fileInput, file) @@ -292,7 +312,7 @@ test('encrypted upload with custom KMS config', async () => { await user.click(submitButton) // Wait for async operations to complete - await new Promise(resolve => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, 100)) // Verify custom KMS config was used expect(createGenericKMSAdapter).toHaveBeenCalledWith( @@ -317,10 +337,10 @@ test('encrypted upload with custom KMS config', async () => { issuer: expect.objectContaining({ did: expect.any(Function) }), audience: expect.objectContaining({ did: expect.any(Function) }), capabilities: expect.arrayContaining([ - expect.objectContaining({ can: 'plan/get', with: AccountDID }) + expect.objectContaining({ can: 'plan/get', with: AccountDID }), ]), expiration: expect.any(Number), - }) + }), ]), fileMetadata: { name: 'secret.txt', @@ -338,13 +358,17 @@ test('encrypted upload with custom KMS config', async () => { test('encrypted upload fails with multiple files', async () => { // We don't need to mock the encryption functions because the error should happen before they're called const space = { - did: vi.fn().mockReturnValue('did:key:z6Mkit3tepJFA1Em9S1BTLVNdJ6rmXTrBaTTbt55dxyQvKZF'), + did: vi + .fn() + .mockReturnValue( + 'did:key:z6Mkit3tepJFA1Em9S1BTLVNdJ6rmXTrBaTTbt55dxyQvKZF' + ), meta: vi.fn().mockReturnValue({ access: { type: 'private', encryption: { provider: 'google-kms', - algorithm: 'aes-256' + algorithm: 'aes-256', }, }, }), @@ -353,7 +377,9 @@ test('encrypted upload fails with multiple files', async () => { const client = { currentSpace: vi.fn().mockReturnValue(space), agent: { - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, }, } @@ -398,14 +424,18 @@ test('encrypted upload fails with multiple files', async () => { await user.click(submitButton) // Wait for the error to propagate - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)) // Verify the upload failed with the correct error expect(uploaderState.status).toBe('failed') - expect(uploaderState.error?.message).toBe('Encrypted uploads currently only support single files') + expect(uploaderState.error?.message).toBe( + 'Encrypted uploads currently only support single files' + ) // Verify NO encryption functions were called since the error happens before them - const { create: createEncryptedClient } = await import('@storacha/encrypt-upload-client') + const { create: createEncryptedClient } = await import( + '@storacha/encrypt-upload-client' + ) expect(createEncryptedClient).not.toHaveBeenCalled() }) @@ -427,7 +457,9 @@ test('encrypted upload falls back to regular upload for public space', async () currentSpace: vi.fn().mockReturnValue(space), uploadFile: vi.fn().mockResolvedValue(cid), // Mock regular upload agent: { - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, }, } @@ -443,7 +475,10 @@ test('encrypted upload falls back to regular upload for public space', async () const handleComplete = vi.fn() render( - + @@ -497,7 +532,9 @@ test('encrypted upload fails with unsupported provider', async () => { const client = { currentSpace: vi.fn().mockReturnValue(space), agent: { - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, proofs: vi.fn().mockReturnValue([]), }, proofs: vi.fn().mockReturnValue([]), @@ -542,14 +579,16 @@ test('encrypted upload fails with unsupported provider', async () => { await user.click(submitButton) // Wait for the error to propagate - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)) // Verify the upload failed with the correct error expect(uploaderState.status).toBe('failed') expect(uploaderState.error?.message).toBe('Encryption provider not supported') - + // Verify NO encryption functions were called since the provider is unsupported - const { create: createEncryptedClient } = await import('@storacha/encrypt-upload-client') + const { create: createEncryptedClient } = await import( + '@storacha/encrypt-upload-client' + ) expect(createEncryptedClient).not.toHaveBeenCalled() }) @@ -557,7 +596,9 @@ test('upload fails without space', async () => { const client = { currentSpace: vi.fn().mockReturnValue(null), // No space selected agent: { - issuer: { did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK' }, + issuer: { + did: () => 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK', + }, }, } @@ -599,12 +640,12 @@ test('upload fails without space', async () => { await user.click(submitButton) // Wait for the error to propagate - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)) // Verify the upload failed with the correct error expect(uploaderState.status).toBe('failed') expect(uploaderState.error?.message).toBe('No space selected for upload') - + // Verify that currentSpace was called but returned null expect(client.currentSpace).toHaveBeenCalled() -}) \ No newline at end of file +})