diff --git a/Dockerfile b/Dockerfile index 58689e05..adadded3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,9 +9,9 @@ RUN yarn COPY src ./src COPY config ./config -COPY package*.json ./ +COPY package.json ./ COPY tsconfig* ./ -COPY .env.sample ./.env +COPY .env.sample ./.env.sample COPY accounts ./accounts RUN yarn run setup:dev diff --git a/package.json b/package.json index e5d4fcbf..da7d6b9c 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "@nestjs/platform-express": "^8.4.1", "@nestjs/swagger": "^5.2.0", "@nestjs/typeorm": "^8.0.3", - "@nevermined-io/nevermined-sdk-dtp": "0.0.6", - "@nevermined-io/nevermined-sdk-js": "0.24.0", + "@nevermined-io/nevermined-sdk-dtp": "^0.0.8", + "@nevermined-io/nevermined-sdk-js": "^0.24.1", "@sideway/address": "^4.1.3", "@sideway/formula": "^3.0.0", "@sideway/pinpoint": "^2.0.0", diff --git a/src/access/access.controller.ts b/src/access/access.controller.ts index 9fa38cec..d716fda7 100644 --- a/src/access/access.controller.ts +++ b/src/access/access.controller.ts @@ -1,10 +1,23 @@ -import { Body, Controller, Get, NotFoundException, Param, Post, Req, Response, StreamableFile, UploadedFile, UseInterceptors } from "@nestjs/common"; +import { + BadRequestException, + Body, + Controller, + Get, + NotFoundException, + Param, + Post, + Req, + Response, + StreamableFile, + UploadedFile, + UseInterceptors +} from "@nestjs/common"; import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; import { Request } from '../common/helpers/request.interface'; import { Public } from "../common/decorators/auth.decorator"; import { FileInterceptor } from "@nestjs/platform-express"; import crypto from 'crypto'; -import { aes_encryption_256 } from "../common/helpers/utils"; +import { aes_encryption_256 } from "@nevermined-io/nevermined-sdk-dtp/dist/utils"; import { ValidationParams } from "@nevermined-io/nevermined-sdk-js/dist/node/ddo/Service"; import BigNumber from "@nevermined-io/nevermined-sdk-js/dist/node/utils/BigNumber"; import { NeverminedService } from '../shared/nevermined/nvm.service'; @@ -12,6 +25,7 @@ import { Logger } from '../shared/logger/logger.service'; import { TransferDto } from "./dto/transfer"; import { UploadDto } from "./dto/upload"; import { UploadResult } from "./dto/upload-result"; +import { AgreementData } from "@nevermined-io/nevermined-sdk-js/dist/node/keeper/contracts/managers"; @ApiTags('Access') @Controller() @@ -35,6 +49,9 @@ export class AccessController { @Response({ passthrough: true }) res, @Param('index') index: number, ): Promise { + if (!req.user.did) { + throw new BadRequestException('DID not specified'); + } return await this.nvmService.downloadAsset(req.user.did, index, res, req.user.address); } @@ -70,7 +87,13 @@ export class AccessController { async doNftTransfer(@Body() transferData: TransferDto, @Req() req: Request): Promise { Logger.debug(`Transferring NFT with agreement ${transferData.agreementId}`); const nevermined = this.nvmService.getNevermined(); - const agreement = await nevermined.keeper.agreementStoreManager.getAgreement(transferData.agreementId); + let agreement: AgreementData; + try { + agreement = await nevermined.keeper.agreementStoreManager.getAgreement(transferData.agreementId); + } catch (e) { + Logger.error(`Error resolving agreement ${transferData.agreementId}`); + throw new NotFoundException(`Agreement ${transferData.agreementId} not found`); + } if (!agreement) { Logger.error(`Agreement ${transferData.agreementId} not found`); throw new NotFoundException(`Agreement ${transferData.agreementId} not found`); @@ -104,6 +127,9 @@ export class AccessController { @Response({ passthrough: true }) res, @Param('index') index: number, ): Promise { + if (!req.user.did) { + throw new BadRequestException('DID not specified'); + } return await this.nvmService.downloadAsset(req.user.did, index, res, req.user.address); } @@ -119,6 +145,9 @@ export class AccessController { description: 'Return the url of asset', }) async doUpload(@Body() uploadData: UploadDto, @Param('backend') backend: string, @UploadedFile() file: Express.Multer.File): Promise { + if (!file) { + throw new BadRequestException('No file in request'); + } let data = file.buffer; if (uploadData.encrypt) { // generate password diff --git a/src/access/access.integration.spec.ts b/src/access/access.integration.spec.ts new file mode 100644 index 00000000..2b7ac371 --- /dev/null +++ b/src/access/access.integration.spec.ts @@ -0,0 +1,100 @@ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; +import { JwtAuthGuard } from '../common/guards/auth/jwt-auth.guard'; +import { AccessController } from './access.controller'; +import { NeverminedModule } from '../shared/nevermined/nvm.module'; +import request from 'supertest'; +import { PassportModule } from '@nestjs/passport'; +import { JwtModule } from '@nestjs/jwt'; +import { AuthService } from '../auth/auth.service.mock'; +import { JwtStrategy } from '../common/strategies/jwt.strategy'; +import { ConfigModule } from '../shared/config/config.module'; + +/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */ + +describe('Info', () => { + let app: INestApplication; + let authService: AuthService; + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + NeverminedModule, + ConfigModule, + PassportModule, + JwtModule.register({ + secret: 'secret', + signOptions: { expiresIn: '60m' }, + }), + ], + providers: [ + AuthService, + JwtStrategy, + ], + controllers: [AccessController], + exports: [], + }).compile(); + app = moduleRef.createNestApplication(); + authService = moduleRef.get(AuthService); + app.useGlobalPipes(new ValidationPipe()); + app.useGlobalGuards(new JwtAuthGuard(new Reflector())); + await app.init(); + }); + it('no DID', async () => { + const response = await request(app.getHttpServer()) + .get(`/access/0x/123`) + .set('Authorization', `Bearer ${await authService.createToken({})}`); + expect((response.error as any).text).toContain('DID not specified'); + }); + it('access / unknown asset', async () => { + const response = await request(app.getHttpServer()) + .get(`/access/0x/123`) + .set( + 'Authorization', + `Bearer ${await authService.createToken({did:"did:nv:0ebed8226ada17fde24b6bf2b95d27f8f05fcce09139ff5cec31f6d81a7cd2ea"})}` + ); + expect((response.error as any).text).toContain('No such DID'); + }); + it('download / unknown asset', async () => { + const response = await request(app.getHttpServer()) + .get(`/download/123`) + .set( + 'Authorization', + `Bearer ${await authService.createToken({did:"did:nv:0ebed8226ada17fde24b6bf2b95d27f8f05fcce09139ff5cec31f6d81a7cd2ea"})}` + ); + expect((response.error as any).text).toContain('No such DID'); + }); + it('nft-access / unknown asset', async () => { + const response = await request(app.getHttpServer()) + .get(`/nft-access/0x/123`) + .set( + 'Authorization', + `Bearer ${await authService.createToken({did:"did:nv:0ebed8226ada17fde24b6bf2b95d27f8f05fcce09139ff5cec31f6d81a7cd2ea"})}` + ); + expect((response.error as any).text).toContain('No such DID'); + }); + it('nft-transfer / no post data', async () => { + const response = await request(app.getHttpServer()) + .post(`/nft-transfer`); + expect((response.error as any).text).toContain('must be a string'); + }); + it('nft-transfer / unknown agreement', async () => { + const response = await request(app.getHttpServer()) + .post(`/nft-transfer`) + .send({ + nftType: 1155, + agreementId: '0ebed8226ada17fde24b6bf2b95d27f8f05fcce09139ff5cec31f6d81a7cd2ea', + nftReceiver: '0x123', + nftHolder: '0x123', + nftAmount: "1" + }); + expect(response.statusCode).toBe(404); + expect((response.error as any).text).toContain('Agreement'); + }); + it('upload / no params', async () => { + const response = await request(app.getHttpServer()) + .post(`/upload/method`); + expect(response.statusCode).toBe(400); + expect((response.error as any).text).toContain('No file'); + }); +}); diff --git a/src/auth/auth.service.mock.ts b/src/auth/auth.service.mock.ts new file mode 100644 index 00000000..a5d1ba1b --- /dev/null +++ b/src/auth/auth.service.mock.ts @@ -0,0 +1,41 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { decodeJwt, JWTPayload } from 'jose'; +import { CLIENT_ASSERTION_TYPE, EthSignJWT } from '../common/guards/shared/jwt.utils'; +import { ethers } from 'ethers'; + +/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */ + +@Injectable() +export class AuthService { + constructor( + private jwtService: JwtService, + ) {} + + validateClaim(clientAssertionType: string, clientAssertion: string) { + if (clientAssertionType !== CLIENT_ASSERTION_TYPE) { + throw new UnauthorizedException('Invalid "assertion_type"'); + } + + const payload: JWTPayload = decodeJwt(clientAssertion); + delete payload.exp; + return { + access_token: this.jwtService.sign(payload), + }; + } + async createToken(obj: any) { + const wallet = ethers.Wallet.createRandom(); + const clientAssertion = await new EthSignJWT({ + ...obj, + iss: wallet.address, + }) + .setProtectedHeader({ alg: 'ES256K' }) + .setIssuedAt() + .setExpirationTime('60m') + .ethSign(wallet); + + return this.validateClaim(CLIENT_ASSERTION_TYPE, clientAssertion).access_token; + }; + + +} diff --git a/src/common/helpers/utils.spec.ts b/src/common/helpers/utils.spec.ts deleted file mode 100644 index cc595a1e..00000000 --- a/src/common/helpers/utils.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { readFileSync } from "fs" -import { decrypt, encrypt } from "./utils" - -describe('utils', () => { - const msg = 'tervvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvfkvfmfdlkvmdlfkvmldfkmvldkfmvldkfmvldkfmvldkfmvlkdmfvlkdmflvkmdflvkmdlfkvmldfkvmldkmest' - const config = { - provider_password: process.env.PROVIDER_PASSWORD, - provider_key: readFileSync(process.env.PROVIDER_KEYFILE).toString(), - provider_rsa_public: readFileSync(process.env.RSA_PUBKEY_FILE).toString(), - provider_rsa_private: readFileSync(process.env.RSA_PRIVKEY_FILE).toString(), - } - it('should encrypt and decrypt using RSA', async () => { - const { result } = await encrypt(config, msg, 'PSK-RSA') - const msg_ = await decrypt(config, result, 'PSK-RSA') - expect(msg_).toBe(msg) - }) - it('should encrypt and decrypt using ECDSA', async () => { - const { result } = await encrypt(config, msg, 'PSK-ECDSA') - const msg_ = await decrypt(config, result, 'PSK-ECDSA') - expect(msg_).toBe(msg) - }) -}) diff --git a/src/common/helpers/utils.ts b/src/common/helpers/utils.ts deleted file mode 100644 index b5a5d2fd..00000000 --- a/src/common/helpers/utils.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { ForbiddenException } from '@nestjs/common'; -import { ethers } from 'ethers'; -import { AuthRoles } from '../type'; -import crypto from 'crypto'; -import { encrypt as ec_encrypt, decrypt as ec_decrypt} from 'eciesjs'; -import NodeRSA from 'node-rsa'; -import { CryptoConfig } from '../../shared/config/config.service'; - -export const checkOwnership = (userId: string, entityUserId: string, roles: AuthRoles[]) => { - if (!roles.some((r) => r === AuthRoles.Admin) && userId !== entityUserId) { - throw new ForbiddenException('This source only can be created or updated by the owner or admin'); - } -}; - -const get_aes_private_key = (passphrase: string) => { - const salt = Buffer.from('this is a salt') - const kdf = crypto.pbkdf2Sync(passphrase, salt, 48, 10000, 'sha256').toString('binary') - const key = kdf.substring(0, 16) - return Buffer.from(key, 'binary') -} - -const BLOCK_SIZE = 16 -const AES_BLOCK_SIZE = 16 - -function mod(a: number, n: number) { - return a - (n * Math.floor(a/n)); -} - -const pad = (s:string) => { - const md = BLOCK_SIZE - mod(s.length, BLOCK_SIZE) - return s + String.fromCharCode(md).repeat(md) -} - -const unpad = (s:string) => { - const num = s.charCodeAt(s.length - 1) - return s.substring(0, s.length - num) -} - -const aes_encryption = (data, passphrase) => { - const private_key = get_aes_private_key(passphrase) - const iv = crypto.randomBytes(AES_BLOCK_SIZE) - const cipher = crypto.createCipheriv('aes-128-cbc', private_key, iv) - let res = cipher.update(pad(data), 'binary', 'binary') - res += cipher.final('binary') - return Buffer.from(iv.toString('binary') + res, 'binary').toString('base64') -} - -const aes_decryption = (data64, passphrase) => { - const private_key = get_aes_private_key(passphrase) - const data = Buffer.from(data64, 'base64') - const iv = data.slice(0, AES_BLOCK_SIZE) - const cipher = crypto.createDecipheriv('aes-128-cbc', private_key, iv) - let res = cipher.update(data.slice(AES_BLOCK_SIZE).toString('binary'), 'binary', 'binary') - res += cipher.final('binary') - return unpad(res) -} - -export const aes_encryption_256 = (data, passphrase) => { - const salt = crypto.randomBytes(BLOCK_SIZE - 'Salted__'.length) - const kdf = crypto.pbkdf2Sync(passphrase, salt, 10000, 48, 'sha256').toString('binary') - const private_key = Buffer.from(kdf.substring(0, 32), 'binary') - const iv = Buffer.from(kdf.substring(32, 48), 'binary') - const cipher = crypto.createCipheriv('aes-256-cbc', private_key, iv) - let res = cipher.update(pad(data), 'binary', 'binary') - res += cipher.final('binary') - return Buffer.from('Salted__' + salt.toString('binary') + res, 'binary').toString('binary') -} - -export const aes_decryption_256 = (encrypted, password) => { - const salt = Buffer.from(encrypted.substring(8, 16), 'binary') - const keydata = crypto - .pbkdf2Sync(password, salt, 10000, 48, 'sha256') - .toString('binary') - const key = Buffer.from(keydata.substring(0, 32), 'binary') - const iv = Buffer.from(keydata.substring(32, 48), 'binary') - const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) - - let decrypted = decipher.update(encrypted.substring(16), 'binary', 'binary') - decrypted += decipher.final() - return unpad(decrypted) -} - -export const encrypt = async (config: CryptoConfig, cipherText: string, method: string): Promise<{publicKey: string, result: string}> => { - if (method === 'PSK-ECDSA') { - const wallet = await ethers.Wallet.fromEncryptedJson(config.provider_key, config.provider_password) - let ecdh = crypto.createECDH('secp256k1'); - ecdh.setPrivateKey(Buffer.from(wallet.privateKey.substring(2), 'hex')) - const result = ec_encrypt(ecdh.getPublicKey(), Buffer.from(cipherText)).toString('binary') - const res = { - publicKey: wallet.publicKey, - result - } - return res - } else if (method === 'PSK-RSA') { - const key = new NodeRSA(config.provider_rsa_public) - const aes_key = crypto.randomBytes(16) - const encrypted_data = aes_encryption(cipherText, aes_key) - const encrypted_aes_key = key.encrypt(aes_key) - return { - publicKey: key.exportKey('public'), - result: Buffer.from(encrypted_data).toString('hex') + '|' + Buffer.from(encrypted_aes_key).toString('hex'), - } - } -}; - -export const decrypt = async (config: CryptoConfig, cipherText: string, method: string) => { - if (method === 'PSK-ECDSA') { - const wallet = await ethers.Wallet.fromEncryptedJson(config.provider_key, config.provider_password) - let ecdh = crypto.createECDH('secp256k1'); - ecdh.setPrivateKey(Buffer.from(wallet.privateKey.substring(2), 'hex')) - return ec_decrypt(ecdh.getPrivateKey(), Buffer.from(cipherText, 'binary')).toString() - } else if (method === 'PSK-RSA') { - const key = new NodeRSA(config.provider_rsa_private) - const [data, encrypted_aes_key] = cipherText.split('|') - const aes_key = key.decrypt(Buffer.from(encrypted_aes_key, 'hex')) - return aes_decryption(Buffer.from(data, 'hex').toString(), aes_key) - } -}; - diff --git a/src/encrypt/encrypt.controller.ts b/src/encrypt/encrypt.controller.ts index 1d106887..f4e64f38 100644 --- a/src/encrypt/encrypt.controller.ts +++ b/src/encrypt/encrypt.controller.ts @@ -1,11 +1,11 @@ import { BadRequestException, Body, Controller, Post } from "@nestjs/common"; import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; -import { encrypt } from "../common/helpers/utils"; import { Public } from '../common/decorators/auth.decorator'; import { Logger } from "@nevermined-io/nevermined-sdk-js"; import { EncryptDto } from "./dto/encrypt"; import { EncryptResult } from "./dto/result"; import { ConfigService } from "../shared/config/config.service"; +import { encrypt } from "@nevermined-io/nevermined-sdk-dtp/dist/utils"; @ApiTags('Encrypt') @Controller() diff --git a/src/encrypt/encrypt.integration.spec.ts b/src/encrypt/encrypt.integration.spec.ts new file mode 100644 index 00000000..53098851 --- /dev/null +++ b/src/encrypt/encrypt.integration.spec.ts @@ -0,0 +1,45 @@ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; +import { JwtAuthGuard } from '../common/guards/auth/jwt-auth.guard'; +import { EncryptController } from './encrypt.controller'; +import request from 'supertest'; +import { ConfigModule } from '../shared/config/config.module'; +import { decrypt } from '@nevermined-io/nevermined-sdk-dtp/dist/utils'; +import { ConfigService } from '../shared/config/config.service'; + +/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */ + +describe('Info', () => { + let app: INestApplication; + const config = new ConfigService(); + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ConfigModule], + providers: [], + controllers: [EncryptController], + exports: [], + }).compile(); + app = moduleRef.createNestApplication(); + app.useGlobalPipes(new ValidationPipe()); + app.useGlobalGuards(new JwtAuthGuard(new Reflector())); + await app.init(); + }); + it('no params', async () => { + const response = await request(app.getHttpServer()).post(`/`); + expect(response.statusCode).toBe(400); + expect((response.error as any).text).toContain('must be a string'); + }); + it('bad method', async () => { + const response = await request(app.getHttpServer()).post(`/`).send({message: 'msg', method: 'foo'}); + expect(response.statusCode).toBe(400); + expect((response.error as any).text).toContain('Only PSK-ECDSA or PSK-RSA encryption allowed'); + }); + it('correct call', async () => { + const response = await request(app.getHttpServer()).post(`/`).send({message: 'msg', method: 'PSK-RSA'}); + expect(response.statusCode).toBe(201); + expect(response.body.method).toBe('PSK-RSA'); + const result = response.body.hash; + expect(await decrypt(config.cryptoConfig(), result, 'PSK-RSA')).toBe('msg'); + }); +}); diff --git a/src/shared/nevermined/nvm.service.ts b/src/shared/nevermined/nvm.service.ts index 116bb4fa..6bc569b4 100644 --- a/src/shared/nevermined/nvm.service.ts +++ b/src/shared/nevermined/nvm.service.ts @@ -1,16 +1,16 @@ import { Injectable } from '@nestjs/common'; import { Dtp } from '@nevermined-io/nevermined-sdk-dtp/dist/Dtp'; import { generateIntantiableConfigFromConfig } from '@nevermined-io/nevermined-sdk-js/dist/node/Instantiable.abstract'; -import { Nevermined } from '@nevermined-io/nevermined-sdk-js'; +import { DDO, Nevermined } from '@nevermined-io/nevermined-sdk-js'; import { utils } from '@nevermined-io/nevermined-sdk-js'; import { BadRequestException, InternalServerErrorException, NotFoundException, StreamableFile } from '@nestjs/common'; -import { decrypt } from '../../common/helpers/utils'; import download from 'download'; import AWS from 'aws-sdk'; import { FormData } from 'formdata-node'; import { Blob } from 'buffer'; import { Logger } from '../logger/logger.service'; import { ConfigService } from '../config/config.service'; +import { decrypt } from '@nevermined-io/nevermined-sdk-dtp'; import { ethers } from 'ethers'; import { didZeroX } from '@nevermined-io/nevermined-sdk-js/dist/node/utils'; @@ -40,7 +40,7 @@ export class NeverminedService { ...generateIntantiableConfigFromConfig(config), nevermined: this.nevermined, }; - this.dtp = await Dtp.getInstance(instanceConfig); + this.dtp = await Dtp.getInstance(instanceConfig, this.config.cryptoConfig()); } getNevermined() { return this.nevermined @@ -61,7 +61,13 @@ export class NeverminedService { async getAssetUrl(did: string, index: number): Promise<{url: string, content_type: string, dtp: boolean}> { // get url for DID - const asset = await this.nevermined.assets.resolve(did) + let asset: DDO + try { + asset = await this.nevermined.assets.resolve(did) + } catch (e) { + Logger.error(`Cannot resolve DID ${did}`) + throw new BadRequestException(`No such DID ${did}`) + } const service = asset.findServiceByType('metadata') const file_attributes = service.attributes.main.files[index] const content_type = file_attributes.contentType @@ -80,13 +86,13 @@ export class NeverminedService { Logger.debug(`Downloading asset from ${did} index ${index}`) try { let {url, content_type, dtp} = await this.getAssetUrl(did, index) - if (dtp) { - return url - } if (!url) { Logger.error(`URL for did ${did} not found`) throw new NotFoundException(`URL for did ${did} not found`) } + if (dtp) { + return url + } Logger.debug(`Serving URL ${url}`) // get url for DID if (url.startsWith('cid://')) { diff --git a/yarn.lock b/yarn.lock index bfcf1568..3c46812a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1096,12 +1096,12 @@ dependencies: uuid "8.3.2" -"@nevermined-io/nevermined-sdk-dtp@0.0.6": - version "0.0.6" - resolved "https://registry.npmjs.org/@nevermined-io/nevermined-sdk-dtp/-/nevermined-sdk-dtp-0.0.6.tgz" - integrity sha512-UNF6aJO6eGeymAhLq9W+8Lj0Ul1W9JygRTwxJP66bVkSP1ZoLxjn/eas4ssroPNa3ZWxbjeMjkMuBLhsMdYfTQ== +"@nevermined-io/nevermined-sdk-dtp@^0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@nevermined-io/nevermined-sdk-dtp/-/nevermined-sdk-dtp-0.0.8.tgz#dc074bee42104946a19402fe74edc9f16fedc2bf" + integrity sha512-nYxXHboYbTVpHWt08Gl8rxL5Ii5fHSJaVOruW/ipKm0eh4khCM4sGGqlErd6C1nptRtQALB6M31yTXqRL2vVCw== dependencies: - "@nevermined-io/nevermined-sdk-js" "^0.24.0" + "@nevermined-io/nevermined-sdk-js" "^0.24.1" circomlibjs "^0.1.1" eciesjs "^0.3.15" ffjavascript "^0.2.55" @@ -1109,10 +1109,10 @@ snarkjs "^0.4.26" web3-utils "^1.7.4" -"@nevermined-io/nevermined-sdk-js@0.24.0", "@nevermined-io/nevermined-sdk-js@^0.24.0": - version "0.24.0" - resolved "https://registry.npmjs.org/@nevermined-io/nevermined-sdk-js/-/nevermined-sdk-js-0.24.0.tgz" - integrity sha512-WSLUKsiHozNOic43mupvzmd/rccMW3Dyu/pHCaN3MWHYrTcImLe3BkJAjfseg4QpZO42H42xzrJUMgKOpXl7+w== +"@nevermined-io/nevermined-sdk-js@^0.24.1": + version "0.24.1" + resolved "https://registry.yarnpkg.com/@nevermined-io/nevermined-sdk-js/-/nevermined-sdk-js-0.24.1.tgz#78c93d1862970c46853cf50dc62ad0bb41d14b27" + integrity sha512-QK3CJogcrySheFe5grptBt7MuI5H0tBYmub3zwHI1qWgawOqSqboX1qM5l5dAI5xzLEbMCsHSuDQ1V7PByeDPQ== dependencies: "@nevermined-io/secret-store-client" "^0.0.16" "@nevermined-io/subgraphs" "0.4.1"