Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: management of NVM API Keys #120

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nevermined-io/cli",
"version": "2.2.1",
"version": "2.2.2",
"main": "index.js",
"repository": "[email protected]:nevermined-io/cli.git",
"author": "Nevermined",
Expand All @@ -25,7 +25,7 @@
"ncli": "./dist/src/index.js"
},
"dependencies": {
"@nevermined-io/sdk": "3.0.29",
"@nevermined-io/sdk": "3.0.33",
"log4js": "^6.9.1",
"chalk": "^4.1.2",
"cross-fetch": "~3.1.5",
Expand Down
35 changes: 35 additions & 0 deletions resources/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -2264,6 +2264,41 @@
"hidden": true,
"description": "The NFT type"
}]
}, {
"name": "create-api-key [name]",
"description": "It creates a new NVM API Key",
"details": "Nevermined API Keys allow to interact with the protocol in a programatic way. This command allows the generation of NVM API Keys. NOTE: This method requires the user to setup the ZERO_PROJECT_ID environment variable.",
"examples": ["ncli app create-api-key 'My Test API Key'"],
"commandHandler": "createApiKey",
"requiresAccount": true,
"positionalArguments": [{
"name": "name",
"type": "string",
"description": "The name of the NVM API Key to create"
}],
"optionalArguments": []
}, {
"name": "list-api-keys",
"description": "It lists all the NVM API Keys associated to the user",
"details": "This command lists allt he NVM API Keys created by the user.",
"examples": ["ncli app list-api-keys"],
"commandHandler": "listApiKeys",
"requiresAccount": true,
"positionalArguments": [],
"optionalArguments": []
}, {
"name": "revoke-api-key [hash]",
"description": "It revokes a existing NVM API Key",
"details": "Nevermined API Keys allow to interact with the protocol in a programatic way. This command allows to revoke an existing NVM API Keys.",
"examples": ["ncli app revoke-api-key 'eyJhbGciOiJFUzI1NksifQ.eyJpc'"],
"commandHandler": "revokeApiKey",
"requiresAccount": true,
"positionalArguments": [{
"name": "hash",
"type": "string",
"description": "The hash of the NVM API Key to revoke"
}],
"optionalArguments": []
}]
}
]
Expand Down
14 changes: 12 additions & 2 deletions resources/networks.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"graphHttpUri": "http://localhost:9000/subgraphs/name/nevermined-io/development",
"neverminedNodeUri": "http://node.nevermined.localnet",
"neverminedNodeAddress": "0x068ed00cf0441e4829d9784fcbe7b9e26d4bd8d0",
"neverminedBackendUri": "http://one-backend.nevermined.localnet",
"verbose": true
},
"nativeToken": "ETH",
Expand All @@ -34,6 +35,7 @@
"graphHttpUri": "",
"neverminedNodeUri": "https://node.staging.nevermined.app",
"neverminedNodeAddress": "0x5838B5512cF9f12FE9f2beccB20eb47211F9B0bc",
"neverminedBackendUri": "https://one-backend.staging.nevermined.app",
"verbose": true
},
"nativeToken": "ETH",
Expand All @@ -59,6 +61,7 @@
"graphHttpUri": "https://api.thegraph.com/subgraphs/name/nevermined-io/public",
"neverminedNodeUri": "https://node.matic.nevermined.app",
"neverminedNodeAddress": "0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1",
"neverminedBackendUri": "https://one-backend.matic.nevermined.app",
"verbose": true
},
"nativeToken": "MATIC",
Expand All @@ -84,6 +87,7 @@
"graphHttpUri": "",
"neverminedNodeUri": "https://node.testing.nevermined.app",
"neverminedNodeAddress": "0x5838B5512cF9f12FE9f2beccB20eb47211F9B0bc",
"neverminedBackendUri": "https://one-backend.testing.nevermined.app",
"verbose": true
},
"nativeToken": "ETH",
Expand All @@ -109,6 +113,7 @@
"graphHttpUri": "https://api.thegraph.com/subgraphs/name/nevermined-io/public",
"neverminedNodeUri": "https://node.arbitrum.nevermined.app",
"neverminedNodeAddress": "0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1",
"neverminedBackendUri": "https://one-backend.arbitrum.nevermined.app",
"verbose": true
},
"nativeToken": "ETH",
Expand All @@ -134,6 +139,7 @@
"graphHttpUri": "https://api.thegraph.com/subgraphs/name/nevermined-io/public",
"neverminedNodeUri": "https://node.base.nevermined.app",
"neverminedNodeAddress": "0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1",
"neverminedBackendUri": "https://one-backend.base.nevermined.app",
"verbose": true
},
"nativeToken": "ETH",
Expand All @@ -159,6 +165,7 @@
"graphHttpUri": "https://api.thegraph.com/subgraphs/name/nevermined-io/public",
"neverminedNodeUri": "https://node.gnosis.nevermined.app",
"neverminedNodeAddress": "0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1",
"neverminedBackendUri": "https://one-backend.gnosis.nevermined.app",
"verbose": true
},
"nativeToken": "xDAI",
Expand All @@ -184,6 +191,7 @@
"graphHttpUri": "https://api.thegraph.com/subgraphs/name/nevermined-io/public",
"neverminedNodeUri": "https://node.optimism.nevermined.app",
"neverminedNodeAddress": "0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1",
"neverminedBackendUri": "https://one-backend.optimism.nevermined.app",
"verbose": true
},
"nativeToken": "ETH",
Expand All @@ -209,6 +217,7 @@
"graphHttpUri": "",
"neverminedNodeUri": "http://node.celo.nevermined.app",
"neverminedNodeAddress": "0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1",
"neverminedBackendUri": "https://one-backend.celo.nevermined.app",
"verbose": true
},
"nativeToken": "CELO",
Expand All @@ -232,13 +241,14 @@
"web3ProviderUri": "https://evm.peaq.network",
"marketplaceUri": "https://marketplace-api.peaq.nevermined.app",
"graphHttpUri": "",
"neverminedNodeUri": "http://node.peaq.nevermined.app",
"neverminedNodeUri": "https://node.peaq.nevermined.app",
"neverminedNodeAddress": "0x824dbcE5E9C96C5b8ce2A35a25a5ab87eD1D00b1",
"neverminedBackendUri": "https://one-backend.peaq.nevermined.app",
"verbose": true
},
"nativeToken": "PEAQ",
"networkName": "peaq-mainnet",
"contractsVersion": "3.5.7",
"contractsVersion": "3.5.8",
"tagName": "public",
"etherscanUrl": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fmpfn1.peaq.network#/explorer",
"erc20TokenAddress": "0x0000000000000000000000000000000000000000",
Expand Down
133 changes: 133 additions & 0 deletions src/commands/app/createApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { NvmAccount, NvmApp, getFullZeroDevPermissions, createSessionKey, NvmApiKey } from '@nevermined-io/sdk'
import {
StatusCodes,
getNeverminedContractAbi,
} from '../../utils'
import chalk from 'chalk'
import { ExecutionOutput } from '../../models/ExecutionOutput'
import { Logger } from 'log4js'
import { ConfigEntry } from '../../models/ConfigDefinition'
import { createPublicClient, http, toHex } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'


