From 25e8a99a319fe2129fb5d3eb2f0c8658323ed0e3 Mon Sep 17 00:00:00 2001 From: rrr523 Date: Fri, 15 Mar 2024 23:44:32 +0800 Subject: [PATCH] feat: Remove Approval when creating bucket --- .changeset/heavy-suits-punch.md | 5 + .../src/components/bucket/create/index.tsx | 39 ++--- packages/js-sdk/src/api/bucket.ts | 140 +++++------------- packages/js-sdk/src/api/virtualGroup.ts | 26 ++++ .../greenfield/storage/MsgCreateBucket.ts | 8 +- packages/js-sdk/src/types/common.ts | 16 +- .../js-sdk/src/types/sp/BucketApproval.ts | 27 ---- packages/js-sdk/src/types/sp/index.ts | 1 - packages/js-sdk/src/utils/asserts/s3.ts | 40 +++++ 9 files changed, 129 insertions(+), 173 deletions(-) create mode 100644 .changeset/heavy-suits-punch.md delete mode 100644 packages/js-sdk/src/types/sp/BucketApproval.ts diff --git a/.changeset/heavy-suits-punch.md b/.changeset/heavy-suits-punch.md new file mode 100644 index 00000000..09bf83e2 --- /dev/null +++ b/.changeset/heavy-suits-punch.md @@ -0,0 +1,5 @@ +--- +'@bnb-chain/greenfield-js-sdk': minor +--- + +feat: Remove approval when create bucket diff --git a/examples/nextjs/src/components/bucket/create/index.tsx b/examples/nextjs/src/components/bucket/create/index.tsx index d85ecb20..53991b89 100644 --- a/examples/nextjs/src/components/bucket/create/index.tsx +++ b/examples/nextjs/src/components/bucket/create/index.tsx @@ -1,6 +1,5 @@ import { client, selectSp } from '@/client'; -import { getOffchainAuthKeys } from '@/utils/offchainAuth'; -import { GRNToString, newBucketGRN } from '@bnb-chain/greenfield-js-sdk'; +import { GRNToString, Long, newBucketGRN, VisibilityType } from '@bnb-chain/greenfield-js-sdk'; import { useState } from 'react'; import { useAccount } from 'wagmi'; @@ -31,34 +30,14 @@ export const CreateBucket = () => { const spInfo = await selectSp(); console.log('spInfo', spInfo); - const provider = await connector?.getProvider(); - const offChainData = await getOffchainAuthKeys(address, provider); - // console.log('offChainData', offChainData); - if (!offChainData) { - alert('No offchain, please create offchain pairs first'); - return; - } - - const createBucketTx = await client.bucket.createBucket( - { - bucketName: createBucketInfo.bucketName, - creator: address, - visibility: 'VISIBILITY_TYPE_PUBLIC_READ', - chargedReadQuota: '0', - spInfo: { - primarySpAddress: spInfo.primarySpAddress, - }, - paymentAddress: address, - }, - { - // type: 'ECDSA', - // privateKey: ACCOUNT_PRIVATEKEY, - type: 'EDDSA', - domain: window.location.origin, - seed: offChainData.seedString, - address, - }, - ); + const createBucketTx = await client.bucket.createBucket({ + bucketName: createBucketInfo.bucketName, + creator: address, + visibility: VisibilityType.VISIBILITY_TYPE_PUBLIC_READ, + chargedReadQuota: Long.fromString('0'), + paymentAddress: address, + primarySpAddress: spInfo.primarySpAddress, + }); const setTagTx = await client.storage.setTag({ operator: address, diff --git a/packages/js-sdk/src/api/bucket.ts b/packages/js-sdk/src/api/bucket.ts index dba8b0ce..9dd38430 100644 --- a/packages/js-sdk/src/api/bucket.ts +++ b/packages/js-sdk/src/api/bucket.ts @@ -24,9 +24,9 @@ import { MsgPutPolicy, MsgUpdateBucketInfo, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; +import { PickVGFStrategy } from '@bnb-chain/greenfield-cosmos-types/greenfield/virtualgroup/common'; import { bytesFromBase64 } from '@bnb-chain/greenfield-cosmos-types/helpers'; import { Headers } from 'cross-fetch'; -import { bytesToUtf8, hexToBytes } from 'ethereum-cryptography/utils'; import Long from 'long'; import { container, delay, inject, injectable } from 'tsyringe'; import { @@ -75,8 +75,6 @@ import { MsgDeleteBucketSDKTypeEIP712 } from '../messages/greenfield/storage/Msg import { MsgMigrateBucketSDKTypeEIP712 } from '../messages/greenfield/storage/MsgMigrateBucket'; import { MsgUpdateBucketInfoSDKTypeEIP712 } from '../messages/greenfield/storage/MsgUpdateBucketInfo'; import type { - CreateBucketApprovalRequest, - CreateBucketApprovalResponse, GetBucketMetaRequest, GetBucketMetaResponse, GetUserBucketsRequest, @@ -97,12 +95,13 @@ import { verifyAddress, verifyBucketName, verifyUrl } from '../utils/asserts/s3' import { decodeObjectFromHexString } from '../utils/encoding'; import { Sp } from './sp'; import { Storage } from './storage'; +import { VirtualGroup } from './virtualGroup'; export interface IBucket { /** - * get approval of creating bucket and send createBucket txn to greenfield chain + * send createBucket txn to greenfield chain */ - createBucket(params: CreateBucketApprovalRequest, authType: AuthType): Promise; + createBucket(msg: MsgCreateBucket): Promise; deleteBucket(msg: MsgDeleteBucket): Promise; @@ -125,14 +124,6 @@ export interface IBucket { authType: AuthType, ): Promise>; - /** - * returns the signature info for the approval of preCreating resources - */ - getCreateBucketApproval( - configParam: CreateBucketApprovalRequest, - authType: AuthType, - ): Promise>; - getMigrateBucketApproval( params: MigrateBucketApprovalRequest, authType: AuthType, @@ -197,119 +188,56 @@ export class Bucket implements IBucket { @inject(delay(() => TxClient)) private txClient: TxClient, @inject(delay(() => Sp)) private sp: Sp, @inject(delay(() => Storage)) private storage: Storage, + @inject(delay(() => VirtualGroup)) private virtualGroup: VirtualGroup, ) {} private queryClient = container.resolve(RpcQueryClient); private spClient = container.resolve(SpClient); - public async getCreateBucketApproval( - params: CreateBucketApprovalRequest, - authType: AuthType, - ): Promise> { - const { - bucketName, - creator, - visibility = 'VISIBILITY_TYPE_PUBLIC_READ', - chargedReadQuota, - spInfo, - duration, - paymentAddress, - } = params; - - try { - assertAuthType(authType); - assertStringRequire(spInfo.primarySpAddress, 'Primary sp address is missing'); - assertStringRequire(creator, 'Empty creator address'); - verifyBucketName(bucketName); + public async createBucket(msg: MsgCreateBucket) { + assertStringRequire(msg.primarySpAddress, 'Primary sp address is missing'); + assertStringRequire(msg.creator, 'Empty creator address'); + verifyBucketName(msg.bucketName); - const endpoint = await this.sp.getSPUrlByPrimaryAddr(spInfo.primarySpAddress); - - const { reqMeta, optionsWithOutHeaders, url } = - getApprovalMetaInfo(endpoint, 'CreateBucket', { - bucket_name: bucketName, - creator, - visibility, - primary_sp_address: spInfo.primarySpAddress, - primary_sp_approval: { - expired_height: '0', - sig: '', - global_virtual_group_family_id: 0, - }, - charged_read_quota: chargedReadQuota, - payment_address: paymentAddress, - }); + const { storageProvider } = await this.sp.getStorageProviderByOperatorAddress({ + operatorAddress: msg.primarySpAddress, + }); - const signHeaders = await this.spClient.signHeaders(reqMeta, authType); - const requestOptions: RequestInit = { - ...optionsWithOutHeaders, - headers: signHeaders, - }; + if (!storageProvider) { + throw new Error(`Storage provider ${msg.primarySpAddress} not found`); + } - const result = await this.spClient.callApi(url, requestOptions, duration, { - code: -1, - message: 'Get create bucket approval error.', + const { globalVirtualGroupFamilyId } = + await this.virtualGroup.getSpOptimalGlobalVirtualGroupFamily({ + spId: storageProvider.id, + pickVgfStrategy: PickVGFStrategy.Strategy_Maximize_Free_Store_Size, }); - const signedMsgString = result.headers.get('X-Gnfd-Signed-Msg') || ''; - - return { - code: 0, - message: 'Get create bucket approval success.', - body: signedMsgString, - statusCode: result.status, - } as SpResponse; - } catch (error: any) { - throw { - code: -1, - message: error.message, - statusCode: error?.statusCode || NORMAL_ERROR_CODE, - }; - } - } + const createBucketMsg: MsgCreateBucket = { + ...msg, + primarySpApproval: { + globalVirtualGroupFamilyId: globalVirtualGroupFamilyId, + expiredHeight: Long.fromInt(0), + sig: Uint8Array.from([]), + }, + }; - private async createBucketTx(msg: MsgCreateBucket, signedMsg: CreateBucketApprovalResponse) { return await this.txClient.tx( MsgCreateBucketTypeUrl, msg.creator, MsgCreateBucketSDKTypeEIP712, { - ...signedMsg, - type: MsgCreateBucketTypeUrl, - charged_read_quota: signedMsg.charged_read_quota, - visibility: signedMsg.visibility, - primary_sp_approval: signedMsg.primary_sp_approval, + ...MsgCreateBucket.toSDK(createBucketMsg), + primary_sp_approval: { + expired_height: '0', + global_virtual_group_family_id: globalVirtualGroupFamilyId, + }, + charged_read_quota: createBucketMsg.chargedReadQuota.toString(), }, - MsgCreateBucket.encode(msg).finish(), + MsgCreateBucket.encode(createBucketMsg).finish(), ); } - public async createBucket(params: CreateBucketApprovalRequest, authType: AuthType) { - assertAuthType(authType); - const { body } = await this.getCreateBucketApproval(params, authType); - - if (!body) { - throw new Error('Get create bucket approval error'); - } - - const signedMsg = JSON.parse(bytesToUtf8(hexToBytes(body))) as CreateBucketApprovalResponse; - - const msg: MsgCreateBucket = { - bucketName: signedMsg.bucket_name, - creator: signedMsg.creator, - visibility: visibilityTypeFromJSON(signedMsg.visibility), - primarySpAddress: signedMsg.primary_sp_address, - primarySpApproval: { - expiredHeight: Long.fromString(signedMsg.primary_sp_approval.expired_height), - sig: bytesFromBase64(signedMsg.primary_sp_approval.sig), - globalVirtualGroupFamilyId: signedMsg.primary_sp_approval.global_virtual_group_family_id, - }, - chargedReadQuota: Long.fromString(signedMsg.charged_read_quota), - paymentAddress: signedMsg.payment_address, - }; - - return await this.createBucketTx(msg, signedMsg); - } - public async deleteBucket(msg: MsgDeleteBucket) { return await this.txClient.tx( MsgDeleteBucketTypeUrl, diff --git a/packages/js-sdk/src/api/virtualGroup.ts b/packages/js-sdk/src/api/virtualGroup.ts index 9e5a4081..7c847dc3 100644 --- a/packages/js-sdk/src/api/virtualGroup.ts +++ b/packages/js-sdk/src/api/virtualGroup.ts @@ -8,6 +8,10 @@ import { QueryGlobalVirtualGroupRequest, QueryGlobalVirtualGroupResponse, QueryParamsResponse, + QuerySPAvailableGlobalVirtualGroupFamiliesRequest, + QuerySPAvailableGlobalVirtualGroupFamiliesResponse, + QuerySpOptimalGlobalVirtualGroupFamilyRequest, + QuerySpOptimalGlobalVirtualGroupFamilyResponse, } from '@bnb-chain/greenfield-cosmos-types/greenfield/virtualgroup/query'; import { MsgSettle } from '@bnb-chain/greenfield-cosmos-types/greenfield/virtualgroup/tx'; import { container, delay, inject, injectable } from 'tsyringe'; @@ -35,6 +39,14 @@ export interface IVirtualGroup { request: QueryGlobalVirtualGroupFamilyRequest, ): Promise; + getSpOptimalGlobalVirtualGroupFamily( + request: QuerySpOptimalGlobalVirtualGroupFamilyRequest, + ): Promise; + + getSpAvailableGlobalVirtualGroupFamilies( + request: QuerySPAvailableGlobalVirtualGroupFamiliesRequest, + ): Promise; + settle(address: string, msg: MsgSettle): Promise; } @@ -68,6 +80,20 @@ export class VirtualGroup implements IVirtualGroup { return await rpc.GlobalVirtualGroupFamily(request); } + public async getSpOptimalGlobalVirtualGroupFamily( + request: QuerySpOptimalGlobalVirtualGroupFamilyRequest, + ) { + const rpc = await this.queryClient.getVirtualGroupClient(); + return await rpc.QuerySpOptimalGlobalVirtualGroupFamily(request); + } + + public async getSpAvailableGlobalVirtualGroupFamilies( + request: QuerySPAvailableGlobalVirtualGroupFamiliesRequest, + ) { + const rpc = await this.queryClient.getVirtualGroupClient(); + return await rpc.QuerySpAvailableGlobalVirtualGroupFamilies(request); + } + public async settle(address: string, msg: MsgSettle) { return await this.txClient.tx( MsgSettleTypeUrl, diff --git a/packages/js-sdk/src/messages/greenfield/storage/MsgCreateBucket.ts b/packages/js-sdk/src/messages/greenfield/storage/MsgCreateBucket.ts index c56a2136..86c767bc 100644 --- a/packages/js-sdk/src/messages/greenfield/storage/MsgCreateBucket.ts +++ b/packages/js-sdk/src/messages/greenfield/storage/MsgCreateBucket.ts @@ -42,9 +42,9 @@ export const MsgCreateBucketSDKTypeEIP712 = { name: 'global_virtual_group_family_id', type: 'uint32', }, - { - name: 'sig', - type: 'bytes', - }, + // { + // name: 'sig', + // type: 'bytes', + // }, ], }; diff --git a/packages/js-sdk/src/types/common.ts b/packages/js-sdk/src/types/common.ts index c206a047..57dcb12b 100644 --- a/packages/js-sdk/src/types/common.ts +++ b/packages/js-sdk/src/types/common.ts @@ -1,6 +1,12 @@ -import * as StorageEnums from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; -import * as PermissionTypes from '@bnb-chain/greenfield-cosmos-types/greenfield/permission/common'; -import * as TimestampTypes from '@bnb-chain/greenfield-cosmos-types/google/protobuf/timestamp'; +export * as TimestampTypes from '@bnb-chain/greenfield-cosmos-types/google/protobuf/timestamp'; +export * as PermissionTypes from '@bnb-chain/greenfield-cosmos-types/greenfield/permission/common'; +export * as StorageEnums from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; +export { + BucketStatus, + ObjectStatus, + RedundancyType, + SourceType, + VisibilityType, +} from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; +export { Long }; import Long from 'long'; - -export { Long, StorageEnums, PermissionTypes, TimestampTypes }; diff --git a/packages/js-sdk/src/types/sp/BucketApproval.ts b/packages/js-sdk/src/types/sp/BucketApproval.ts deleted file mode 100644 index 1ca043ba..00000000 --- a/packages/js-sdk/src/types/sp/BucketApproval.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { VisibilityType } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; - -export type CreateBucketApprovalRequest = { - bucketName: string; - creator: string; - visibility: keyof typeof VisibilityType; - chargedReadQuota: string; - spInfo: { - primarySpAddress: string; - }; - duration?: number; - paymentAddress: string; -}; - -export interface CreateBucketApprovalResponse { - creator: string; - bucket_name: string; - visibility: keyof typeof VisibilityType; - payment_address: string; - primary_sp_address: string; - primary_sp_approval: { - expired_height: string; - sig: string; - global_virtual_group_family_id: number; - }; - charged_read_quota: string; -} diff --git a/packages/js-sdk/src/types/sp/index.ts b/packages/js-sdk/src/types/sp/index.ts index cea87ef0..cd13cd0d 100644 --- a/packages/js-sdk/src/types/sp/index.ts +++ b/packages/js-sdk/src/types/sp/index.ts @@ -1,4 +1,3 @@ -export * from './BucketApproval'; export * from './ErrorResponse'; export * from './GetBucketMeta'; export * from './GetObject'; diff --git a/packages/js-sdk/src/utils/asserts/s3.ts b/packages/js-sdk/src/utils/asserts/s3.ts index 536c1069..87a6a5d7 100644 --- a/packages/js-sdk/src/utils/asserts/s3.ts +++ b/packages/js-sdk/src/utils/asserts/s3.ts @@ -114,9 +114,49 @@ const generateUrlByBucketName = (endpoint = '', bucketName: string) => { return endpoint.replace(`${protocol}//`, `${protocol}//${bucketName}.`); }; +const isSQLInjection = (input: string) => { + // Define patterns that may indicate SQL injection, especially those with a semicolon followed by common SQL keywords + const patterns = [ + '(?i).*;.*select', // Matches any string with a semicolon followed by "select" + '(?i).*;.*insert', // Matches any string with a semicolon followed by "insert" + '(?i).*;.*update', // Matches any string with a semicolon followed by "update" + '(?i).*;.*delete', // Matches any string with a semicolon followed by "delete" + '(?i).*;.*drop', // Matches any string with a semicolon followed by "drop" + '(?i).*;.*alter', // Matches any string with a semicolon followed by "alter" + '/\\*.*\\*/', // Matches SQL block comment + ]; + + for (const pattern of patterns) { + const regex = new RegExp(pattern); + if (regex.test(input)) { + return true; + } + } + + return false; +}; + +// CheckObjectName This code block checks for unsupported or potentially risky formats in object names. +// The checks are essential for ensuring the security and compatibility of the object names within the system. +// 1. ".." in object names: Checked to prevent path traversal attacks which might access directories outside the intended scope. +// 2. Object name being "/": The root directory should not be used as an object name due to potential security risks and ambiguity. +// 3. "\\" in object names: Backslashes are checked because they are often not supported in UNIX-like file systems and can cause issues in path parsing. +// 4. SQL Injection patterns in object names: Ensures that the object name does not contain patterns that could be used for SQL injection attacks, maintaining the integrity of the database. +const checkObjectName = (objectName: string) => { + if ( + objectName.includes('..') || + objectName === '/' || + objectName.includes('\\') || + isSQLInjection(objectName) + ) { + throw new Error(`fail to check object name: ${objectName}`); + } +}; + export { verifyBucketName, verifyObjectName, + checkObjectName, verifyAddress, trimString, verifyUrl,