export const createApiKey = async (
nvmApp: NvmApp,
publisherAccount: NvmAccount,
argv: any,
config: ConfigEntry,
logger: Logger
): Promise<ExecutionOutput> => {

const ZERO_PROJECT_ID = process.env.ZERO_PROJECT_ID || undefined
if (!ZERO_PROJECT_ID) {
return {
status: StatusCodes.ERROR,
errorMessage: `Missing environment variable ZERO_PROJECT_ID`
}
}

const NVM_BACKEND_URL = config.nvm.neverminedBackendUri || undefined
if (!NVM_BACKEND_URL) {
return {
status: StatusCodes.ERROR,
errorMessage: `Missing Nevermined Backend URL`
}
}

const BUNDLER_RPC = `https://rpc.zerodev.app/api/v2/bundler/${ZERO_PROJECT_ID}`

const publicClient = createPublicClient({
chain: nvmApp.sdk.client.chain,
transport: http(BUNDLER_RPC),
})
logger.debug(`Public client created with chain: ${nvmApp.sdk.client.chain?.name}`)
logger.debug(BUNDLER_RPC)

logger.info(chalk.dim(`Creating a new NVM API Key ...`))


const networkName = config.networkName || 'geth-localnet'
const didRegistryAbi = getNeverminedContractAbi('DIDRegistry', networkName)
const nftSalesTemplateAbi = getNeverminedContractAbi('NFTSalesTemplate', networkName)
const nftPlansAbi = getNeverminedContractAbi('NFT1155SubscriptionUpgradeable', networkName)

logger.debug(`Generating permissions for the API Key ...`)
const permissions = getFullZeroDevPermissions(
didRegistryAbi.address, // DIDRegistry address
nftSalesTemplateAbi.address, // Sales Template address
config.erc20TokenAddress as `0x${string}`, // ERC20 address
nftPlansAbi.address, // NFT1155 address
nftPlansAbi.address,
)

logger.debug(`Permissions generated for network: ${networkName}`)
logger.debug(`DIDRegistry: ${didRegistryAbi.address}`)
logger.debug(`NFTSalesTemplate: ${nftSalesTemplateAbi.address}`)
logger.debug(`NFTPlansAbi: ${nftPlansAbi.address}`)
logger.debug(`ERC20 Address: ${config.erc20TokenAddress}`)
// logger.debug(JSON.stringify(permissions))


logger.debug(`Creating a new ZeroDev kernel client ...`)
logger.debug(`Chain ID: ${nvmApp.sdk.client.chain?.id}`)

const privateKey = toHex(publisherAccount.getAccountSigner().getHdKey().privateKey)

const account = privateKeyToAccount(privateKey)
logger.debug(`Private key Account: ${account.address}`)

logger.debug(`Creating a new ZeroDev session key via account: ${account.address} ...`)
const sessionKey = await createSessionKey(account, publicClient, permissions)

logger.debug(`Generating encrypted NVM API Key ...`)
const encryptedNvmApiKey = await NvmApiKey.generate(
nvmApp.sdk.utils.signature,
publisherAccount,
sessionKey,
config.nvm.marketplaceAuthToken!,
config.nvm.neverminedNodeAddress!,
await nvmApp.sdk.services.node.getEcdsaPublicKey()
)
// logger.debug(encryptedNvmApiKey)

logger.debug(`Registering NVM API Key ...`)
const options = {
method: 'POST',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
name: argv.name,
nvmKey: encryptedNvmApiKey
})
}

// logger.debug(`POST ${NVM_BACKEND_URL} with options`)
// logger.debug(JSON.parse(options.body))
const _result = await fetch(`${NVM_BACKEND_URL}/api/v1/api-keys`, options)
if (_result.status !== 200 && _result.status !== 201) {
logger.error(`Error registering NVM API Key: ${_result.statusText}`)
logger.error(await _result.text())
return {
status: StatusCodes.ERROR,
errorMessage: `Error registering NVM API Key: ${_result.statusText}`
}
}

logger.info(chalk.green(`NVM API Key created successfully`))
const keyResult = await _result.json()
logger.info(chalk.dim(`API Key Wallet: ${chalk.yellow(keyResult.userWallet)}`))
logger.info(chalk.dim(`API Key UserId: ${chalk.yellow(keyResult.userId)}`))
logger.info(chalk.dim(`API Key Hash: ${chalk.yellow(keyResult.hash)}`))

return {
status: StatusCodes.OK,
results: JSON.stringify({
sessionKey,
userWallet: keyResult.userWallet,
userId: keyResult.userId,
hash: keyResult.hash
})
}
}
77 changes: 77 additions & 0 deletions src/commands/app/listApiKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { NvmAccount, NvmApp } from '@nevermined-io/sdk'
import {
StatusCodes,
} from '../../utils'
import chalk from 'chalk'
import { ExecutionOutput } from '../../models/ExecutionOutput'
import { Logger } from 'log4js'
import { ConfigEntry } from '../../models/ConfigDefinition'


export const listApiKeys = async (
_nvmApp: NvmApp,
_publisherAccount: NvmAccount,
_argv: any,
config: ConfigEntry,
logger: Logger
): Promise<ExecutionOutput> => {

const NVM_BACKEND_URL = config.nvm.neverminedBackendUri || undefined
if (!NVM_BACKEND_URL) {
return {
status: StatusCodes.ERROR,
errorMessage: `Missing Nevermined Backend URL`
}
}

logger.info(chalk.dim(`Listing NVM API Keys ...`))
// logger.info(config.nvm.marketplaceAuthToken)
// logger.info(config.nvm.marketplaceUri)
// const clientAssertion = await nvmApp.sdk.utils.jwt.generateClientAssertion(publisherAccount)
// const marketplaceAuthToken = await nvmApp.sdk.services.marketplace.login(clientAssertion)

const options = {
method: 'GET',
headers: {
'Accept': '*/*',
'Authorization': `Bearer ${config.nvm.marketplaceAuthToken}`
}
}

logger.debug(`${NVM_BACKEND_URL}/api/v1/api-keys`)
logger.debug(JSON.stringify(options))
const _result = await fetch(`${NVM_BACKEND_URL}/api/v1/api-keys`, options)

if (_result.status !== 200) {
logger.error(`Error listing NVM API Keys: ${_result.statusText}`)
logger.error(await _result.text())
return {
status: StatusCodes.ERROR,
errorMessage: `Error listing NVM API Keys: ${_result.statusText}`
}
}

const keyResult = await _result.json()
logger.info(chalk.green(`NVM API Keys found ${keyResult.totalResults}`))


keyResult.apiKeys.forEach((key: any) => {
logger.info('---------------------------------')
logger.info(chalk.dim(`\tName: ${chalk.yellow(key.name)}`))
if (key.isActive)
logger.info(chalk.dim(`\tIs Active?: ${chalk.green('yes')}`))
else
logger.info(chalk.dim(`\tIs Active?: ${chalk.red('no')}`))

logger.info(chalk.dim(`\tHash: ${chalk.yellow(key.hash)}`))
logger.info(chalk.dim(`\tWallet: ${chalk.yellow(key.userWallet)}`))
logger.info(chalk.dim(`\tCreated/Expires: ${chalk.yellow(key.createdAt)} / ${chalk.yellow(key.expiresAt)}`))
})

return {
status: StatusCodes.OK,
results: JSON.stringify({
keyResult
})
}
}
Loading
Loading