From b0336ef573063eebd17f0d31fadb4a1372ec712d Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Tue, 21 Feb 2023 15:01:55 +0800 Subject: [PATCH 01/11] post file --- controller/create_record.go | 3 +-- controller/main.go | 2 ++ controller/upload.go | 40 +++++++++++++++++++++++++++++++++++++ docker-compose.yml | 14 ++++++------- 4 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 controller/upload.go diff --git a/controller/create_record.go b/controller/create_record.go index b1480d0..dfa0577 100644 --- a/controller/create_record.go +++ b/controller/create_record.go @@ -1,7 +1,6 @@ package controller import ( - "fmt" "github.com/nextdotid/creator_suite/types" "github.com/nextdotid/creator_suite/util" "math/big" @@ -31,7 +30,7 @@ type CreateRecordResponse struct { func create_record(c *gin.Context) { req := CreateRecordRequest{} if err := c.BindJSON(&req); err != nil { - fmt.Println(req) + //fmt.Println(req) errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error", err)) return } diff --git a/controller/main.go b/controller/main.go index 262875f..cefedb3 100644 --- a/controller/main.go +++ b/controller/main.go @@ -40,6 +40,8 @@ func Init() { Engine.POST("/api/v1/create", create_record) Engine.POST("/api/v1/update", update_record) + Engine.POST("/api/v1/upload", upload_file) + Engine.GET("/api/v1/get-content", get_content) Engine.GET("/api/v1/show-content", show_content) diff --git a/controller/upload.go b/controller/upload.go new file mode 100644 index 0000000..7ec97d9 --- /dev/null +++ b/controller/upload.go @@ -0,0 +1,40 @@ +package controller + +import ( + "fmt" + "github.com/gin-gonic/gin" + "golang.org/x/xerrors" + "net/http" + "path/filepath" +) + +type UploadFileResponse struct { + Name string `json:"name"` + Email string `json:"email"` +} + +func upload_file(c *gin.Context) { + name := c.PostForm("name") + email := c.PostForm("email") + + // Source + file, err := c.FormFile("file") + if err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("get file error", err)) + return + } + + filename := filepath.Dir("/storage/" + file.Filename) + fmt.Printf("filename: %s", filename) + + if err = c.SaveUploadedFile(file, filename); err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to upload the file", err)) + return + } + + c.JSON(http.StatusOK, UploadFileResponse{ + Name: name, + Email: email, + }) + +} diff --git a/docker-compose.yml b/docker-compose.yml index 4077dac..9725212 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,13 +12,13 @@ services: POSTGRES_DB: ${DB_NAME} volumes: - creator_suite_db:/var/lib/postgresql/data - frontend: - image: ghcr.io/nextdotid/creator_suite_frontend_v2:latest - container_name: creator-suite-frontend - ports: - - "3001:3000" - environment: - NODE_ENV: development +# frontend: +# image: ghcr.io/nextdotid/creator_suite_frontend_v2:latest +# container_name: creator-suite-frontend +# ports: +# - "3001:3000" +# environment: +# NODE_ENV: development server: image: ghcr.io/nextdotid/creator_suite_server:latest container_name: creator-suite-server From 570861130c9725c590f28b1a78b5f2acd18ec78e Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Wed, 22 Mar 2023 14:19:40 +0800 Subject: [PATCH 02/11] create api request format --- controller/create_record.go | 41 ++++++++++++++++++++++++++++--------- controller/main.go | 1 - controller/upload.go | 40 ------------------------------------ 3 files changed, 31 insertions(+), 51 deletions(-) delete mode 100644 controller/upload.go diff --git a/controller/create_record.go b/controller/create_record.go index 2384f9c..1c16ef9 100644 --- a/controller/create_record.go +++ b/controller/create_record.go @@ -1,14 +1,16 @@ package controller import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/nextdotid/creator_suite/model" "github.com/nextdotid/creator_suite/types" "github.com/nextdotid/creator_suite/util" + "golang.org/x/xerrors" "math/big" "net/http" - - "github.com/gin-gonic/gin" - "github.com/nextdotid/creator_suite/model" - "golang.org/x/xerrors" + "path/filepath" + "strconv" ) type CreateRecordRequest struct { @@ -29,16 +31,35 @@ type CreateRecordResponse struct { func create_record(c *gin.Context) { req := CreateRecordRequest{} - if err := c.BindJSON(&req); err != nil { - //fmt.Println(req) - errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error", err)) - return - } + fmt.Println(c.PostForm("network")) + req.Network = types.Network(c.PostForm("network")) if !req.Network.IsValid() { errorResp(c, http.StatusBadRequest, xerrors.Errorf("Cannot support the network right now")) return } + req.ManagedContract = c.PostForm("managed_contract") + req.PaymentTokenAddress = c.PostForm("payment_token_address") + req.PaymentTokenAmount = c.PostForm("payment_token_amount") + req.Password = c.PostForm("password") + req.ContentName = c.PostForm("content_name") + et, _ := strconv.ParseInt(c.PostForm("encryption_type"), 10, 64) + req.EncryptionType = int8(et) + req.Description = c.PostForm("description") + // Source + file, err := c.FormFile("file") + if err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("get file error", err)) + return + } + + filename := "/storage/" + file.Filename + fmt.Printf("filename: %s", filename) + if err = c.SaveUploadedFile(file, filename); err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to upload the file", err)) + return + } + fileExtension := filepath.Ext(filename) var keyID int64 if req.EncryptionType == model.ENCRYPTION_TYPE_AES { record := &model.KeyRecord{ @@ -59,7 +80,7 @@ func create_record(c *gin.Context) { } content, err := model.CreateRecord(req.ManagedContract, keyID, req.EncryptionType, - req.FileExtension, req.Network, req.ContentName, req.Description) + fileExtension, req.Network, req.ContentName, req.Description) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) return diff --git a/controller/main.go b/controller/main.go index cefedb3..e32507c 100644 --- a/controller/main.go +++ b/controller/main.go @@ -40,7 +40,6 @@ func Init() { Engine.POST("/api/v1/create", create_record) Engine.POST("/api/v1/update", update_record) - Engine.POST("/api/v1/upload", upload_file) Engine.GET("/api/v1/get-content", get_content) Engine.GET("/api/v1/show-content", show_content) diff --git a/controller/upload.go b/controller/upload.go deleted file mode 100644 index 7ec97d9..0000000 --- a/controller/upload.go +++ /dev/null @@ -1,40 +0,0 @@ -package controller - -import ( - "fmt" - "github.com/gin-gonic/gin" - "golang.org/x/xerrors" - "net/http" - "path/filepath" -) - -type UploadFileResponse struct { - Name string `json:"name"` - Email string `json:"email"` -} - -func upload_file(c *gin.Context) { - name := c.PostForm("name") - email := c.PostForm("email") - - // Source - file, err := c.FormFile("file") - if err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("get file error", err)) - return - } - - filename := filepath.Dir("/storage/" + file.Filename) - fmt.Printf("filename: %s", filename) - - if err = c.SaveUploadedFile(file, filename); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to upload the file", err)) - return - } - - c.JSON(http.StatusOK, UploadFileResponse{ - Name: name, - Email: email, - }) - -} From 2a770e023b45b5096fb6bbfb5225c8bbeca1225c Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Wed, 22 Mar 2023 14:24:41 +0800 Subject: [PATCH 03/11] fix file extension --- controller/create_record.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/controller/create_record.go b/controller/create_record.go index 1c16ef9..f0add37 100644 --- a/controller/create_record.go +++ b/controller/create_record.go @@ -1,7 +1,6 @@ package controller import ( - "fmt" "github.com/gin-gonic/gin" "github.com/nextdotid/creator_suite/model" "github.com/nextdotid/creator_suite/types" @@ -11,6 +10,7 @@ import ( "net/http" "path/filepath" "strconv" + "strings" ) type CreateRecordRequest struct { @@ -31,7 +31,6 @@ type CreateRecordResponse struct { func create_record(c *gin.Context) { req := CreateRecordRequest{} - fmt.Println(c.PostForm("network")) req.Network = types.Network(c.PostForm("network")) if !req.Network.IsValid() { errorResp(c, http.StatusBadRequest, xerrors.Errorf("Cannot support the network right now")) @@ -54,12 +53,11 @@ func create_record(c *gin.Context) { } filename := "/storage/" + file.Filename - fmt.Printf("filename: %s", filename) if err = c.SaveUploadedFile(file, filename); err != nil { errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to upload the file", err)) return } - fileExtension := filepath.Ext(filename) + fileExtension := strings.Trim(filepath.Ext(filename), ".") var keyID int64 if req.EncryptionType == model.ENCRYPTION_TYPE_AES { record := &model.KeyRecord{ From 33f3011af6504afec890ab2398ee7414a16f0060 Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Wed, 22 Mar 2023 15:48:44 +0800 Subject: [PATCH 04/11] file encryption --- controller/create_record.go | 49 +++++++++++++++++++++++++------------ docker-compose.yml | 1 - model/content.go | 4 +-- model/contract.go | 45 ++++++++++++++++------------------ test.png | 0 5 files changed, 56 insertions(+), 43 deletions(-) create mode 100644 test.png diff --git a/controller/create_record.go b/controller/create_record.go index f0add37..181e9a9 100644 --- a/controller/create_record.go +++ b/controller/create_record.go @@ -5,9 +5,12 @@ import ( "github.com/nextdotid/creator_suite/model" "github.com/nextdotid/creator_suite/types" "github.com/nextdotid/creator_suite/util" + "github.com/nextdotid/creator_suite/util/dare" + "github.com/nextdotid/creator_suite/util/encrypt" "golang.org/x/xerrors" "math/big" "net/http" + "os" "path/filepath" "strconv" "strings" @@ -23,6 +26,7 @@ type CreateRecordRequest struct { EncryptionType int8 `json:"encryption_type"` FileExtension string `json:"file_extension"` Description string `json:"description"` + CreatorAddress string `json:"creator_address"` } type CreateRecordResponse struct { @@ -44,6 +48,7 @@ func create_record(c *gin.Context) { et, _ := strconv.ParseInt(c.PostForm("encryption_type"), 10, 64) req.EncryptionType = int8(et) req.Description = c.PostForm("description") + req.CreatorAddress = c.PostForm("creator_address") // Source file, err := c.FormFile("file") @@ -51,13 +56,8 @@ func create_record(c *gin.Context) { errorResp(c, http.StatusBadRequest, xerrors.Errorf("get file error", err)) return } + fileExtension := strings.Trim(filepath.Ext(file.Filename), ".") - filename := "/storage/" + file.Filename - if err = c.SaveUploadedFile(file, filename); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to upload the file", err)) - return - } - fileExtension := strings.Trim(filepath.Ext(filename), ".") var keyID int64 if req.EncryptionType == model.ENCRYPTION_TYPE_AES { record := &model.KeyRecord{ @@ -78,21 +78,38 @@ func create_record(c *gin.Context) { } content, err := model.CreateRecord(req.ManagedContract, keyID, req.EncryptionType, - fileExtension, req.Network, req.ContentName, req.Description) + fileExtension, req.Network, req.ContentName, req.Description, req.CreatorAddress) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) return } - //err = model.CreateAsset(content.ID, req.ManagedContract, req.PaymentTokenAddress, tokenAmount, req.Network) - //if err != nil { - // updateErr := content.UpdateToInvalidStatus(content.ID) - // if updateErr != nil { - // log.Errorf("update content record err:%v", updateErr) - // } - // errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Create an asset in Contract error: %v", err)) - // return - //} + filePath := "/storage/" + strconv.FormatInt(content.ID, 10) + "/" + file.Filename + if err = os.Mkdir("/storage/"+strconv.FormatInt(content.ID, 10), os.ModePerm); err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to create new folder, err: %w", err)) + return + } + if err = c.SaveUploadedFile(file, filePath); err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to upload the file, err:%w", err)) + return + } + + // generate encrypted file + if req.EncryptionType == model.ENCRYPTION_TYPE_AES { + src, _ := os.Open(filePath) + dst, _ := os.Create(filePath + ".enc") + key, err := encrypt.DeriveKey([]byte(req.Password), src, dst) + if err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to DeriveKey to encrypt", err)) + return + } + cfg := dare.Config{Key: key} + _, err = encrypt.AesEncrypt(src, dst, cfg) + if err != nil { + errorResp(c, http.StatusBadRequest, xerrors.Errorf("fail to encrypt the file", err)) + return + } + } c.JSON(http.StatusOK, CreateRecordResponse{ ContentID: content.ID, diff --git a/docker-compose.yml b/docker-compose.yml index 9725212..afeb74a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,6 @@ services: DB_USER: ${DB_USER} DB_PASSWORD: ${DB_PASSWORD} DB_NAME: ${DB_NAME} - TX_ACCOUNT: ${TX_ACCOUNT} RPC_SERVER_ON_CHAIN: ${RPC_SERVER_ON_CHAIN} volumes: - ~/storage:/storage diff --git a/model/content.go b/model/content.go index 895c86b..088978b 100644 --- a/model/content.go +++ b/model/content.go @@ -50,13 +50,13 @@ func ListContent() ([]Content, error) { } func CreateRecord(managedContract string, keyID int64, encryptionType int8, - fileExtension string, network types.Network, contentName string, description string) ( + fileExtension string, network types.Network, contentName string, description string, creator string) ( content *Content, err error) { c := &Content{} c.KeyID = keyID c.ContentName = contentName c.ManagedContract = managedContract - c.CreatorAddress = GetTxAccAddr().String() + c.CreatorAddress = creator c.EncryptionType = encryptionType c.FileExtension = fileExtension c.Network = string(network) diff --git a/model/contract.go b/model/contract.go index fab1e92..76c2b9a 100644 --- a/model/contract.go +++ b/model/contract.go @@ -1,31 +1,28 @@ package model import ( - "crypto/ecdsa" "fmt" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/nextdotid/creator_suite/config" "github.com/nextdotid/creator_suite/model/contracts" - "github.com/nextdotid/creator_suite/types" "golang.org/x/xerrors" - "math/big" ) -func CreateAsset(contentId int64, contractAddr string, tokenAddr string, tokenAmount *big.Int, network types.Network) error { - conn, err := contracts.NewContracts(common.HexToAddress(contractAddr), EthClient) - if err != nil { - return xerrors.Errorf("failed to connect the content: %v", err) - } - tx_acc := GetTxAccSK() - transactOps, err := bind.NewKeyedTransactorWithChainID(tx_acc, network.GetChainID()) - tx, err := conn.CreateAsset(transactOps, uint64(contentId), common.HexToAddress(tokenAddr), tokenAmount) - if err != nil { - return xerrors.Errorf("failed to create the content asset through contract, err:%v, tx:%v", err, tx) - } - return nil -} +//func CreateAsset(contentId int64, contractAddr string, tokenAddr string, tokenAmount *big.Int, network types.Network) error { +// conn, err := contracts.NewContracts(common.HexToAddress(contractAddr), EthClient) +// if err != nil { +// return xerrors.Errorf("failed to connect the content: %v", err) +// } +// tx_acc := GetTxAccSK() +// transactOps, err := bind.NewKeyedTransactorWithChainID(tx_acc, network.GetChainID()) +// tx, err := conn.CreateAsset(transactOps, uint64(contentId), common.HexToAddress(tokenAddr), tokenAmount) +// if err != nil { +// return xerrors.Errorf("failed to create the content asset through contract, err:%v, tx:%v", err, tx) +// } +// return nil +//} func IsQualified(contractAddr string, addr string, assetId uint64) (bool, error) { conn, err := contracts.NewContracts(common.HexToAddress(contractAddr), EthClient) @@ -43,14 +40,14 @@ func GetAssetID(contractAddr string, addr string, contentID uint64) (uint64, err return conn.ContentAssetMapping(&bind.CallOpts{}, common.HexToAddress(addr), contentID) } -func GetTxAccSK() *ecdsa.PrivateKey { - skBytes := common.Hex2Bytes(config.GetTxAccConf()) - sk, err := crypto.ToECDSA(skBytes) - if err != nil { - l.Errorf("failed to parse paymaster secret key: %v", err) - } - return sk -} +//func GetTxAccSK() *ecdsa.PrivateKey { +// skBytes := common.Hex2Bytes(config.GetTxAccConf()) +// sk, err := crypto.ToECDSA(skBytes) +// if err != nil { +// l.Errorf("failed to parse paymaster secret key: %v", err) +// } +// return sk +//} func GetTxAccAddr() common.Address { skBytes := common.Hex2Bytes(config.GetTxAccConf()) diff --git a/test.png b/test.png new file mode 100644 index 0000000..e69de29 From 6a42d8b2e2134f03027443c70e4c9c428e82fce5 Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Wed, 22 Mar 2023 16:52:53 +0800 Subject: [PATCH 05/11] api doc --- docs/api.apib | 15 ++++++++++----- test.png | 0 2 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 test.png diff --git a/docs/api.apib b/docs/api.apib index f0a4bb7..9f274e2 100644 --- a/docs/api.apib +++ b/docs/api.apib @@ -1,5 +1,5 @@ FORMAT: 1A -HOST: https://xxx +HOST: https://xxx.coreservice.com/create_suite # CreatorSuite @@ -9,15 +9,17 @@ CreatorSuite Service provides create and get api to distribute content asset for ### Create a Content Asset [post] - + Attributes (object) + + Attributes (body/form-data) + managed_contract (string, required) - the contract that received the tokens + network (string, required) - interact network. + payment_token_address (string, required) - which token and how much it cost to unlock the content - + payment_token_amount (string, required) - same as above + + payment_token_amount (string, required) - same as above. support 1, 10, 1.5 as input + encryption_type (string, required) - encryption_type=1 symmetric encryption option, encryption_type=2 asymmetric encryption + password (string, optional) - depends on encryption_type. If encryption_type=1, use it encryption/decryption the content; if encryption_type=2, keep it empty. - + file_extension (string, required) - record the file extension for decryption process + + content_name (string, optional) - name of the content + description (string, optional) - description of the content + + creator_address(string, required) - creator's wallet address + + file (file, required) - the original file to list + Request (application/json) @@ -28,7 +30,10 @@ CreatorSuite Service provides create and get api to distribute content asset for "payment_token_amount": "5" , "password": "1234567890qwertq", "encryption_type":1, - "file_extension":"jpg" + "content_name": "my first content" + "description" : "it's about ..." + "creator_address": "0x..." + "file": XXX } diff --git a/test.png b/test.png deleted file mode 100644 index e69de29..0000000 From 747bc647c7576303651cdeb988e3fa4dbe80fc74 Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Wed, 22 Mar 2023 19:10:46 +0800 Subject: [PATCH 06/11] add content info api --- controller/content_info.go | 46 ++++++++++++++ controller/create_record.go | 30 +++------ controller/file_manage.go | 117 +++++++++--------------------------- controller/get_content.go | 2 +- controller/main.go | 12 +--- docs/api.apib | 12 ++-- 6 files changed, 96 insertions(+), 123 deletions(-) create mode 100644 controller/content_info.go diff --git a/controller/content_info.go b/controller/content_info.go new file mode 100644 index 0000000..b4c07ea --- /dev/null +++ b/controller/content_info.go @@ -0,0 +1,46 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "github.com/nextdotid/creator_suite/model" + "golang.org/x/xerrors" + "net/http" + "strconv" +) + +type ContentInfoRequest struct { + ContentID int64 `json:"content_id"` +} + +type ContentInfoResponse struct { + Extension string `json:"extension"` + ManagedContract string `json:"managed_contract"` + ContentName string `json:"content_name"` + Description string `json:"description"` + CreatorAddress string `json:"creator_address"` + KeyID int64 `json:"key_id"` + CreatedTime string `json:"created_time"` + UpdateTime string `json:"update_time"` +} + +func content_info(c *gin.Context) { + req := ContentInfoRequest{} + + req.ContentID, _ = strconv.ParseInt(c.Query("content_id"), 10, 64) + content, err := model.FindContentByID(req.ContentID) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) + return + } + + c.JSON(http.StatusOK, ContentInfoResponse{ + Extension: content.FileExtension, + ManagedContract: content.ManagedContract, + CreatorAddress: content.CreatorAddress, + ContentName: content.ContentName, + Description: content.Description, + KeyID: content.KeyID, + CreatedTime: content.CreatedAt.String(), + UpdateTime: content.UpdatedAt.String(), + }) +} diff --git a/controller/create_record.go b/controller/create_record.go index 181e9a9..78adf04 100644 --- a/controller/create_record.go +++ b/controller/create_record.go @@ -4,11 +4,9 @@ import ( "github.com/gin-gonic/gin" "github.com/nextdotid/creator_suite/model" "github.com/nextdotid/creator_suite/types" - "github.com/nextdotid/creator_suite/util" "github.com/nextdotid/creator_suite/util/dare" "github.com/nextdotid/creator_suite/util/encrypt" "golang.org/x/xerrors" - "math/big" "net/http" "os" "path/filepath" @@ -17,16 +15,14 @@ import ( ) type CreateRecordRequest struct { - ManagedContract string `json:"managed_contract"` - Network types.Network `json:"network"` - PaymentTokenAddress string `json:"payment_token_address"` - PaymentTokenAmount string `json:"payment_token_amount"` - Password string `json:"password"` - ContentName string `json:"content_name"` - EncryptionType int8 `json:"encryption_type"` - FileExtension string `json:"file_extension"` - Description string `json:"description"` - CreatorAddress string `json:"creator_address"` + ManagedContract string `json:"managed_contract"` + Network types.Network `json:"network"` + Password string `json:"password"` + ContentName string `json:"content_name"` + EncryptionType int8 `json:"encryption_type"` + FileExtension string `json:"file_extension"` + Description string `json:"description"` + CreatorAddress string `json:"creator_address"` } type CreateRecordResponse struct { @@ -41,8 +37,6 @@ func create_record(c *gin.Context) { return } req.ManagedContract = c.PostForm("managed_contract") - req.PaymentTokenAddress = c.PostForm("payment_token_address") - req.PaymentTokenAmount = c.PostForm("payment_token_amount") req.Password = c.PostForm("password") req.ContentName = c.PostForm("content_name") et, _ := strconv.ParseInt(c.PostForm("encryption_type"), 10, 64) @@ -50,7 +44,6 @@ func create_record(c *gin.Context) { req.Description = c.PostForm("description") req.CreatorAddress = c.PostForm("creator_address") - // Source file, err := c.FormFile("file") if err != nil { errorResp(c, http.StatusBadRequest, xerrors.Errorf("get file error", err)) @@ -70,13 +63,6 @@ func create_record(c *gin.Context) { return } } - - tokenAmount := util.ToWei(req.PaymentTokenAmount, 18) - if tokenAmount == big.NewInt(0) { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("token amount invalid")) - return - } - content, err := model.CreateRecord(req.ManagedContract, keyID, req.EncryptionType, fileExtension, req.Network, req.ContentName, req.Description, req.CreatorAddress) if err != nil { diff --git a/controller/file_manage.go b/controller/file_manage.go index 97848d7..bf5eabe 100644 --- a/controller/file_manage.go +++ b/controller/file_manage.go @@ -26,33 +26,26 @@ type Folder struct { CreatedTime string `json:"created_time"` UpdateTime string `json:"update_time"` ContentID int64 `json:"content_id"` - Files []File `json:"children"` + Files []File `json:"files"` } type File struct { - Name string `json:"name"` - Type string `json:"type"` - Size string `json:"size"` - Extension string `json:"extension"` - Path string `json:"path"` - - ContentID int64 `json:"content_id"` + Name string `json:"name"` + Size string `json:"size"` + Extension string `json:"extension"` + Path string `json:"path"` ManagedContract string `json:"managed_contract"` + ContentName string `json:"content_name"` + Description string `json:"description"` + CreatorAddress string `json:"creator_address"` AssetID int64 `json:"asset_id"` KeyID int64 `json:"key_id"` - LocationUrl string `json:"location_url"` CreatedTime string `json:"created_time"` UpdateTime string `json:"update_time"` } -type ListRequest struct { - Path string `json:"path"` // /storage/ - //Cfg ipfs.IpfsConfig `json:"cfg"` -} - type ListResponse struct { Folders []Folder `json:"folders"` - Files []File `json:"files"` } func formatFileSize(fileSize int64) (size string) { @@ -89,14 +82,8 @@ func pathJoin(basePath string, elem ...string) string { } func list(c *gin.Context) { - req := ListRequest{} - if err := c.BindJSON(&req); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error")) - return - } - + const FILE_PATH = "/storage" folders := make([]Folder, 0) - files := make([]File, 0) // list content table contents, err := model.ListContent() @@ -109,9 +96,7 @@ func list(c *gin.Context) { contentMap[c.ID] = c } - // list volumes: STORAGE - log.Infof("list storage volumes: %s", req.Path) - list, err := ioutil.ReadDir(req.Path) + list, err := ioutil.ReadDir(FILE_PATH) if err != nil { log.Infof("I/O error: %v", err) errorResp(c, http.StatusBadRequest, xerrors.Errorf("I/O error")) @@ -123,15 +108,18 @@ func list(c *gin.Context) { if err != nil { continue } + content, err := model.FindContentByID(contentID) + if err != nil { + continue + } folder := Folder{ Name: item.Name(), - Type: "dirs", - Path: pathJoin(req.Path, item.Name()), + Path: pathJoin(FILE_PATH, item.Name()), ContentID: contentID, CreatedTime: util.Datetime2DateString(item.ModTime()), UpdateTime: util.Datetime2DateString(item.ModTime()), } - children := make([]File, 0) + files := make([]File, 0) f, err := ioutil.ReadDir(folder.Path) if err != nil { errorResp(c, http.StatusBadRequest, xerrors.Errorf("I/O error")) @@ -139,74 +127,29 @@ func list(c *gin.Context) { } for _, item := range f { if !item.IsDir() { - children = append(children, File{ - Name: item.Name(), - Type: "localfile", - Size: formatFileSize(item.Size()), - Extension: Ext(item.Name()), - Path: filepath.Join(folder.Path, item.Name()), - CreatedTime: util.Datetime2DateString(item.ModTime()), - UpdateTime: util.Datetime2DateString(item.ModTime()), + files = append(files, File{ + Name: item.Name(), + //Type: "localfile", + Size: formatFileSize(item.Size()), + Extension: content.FileExtension, + ManagedContract: content.ManagedContract, + CreatorAddress: content.CreatorAddress, + ContentName: content.ContentName, + Description: content.Description, + KeyID: content.KeyID, + Path: filepath.Join(folder.Path, item.Name()), + CreatedTime: util.Datetime2DateString(item.ModTime()), + UpdateTime: util.Datetime2DateString(item.ModTime()), }) } } - - //if content, ok := contentMap[contentID]; ok { - // if content.EncryptionType == model.ENCRYPTION_TYPE_AES { - // //cid := ipfs.ParseCid(content.LocationUrl) - // //ctx, cancel := context.WithCancel(context.Background()) - // //defer func() { - // // cancel() - // //}() - // //log.Infof("content_id = %d, cid = %s", content.ID, cid) - // //stat, err := ipfs.Stat(ctx, &req.Cfg, cid) - // //if err != nil { - // // errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in IPFS: %w", err)) - // // return - // //} - // children = append(children, File{ - // Name: folder.Name, - // Type: "ipfsfile", - // Size: formatFileSize(stat.Size), - // Extension: "ipfs", - // Path: content.LocationUrl, - // - // ContentID: content.ID, - // ManagedContract: content.ManagedContract, - // KeyID: content.KeyID, - // LocationUrl: content.LocationUrl, - // CreatedTime: util.Datetime2DateString(content.CreatedAt), - // UpdateTime: util.Datetime2DateString(content.UpdatedAt), - // }) - // } - //} - folder.Files = children + folder.Files = files folders = append(folders, folder) } } - // list host mount path - list2, err := ioutil.ReadDir(req.Path) - if err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("I/O error")) - return - } - for _, item := range list2 { - if !item.IsDir() { - files = append(files, File{ - Name: item.Name(), - Type: "localfile", - Size: formatFileSize(item.Size()), - Extension: Ext(item.Name()), - Path: pathJoin(req.Path, item.Name()), - CreatedTime: util.Datetime2DateString(item.ModTime()), - UpdateTime: util.Datetime2DateString(item.ModTime()), - }) - } - } c.JSON(http.StatusOK, ListResponse{ Folders: folders, - Files: files, }) } diff --git a/controller/get_content.go b/controller/get_content.go index c5f408b..736ebe9 100644 --- a/controller/get_content.go +++ b/controller/get_content.go @@ -33,7 +33,7 @@ type GetContentResponse struct { func get_content(c *gin.Context) { req := GetContentRequest{} req.PublicKey = c.Query("public_key") - req.ContentID, _ = strconv.ParseInt(c.Query("content_id"), 0, 64) + req.ContentID, _ = strconv.ParseInt(c.Query("content_id"), 10, 64) pub_key, err := util.StringToPublicKey(req.PublicKey) if err != nil { diff --git a/controller/main.go b/controller/main.go index e32507c..2a8bab7 100644 --- a/controller/main.go +++ b/controller/main.go @@ -42,15 +42,9 @@ func Init() { Engine.POST("/api/v1/update", update_record) Engine.GET("/api/v1/get-content", get_content) - Engine.GET("/api/v1/show-content", show_content) - - //Engine.POST("/api/v1/ipfs/alive", alive) - //Engine.POST("/api/v1/ipfs/upload", upload) - - Engine.POST("/api/v1/file/list", list) - Engine.POST("/api/v1/file/create", create) - Engine.POST("/api/v1/file/move", move) - Engine.POST("/api/v1/file/copy", copy) + Engine.GET("/api/v1/content-info", content_info) + //Engine.GET("/api/v1/show-content", show_content) + Engine.GET("/api/v1/file/list", list) } type ErrorResponse struct { diff --git a/docs/api.apib b/docs/api.apib index 9f274e2..222df5d 100644 --- a/docs/api.apib +++ b/docs/api.apib @@ -12,8 +12,6 @@ CreatorSuite Service provides create and get api to distribute content asset for + Attributes (body/form-data) + managed_contract (string, required) - the contract that received the tokens + network (string, required) - interact network. - + payment_token_address (string, required) - which token and how much it cost to unlock the content - + payment_token_amount (string, required) - same as above. support 1, 10, 1.5 as input + encryption_type (string, required) - encryption_type=1 symmetric encryption option, encryption_type=2 asymmetric encryption + password (string, optional) - depends on encryption_type. If encryption_type=1, use it encryption/decryption the content; if encryption_type=2, keep it empty. + content_name (string, optional) - name of the content @@ -26,8 +24,6 @@ CreatorSuite Service provides create and get api to distribute content asset for { "managed_contract": "0x3A6c014579583c5D412A9F3914a67C0885dB90c0", "network":"mumbai", - "payment_token_address": "0x9801ca34B280b60292692E3fD00599f0Fbb8d6b2", - "payment_token_amount": "5" , "password": "1234567890qwertq", "encryption_type":1, "content_name": "my first content" @@ -38,7 +34,15 @@ CreatorSuite Service provides create and get api to distribute content asset for + Response 200 (application/json) + {} + +### Update a Content Asset [post] + + + Attributes (body/form-data) + + content_id (string, required) - the contract that received the tokens + + asset_id (string, required) - interact network. + + Response 200 (application/json) {} ## Get a Specific Asset [/get-content] From aaab65e4ab2de21b28c0f04d014b6e9c3dd5ec36 Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Thu, 23 Mar 2023 17:46:03 +0800 Subject: [PATCH 07/11] use file name as content name --- controller/create_record.go | 5 +- controller/file_manage.go | 201 +----------------------------------- controller/ipfs.go | 60 ----------- controller/main.go | 2 + util/ipfs/client.go | 15 +-- 5 files changed, 17 insertions(+), 266 deletions(-) delete mode 100644 controller/ipfs.go diff --git a/controller/create_record.go b/controller/create_record.go index 78adf04..a54bf98 100644 --- a/controller/create_record.go +++ b/controller/create_record.go @@ -18,7 +18,6 @@ type CreateRecordRequest struct { ManagedContract string `json:"managed_contract"` Network types.Network `json:"network"` Password string `json:"password"` - ContentName string `json:"content_name"` EncryptionType int8 `json:"encryption_type"` FileExtension string `json:"file_extension"` Description string `json:"description"` @@ -38,7 +37,6 @@ func create_record(c *gin.Context) { } req.ManagedContract = c.PostForm("managed_contract") req.Password = c.PostForm("password") - req.ContentName = c.PostForm("content_name") et, _ := strconv.ParseInt(c.PostForm("encryption_type"), 10, 64) req.EncryptionType = int8(et) req.Description = c.PostForm("description") @@ -63,8 +61,9 @@ func create_record(c *gin.Context) { return } } + content, err := model.CreateRecord(req.ManagedContract, keyID, req.EncryptionType, - fileExtension, req.Network, req.ContentName, req.Description, req.CreatorAddress) + fileExtension, req.Network, file.Filename, req.Description, req.CreatorAddress) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) return diff --git a/controller/file_manage.go b/controller/file_manage.go index bf5eabe..1cd8928 100644 --- a/controller/file_manage.go +++ b/controller/file_manage.go @@ -2,10 +2,8 @@ package controller import ( "fmt" - "io" "io/ioutil" "net/http" - "os" "path/filepath" "strconv" "strings" @@ -13,15 +11,12 @@ import ( "github.com/gin-gonic/gin" "github.com/nextdotid/creator_suite/model" "github.com/nextdotid/creator_suite/util" - "github.com/nextdotid/creator_suite/util/dare" - "github.com/nextdotid/creator_suite/util/encrypt" log "github.com/sirupsen/logrus" "golang.org/x/xerrors" ) type Folder struct { Name string `json:"name"` - Type string `json:"type"` Path string `json:"path"` CreatedTime string `json:"created_time"` UpdateTime string `json:"update_time"` @@ -38,7 +33,6 @@ type File struct { ContentName string `json:"content_name"` Description string `json:"description"` CreatorAddress string `json:"creator_address"` - AssetID int64 `json:"asset_id"` KeyID int64 `json:"key_id"` CreatedTime string `json:"created_time"` UpdateTime string `json:"update_time"` @@ -106,10 +100,12 @@ func list(c *gin.Context) { if item.IsDir() { contentID, err := strconv.ParseInt(item.Name(), 10, 32) if err != nil { + log.Warnf("invalid contentID: %d err: %v", contentID, err) continue } - content, err := model.FindContentByID(contentID) - if err != nil { + content := contentMap[contentID] + if &content == nil { + log.Warnf("fail to get content info, contentID: %d", contentID) continue } folder := Folder{ @@ -128,8 +124,7 @@ func list(c *gin.Context) { for _, item := range f { if !item.IsDir() { files = append(files, File{ - Name: item.Name(), - //Type: "localfile", + Name: item.Name(), Size: formatFileSize(item.Size()), Extension: content.FileExtension, ManagedContract: content.ManagedContract, @@ -152,189 +147,3 @@ func list(c *gin.Context) { Folders: folders, }) } - -type CreateRequest struct { - EncryptType int `json:"encrypt_type"` - Key string `json:"key"` - OriginFile string `json:"origin_file"` - EncryptFile string `json:"encrypt_file"` -} - -type CreateResponse struct { - KeyID int64 `json:"key_id"` - EncryptFile string `json:"encrypt_file"` -} - -func create(c *gin.Context) { - req := CreateRequest{} - if err := c.BindJSON(&req); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error")) - return - } - input := req.OriginFile - output := req.EncryptFile - if req.EncryptFile == "" { - output = fmt.Sprintf("%s.enc", input) - } - if req.EncryptType == model.ENCRYPTION_TYPE_AES { - if input == "" || output == "" { - fmt.Fprintf(os.Stderr, "\033[1;31;40m invalid file path") - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error: invalid file path")) - return - } - if req.Key == "" || len(req.Key) < 16 { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error: invalid aes key")) - return - } - in, err := os.Open(input) - if err != nil { - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("I/O Error: failed to open '%s': %v", input, err)) - return - } - out, err := os.Create(output) - if err != nil { - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("I/O Error: failed to create '%s': %v", output, err)) - return - } - key, err := encrypt.DeriveKey([]byte(req.Key), in, out) - if err != nil { - out.Close() - os.Remove(out.Name()) - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("DeriveKey err: %v", err)) - return - } - record := &model.KeyRecord{ - Password: req.Key, - } - keyID, err := record.CreateRecord() - if err != nil { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) - return - } - log.Infof("Password saved. [key id is %d ]", keyID) - cfg := dare.Config{Key: key} - if _, err := encrypt.AesEncrypt(in, out, cfg); err != nil { - out.Close() - os.Remove(out.Name()) - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("Encrypt err: %v", err)) - return - } - log.Infof("Encrypt content finished! [output file is %s]", out.Name()) - c.JSON(http.StatusOK, CreateResponse{ - KeyID: keyID, - EncryptFile: out.Name(), - }) - } else { - c.JSON(http.StatusOK, CreateResponse{ - KeyID: -1, - EncryptFile: "", - }) - } -} - -type MoveRequest struct { - Src string `json:"src"` - Dst string `json:"dst"` -} - -type MoveResponse struct{} - -func move(c *gin.Context) { - req := MoveRequest{} - if err := c.BindJSON(&req); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error")) - return - } - - if req.Src == "" || req.Dst == "" { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error: invalid file path")) - return - } - - path := filepath.Dir(req.Dst) - if _, err := os.Stat(path); os.IsNotExist(err) { - err := os.Mkdir(path, 0755) - if err != nil { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("I/O Error: %v", err)) - return - } - } - - log.Infof("Move file src: %s, dst: %s", req.Src, req.Dst) - err := os.Rename(req.Src, req.Dst) - if err != nil { - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("I/O Error: failed to move %v", err)) - return - } - c.JSON(http.StatusOK, MoveResponse{}) -} - -type CopyRequest struct { - Src string `json:"src"` - Dst string `json:"dst"` -} - -type CopyResponse struct{} - -func copy(c *gin.Context) { - req := CopyRequest{} - if err := c.BindJSON(&req); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error")) - return - } - - if req.Src == "" || req.Dst == "" { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error: invalid file path")) - return - } - - path := filepath.Dir(req.Dst) - if _, err := os.Stat(path); os.IsNotExist(err) { - err := os.Mkdir(path, 0755) - if err != nil { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("I/O Error: %v", err)) - return - } - } - - log.Infof("Move file src: %s, dst: %s", req.Src, req.Dst) - - fin, err := os.Open(req.Src) - if err != nil { - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("I/O Error: failed to Open %v", err)) - return - } - - defer fin.Close() - - fout, err := os.Create(req.Dst) - if err != nil { - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("I/O Error: failed to Create %v", err)) - return - } - - defer fout.Close() - - _, err = io.CopyBuffer(fout, fin, make([]byte, 32*1024)) - if err != nil { - errorResp(c, - http.StatusInternalServerError, - xerrors.Errorf("I/O Error: failed to copy %v", err)) - return - } - c.JSON(http.StatusOK, CopyResponse{}) -} diff --git a/controller/ipfs.go b/controller/ipfs.go deleted file mode 100644 index 9474a21..0000000 --- a/controller/ipfs.go +++ /dev/null @@ -1,60 +0,0 @@ -package controller - -import ( - "context" - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "github.com/nextdotid/creator_suite/util/ipfs" - "golang.org/x/xerrors" -) - -type AliveRequest = ipfs.IpfsConfig - -type AliveResponse struct { - IsAlive bool `json:"is_alive"` - Message string `json:"message"` -} - -func alive(c *gin.Context) { - req := ipfs.IpfsConfig{} - if err := c.BindJSON(&req); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error")) - return - } - - isAlive, err := ipfs.Alive(&req) - if err != nil { - c.JSON(http.StatusOK, AliveResponse{isAlive, fmt.Sprintf("%v", err)}) - return - } - c.JSON(http.StatusOK, AliveResponse{isAlive, ""}) -} - -type UploadRequest struct { - LocalFile string `json:"local_file"` - Cfg ipfs.IpfsConfig `json:"cfg"` -} - -type UploadResponse struct { - ContentLocateUrl string `json:"content_locate_url"` -} - -func upload(c *gin.Context) { - req := UploadRequest{} - if err := c.BindJSON(&req); err != nil { - errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error")) - return - } - ctx, cancel := context.WithCancel(context.Background()) - defer func() { - cancel() - }() - path, err := ipfs.Upload(ctx, &req.Cfg, req.LocalFile) - if err != nil { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in IPFS Upload: %w", err)) - return - } - c.JSON(http.StatusOK, UploadResponse{ContentLocateUrl: path}) -} diff --git a/controller/main.go b/controller/main.go index 2a8bab7..a1b2b95 100644 --- a/controller/main.go +++ b/controller/main.go @@ -43,7 +43,9 @@ func Init() { Engine.GET("/api/v1/get-content", get_content) Engine.GET("/api/v1/content-info", content_info) + //preview //Engine.GET("/api/v1/show-content", show_content) + Engine.GET("/api/v1/file/list", list) } diff --git a/util/ipfs/client.go b/util/ipfs/client.go index 6c677bb..31707f1 100644 --- a/util/ipfs/client.go +++ b/util/ipfs/client.go @@ -61,13 +61,14 @@ func Alive(cfg *IpfsConfig) (bool, error) { return true, nil } -/** - * @description: - * @param {*IpfsConfig} cfg IPFS Authorization Config - * @param {string} path LocalFile path - * @return { - {string} LocationUrl: object of the path in IPFS - } +/* +* + - @description: + - @param {*IpfsConfig} cfg IPFS Authorization Config + - @param {string} path LocalFile path + - @return { + {string} LocationUrl: object of the path in IPFS + } */ func Upload(ctx context.Context, cfg *IpfsConfig, path string) (string, error) { httpCli := &http.Client{} From 9d30d59832e806fe65a9e0067efd380b48bab15f Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Mon, 27 Mar 2023 18:46:31 +0800 Subject: [PATCH 08/11] encrypt method change to x25519-xsalsa20-poly1305(for metamask --- controller/get_content.go | 57 ++++++++++++++++++---------- util/encrypt/encrypt.go | 78 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 22 deletions(-) diff --git a/controller/get_content.go b/controller/get_content.go index 736ebe9..ef270e2 100644 --- a/controller/get_content.go +++ b/controller/get_content.go @@ -6,10 +6,8 @@ import ( "io/ioutil" "strconv" - "github.com/ethereum/go-ethereum/crypto" "github.com/gin-gonic/gin" "github.com/nextdotid/creator_suite/model" - "github.com/nextdotid/creator_suite/util" "github.com/nextdotid/creator_suite/util/encrypt" log "github.com/sirupsen/logrus" @@ -32,15 +30,23 @@ type GetContentResponse struct { func get_content(c *gin.Context) { req := GetContentRequest{} - req.PublicKey = c.Query("public_key") - req.ContentID, _ = strconv.ParseInt(c.Query("content_id"), 10, 64) - pub_key, err := util.StringToPublicKey(req.PublicKey) - if err != nil { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error, publicKey invalid: %w", err)) + if err := c.BindJSON(&req); err != nil { + fmt.Println(req) + errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error", err)) return } + // + //fmt.Println(req.PublicKey) + //req.ContentID, _ = strconv.ParseInt(c.GetQuery("content_id"), 10, 64) + + //pub_key, err := util.StringToPublicKey(req.PublicKey) + //if err != nil { + // errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error, publicKey invalid: %w", err)) + // return + //} + content, err := model.FindContentByID(req.ContentID) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) @@ -55,25 +61,36 @@ func get_content(c *gin.Context) { return } - is_paid, err := model.IsQualified(content.ManagedContract, crypto.PubkeyToAddress(*pub_key).String(), assetID) - if !is_paid || err != nil { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Can't find any payment record: %w", err)) - return - } - - key, err := model.FindKeyRecordByID(content.KeyID) - if err != nil { - errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) - return - } + //is_paid, err := model.IsQualified(content.ManagedContract, crypto.PubkeyToAddress(*pub_key).String(), assetID) + //if !is_paid || err != nil { + // errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Can't find any payment record: %w", err)) + // return + //} var encrypted_result string var encrypted_password string if content.EncryptionType == model.ENCRYPTION_TYPE_AES { - encrypted_password, err = encrypt.EncryptPasswordByPublicKey(key.Password, req.PublicKey) + key, err := model.FindKeyRecordByID(content.KeyID) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) + return + } + encrypted_password, err = encrypt.EncryptPasswordWithEncryptionPublicKey(req.PublicKey, key.Password) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Fail to give the encrypted password: %w", err)) + return + } encrypted_result, err = getContent(pathJoin(STORAGE, strconv.FormatInt(content.ID, 10), content.ContentName+".enc")) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Fail to give the encrypted content: %w", err)) + return + } } else { - encrypted_result, err = encrypt.EncryptContentByPublicKey(pathJoin(STORAGE, strconv.FormatInt(content.ID, 10), content.ContentName), req.PublicKey) + encrypted_result, err = encrypt.EncryptFileWithEncryptionPublicKey(req.PublicKey, pathJoin(STORAGE, strconv.FormatInt(content.ID, 10), content.ContentName)) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Fail to give the encrypted content: %w", err)) + return + } } if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Can't encrypt content by public_key: %w", err)) diff --git a/util/encrypt/encrypt.go b/util/encrypt/encrypt.go index d95fce7..1a25d76 100644 --- a/util/encrypt/encrypt.go +++ b/util/encrypt/encrypt.go @@ -2,7 +2,10 @@ package encrypt import ( "crypto/rand" + "encoding/base64" + "encoding/json" "fmt" + "golang.org/x/crypto/nacl/box" "io" "io/ioutil" "os" @@ -48,9 +51,9 @@ func EncryptContentByPublicKey(filePath string, publicKey string) (string, error } bytes, err := ioutil.ReadFile(filePath) if err != nil { - return "", fmt.Errorf("invalid public key") + return "", fmt.Errorf("fail to get the file") } - fmt.Printf("content bytes: %s", bytes) + //fmt.Printf("content bytes: %s", bytes) encryptDataByte, err := EciesEncrypt(bytes, keyByte) if err != nil { return "", err @@ -58,6 +61,77 @@ func EncryptContentByPublicKey(filePath string, publicKey string) (string, error return hexutil.Encode(encryptDataByte), nil } +func EncryptPasswordWithEncryptionPublicKey(encryptionPublicKey string, password string) (string, error) { + fmt.Printf("inside fun %s", encryptionPublicKey) + publicKeyBytes, err := base64.StdEncoding.DecodeString(encryptionPublicKey) + if err != nil { + return "", fmt.Errorf("DecodeString EncryptionPublicKey err:%v", err) + } + var publicKey [32]byte + copy(publicKey[:], publicKeyBytes) + + var nonce [24]byte + if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { + return "", fmt.Errorf("generate nonce for encryption err:%v", err) + } + + pk, sk, err := box.GenerateKey(rand.Reader) + if err != nil { + return "", fmt.Errorf("GenerateKey for encryption err:%v", err) + } + encryptedBytes := box.Seal(nil, []byte(password), &nonce, &publicKey, sk) + + //return encryptedMessage, nil + encryptionData := map[string]interface{}{ + "version": "x25519-xsalsa20-poly1305", + "nonce": base64.StdEncoding.EncodeToString(nonce[:]), + "ephemPublicKey": base64.StdEncoding.EncodeToString(pk[:]), + "ciphertext": base64.StdEncoding.EncodeToString(encryptedBytes), + } + + encryptedData, err := json.Marshal(encryptionData) + if err != nil { + return "", fmt.Errorf("Json Marshal EncryptionData struct err:%v", err) + } + return hexutil.Encode(encryptedData), nil +} + +func EncryptFileWithEncryptionPublicKey(encryptionPublicKey string, filePath string) (string, error) { + publicKeyBytes, err := base64.StdEncoding.DecodeString(encryptionPublicKey) + if err != nil { + return "", fmt.Errorf("DecodeString EncryptionPublicKey err:%v", err) + } + var publicKey [32]byte + copy(publicKey[:], publicKeyBytes) + + var nonce [24]byte + if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { + return "", fmt.Errorf("generate nonce for encryption err:%v", err) + } + + fileBytes, err := ioutil.ReadFile(filePath) + if err != nil { + return "", fmt.Errorf("fail to get the file") + } + + pk, sk, err := box.GenerateKey(rand.Reader) + encryptedBytes := box.Seal(nil, []byte(fileBytes), &nonce, &publicKey, sk) + + //return encryptedMessage, nil + encryptionData := map[string]interface{}{ + "version": "x25519-xsalsa20-poly1305", + "nonce": base64.StdEncoding.EncodeToString(nonce[:]), + "ephemPublicKey": base64.StdEncoding.EncodeToString(pk[:]), + "ciphertext": base64.StdEncoding.EncodeToString(encryptedBytes), + } + + encryptedData, err := json.Marshal(encryptionData) + if err != nil { + return "", fmt.Errorf("Json Marshal EncryptionData struct err:%v", err) + } + return hexutil.Encode(encryptedData), nil +} + // ******************************* Use dare ***************************************** // Encrypt reads from src until it encounters an io.EOF and encrypts all received data. From 2040278c56936d2194e11dcb42e16f6dda804c3d Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Tue, 28 Mar 2023 10:12:24 +0800 Subject: [PATCH 09/11] modify get-content api from get to post --- controller/get_content.go | 9 ++++----- controller/main.go | 2 +- docs/api.apib | 2 +- util/encrypt/encrypt.go | 1 - 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/controller/get_content.go b/controller/get_content.go index ef270e2..6c3ae2d 100644 --- a/controller/get_content.go +++ b/controller/get_content.go @@ -17,8 +17,8 @@ import ( ) type GetContentRequest struct { - ContentID int64 `json:"content_id"` - PublicKey string `json:"public_key"` + ContentID int64 `json:"content_id"` + EncryptionPublicKey string `json:"encryption_public_key"` } type GetContentResponse struct { @@ -32,7 +32,6 @@ func get_content(c *gin.Context) { req := GetContentRequest{} if err := c.BindJSON(&req); err != nil { - fmt.Println(req) errorResp(c, http.StatusBadRequest, xerrors.Errorf("Param error", err)) return } @@ -75,7 +74,7 @@ func get_content(c *gin.Context) { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) return } - encrypted_password, err = encrypt.EncryptPasswordWithEncryptionPublicKey(req.PublicKey, key.Password) + encrypted_password, err = encrypt.EncryptPasswordWithEncryptionPublicKey(req.EncryptionPublicKey, key.Password) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Fail to give the encrypted password: %w", err)) return @@ -86,7 +85,7 @@ func get_content(c *gin.Context) { return } } else { - encrypted_result, err = encrypt.EncryptFileWithEncryptionPublicKey(req.PublicKey, pathJoin(STORAGE, strconv.FormatInt(content.ID, 10), content.ContentName)) + encrypted_result, err = encrypt.EncryptFileWithEncryptionPublicKey(req.EncryptionPublicKey, pathJoin(STORAGE, strconv.FormatInt(content.ID, 10), content.ContentName)) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Fail to give the encrypted content: %w", err)) return diff --git a/controller/main.go b/controller/main.go index a1b2b95..3f8f649 100644 --- a/controller/main.go +++ b/controller/main.go @@ -40,8 +40,8 @@ func Init() { Engine.POST("/api/v1/create", create_record) Engine.POST("/api/v1/update", update_record) + Engine.POST("/api/v1/get-content", get_content) - Engine.GET("/api/v1/get-content", get_content) Engine.GET("/api/v1/content-info", content_info) //preview //Engine.GET("/api/v1/show-content", show_content) diff --git a/docs/api.apib b/docs/api.apib index 222df5d..e81e4bf 100644 --- a/docs/api.apib +++ b/docs/api.apib @@ -51,7 +51,7 @@ CreatorSuite Service provides create and get api to distribute content asset for After paying for a specific content asset, the buyer can call the get-content api to get the encrypted content asset + Parameters - + public_key(string) - the public key of the account that make the payment of content asset + + encryption_public_key(string) - the encryption public key, get from metamask API + content_id(number) - the content asset ID + Response 200 (application/json) diff --git a/util/encrypt/encrypt.go b/util/encrypt/encrypt.go index 1a25d76..2a495d8 100644 --- a/util/encrypt/encrypt.go +++ b/util/encrypt/encrypt.go @@ -62,7 +62,6 @@ func EncryptContentByPublicKey(filePath string, publicKey string) (string, error } func EncryptPasswordWithEncryptionPublicKey(encryptionPublicKey string, password string) (string, error) { - fmt.Printf("inside fun %s", encryptionPublicKey) publicKeyBytes, err := base64.StdEncoding.DecodeString(encryptionPublicKey) if err != nil { return "", fmt.Errorf("DecodeString EncryptionPublicKey err:%v", err) From a2e8e102548bb7f6786974e2a3738d9720312adc Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Thu, 13 Apr 2023 17:19:14 +0800 Subject: [PATCH 10/11] get-content encrypt process --- README.md | 9 ++++--- controller/get_content.go | 19 +++++++------- controller/update_record.go | 10 ++++++-- util/crypto_graphy.go | 50 +++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 util/crypto_graphy.go diff --git a/README.md b/README.md index f29ec0a..2e7a0e2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # CreatorSuite -CreatorSuite is designed to be a useful tool in Core Service to help content creator to distribute their content. +CreatorSuite is designed to be a useful tool in [Core Service](https://github.com/nextdotid/core_service_sharp) to help content creator to distribute their content. +Base on that, creators don't need any third-party to list and distribute their contents, instead, they have a self-hosted service to help them organize content asset so that they can 100% control their data by themselves. -## frontend -[frontend repo](https://github.com/NextDotID/creator_suite_fronted) + +## Frontend +docker-compose.yml includes the frontend service, [the source code is here.](https://github.com/NextDotID/creator_suite_frontend) ## Quick Start ```shell cp .env.example .env + docker-compose up docker-compose up --build // if you have any changes diff --git a/controller/get_content.go b/controller/get_content.go index 6c3ae2d..be12758 100644 --- a/controller/get_content.go +++ b/controller/get_content.go @@ -3,6 +3,7 @@ package controller import ( "fmt" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/nextdotid/creator_suite/util" "io/ioutil" "strconv" @@ -19,6 +20,8 @@ import ( type GetContentRequest struct { ContentID int64 `json:"content_id"` EncryptionPublicKey string `json:"encryption_public_key"` + Signature string `json:"signature"` + SignaturePayload string `json:"signature_payload"` } type GetContentResponse struct { @@ -36,15 +39,11 @@ func get_content(c *gin.Context) { return } - // - //fmt.Println(req.PublicKey) - //req.ContentID, _ = strconv.ParseInt(c.GetQuery("content_id"), 10, 64) - - //pub_key, err := util.StringToPublicKey(req.PublicKey) - //if err != nil { - // errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error, publicKey invalid: %w", err)) - // return - //} + _, err := util.ValidSignatureAndGetTheAddress(req.SignaturePayload, req.Signature) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error, publicKey invalid: %w", err)) + return + } content, err := model.FindContentByID(req.ContentID) if err != nil { @@ -60,7 +59,7 @@ func get_content(c *gin.Context) { return } - //is_paid, err := model.IsQualified(content.ManagedContract, crypto.PubkeyToAddress(*pub_key).String(), assetID) + //is_paid, err := model.IsQualified(content.ManagedContract, addr, assetID) //if !is_paid || err != nil { // errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Can't find any payment record: %w", err)) // return diff --git a/controller/update_record.go b/controller/update_record.go index ea579c2..7f0f88f 100644 --- a/controller/update_record.go +++ b/controller/update_record.go @@ -10,7 +10,7 @@ import ( type UpdateRecordRequest struct { ContentID int64 `json:"content_id"` - AssetID int64 `json:"asset_id"` + //AssetID int64 `json:"stat"` } type UpdateRecordResponse struct { @@ -25,7 +25,13 @@ func update_record(c *gin.Context) { return } - err := model.UpdateAssetID(req.ContentID, req.AssetID) + content, err := model.FindContentByID(req.ContentID) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) + c.JSON(http.StatusOK, UpdateRecordResponse{}) + return + } + err = content.UpdateToInvalidStatus(req.ContentID) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) c.JSON(http.StatusOK, UpdateRecordResponse{}) diff --git a/util/crypto_graphy.go b/util/crypto_graphy.go new file mode 100644 index 0000000..f8fc7d8 --- /dev/null +++ b/util/crypto_graphy.go @@ -0,0 +1,50 @@ +package util + +import ( + "crypto/ecdsa" + "fmt" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/xerrors" +) + +// ValidSignatureAndGetTheAddress +func ValidSignatureAndGetTheAddress(signaturePayload string, signature string) (string, error) { + signByte, err := hexutil.Decode(signature) + + if err != nil { + return "", err + } + publicKeyRecovered, err := RecoverPublicKeyFromPersonalSignature(signaturePayload, signByte) + if err != nil { + return "", xerrors.Errorf("%w", err) + } + return crypto.PubkeyToAddress(*publicKeyRecovered).String(), nil +} + +// RecoverPublicKeyFromPersonalSignature extract a public key from signature +func RecoverPublicKeyFromPersonalSignature(payload string, signature []byte) (publicKey *ecdsa.PublicKey, err error) { + // Recover pubkey from signature + if len(signature) != 65 { + return nil, xerrors.Errorf("Error: Signature length invalid: %d instead of 65", len(signature)) + } + if signature[64] == 27 || signature[64] == 28 { + signature[64] -= 27 + } + + if signature[64] != 0 && signature[64] != 1 { + return nil, xerrors.Errorf("Error: Signature Recovery ID not supported: %d", signature[64]) + } + + publicKeyRecovered, err := crypto.SigToPub(signPersonalHash([]byte(payload)), signature) + if err != nil { + return nil, xerrors.Errorf("Error when recovering publicKey from signature: %s", err.Error()) + } + + return publicKeyRecovered, nil +} + +func signPersonalHash(data []byte) []byte { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(msg)) +} From 96f6a82412551cd0063ff5c02ab861cfdf560f1f Mon Sep 17 00:00:00 2001 From: fengshanshan Date: Mon, 17 Apr 2023 15:30:27 +0800 Subject: [PATCH 11/11] get-content verify signature --- controller/get_content.go | 40 +++++++++++++++++++++++++++++++++------ util/crypto_graphy.go | 16 +++++++--------- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/controller/get_content.go b/controller/get_content.go index be12758..6bbc9e3 100644 --- a/controller/get_content.go +++ b/controller/get_content.go @@ -5,7 +5,9 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/nextdotid/creator_suite/util" "io/ioutil" + "regexp" "strconv" + "time" "github.com/gin-gonic/gin" "github.com/nextdotid/creator_suite/model" @@ -39,12 +41,18 @@ func get_content(c *gin.Context) { return } - _, err := util.ValidSignatureAndGetTheAddress(req.SignaturePayload, req.Signature) + recoveredAddr, err := util.ValidSignatureAndGetTheAddress(req.SignaturePayload, req.Signature) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Param error, publicKey invalid: %w", err)) return } + err = validateAddressAndTimestamp(req.SignaturePayload, recoveredAddr) + if err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Signature Verification error: %w", err)) + return + } + content, err := model.FindContentByID(req.ContentID) if err != nil { errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Error in DB: %w", err)) @@ -59,11 +67,11 @@ func get_content(c *gin.Context) { return } - //is_paid, err := model.IsQualified(content.ManagedContract, addr, assetID) - //if !is_paid || err != nil { - // errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Can't find any payment record: %w", err)) - // return - //} + is_paid, err := model.IsQualified(content.ManagedContract, recoveredAddr, assetID) + if !is_paid || err != nil { + errorResp(c, http.StatusInternalServerError, xerrors.Errorf("Can't find any payment record: %w", err)) + return + } var encrypted_result string var encrypted_password string @@ -110,3 +118,23 @@ func getContent(filePath string) (string, error) { } return hexutil.Encode(bytes), nil } + +func validateAddressAndTimestamp(signaturePayload string, address string) error { + reAcc := regexp.MustCompile("0x[a-fA-F0-9]{40}") + matchedAcc := reAcc.FindString(signaturePayload) + if matchedAcc != address { + return xerrors.New("sign account doesn't match") + } + + reTime := regexp.MustCompile(`timestamp:(\d+)`) + matchedTime := reTime.FindStringSubmatch(signaturePayload) + if len(matchedTime) < 2 { + return xerrors.New("cannot get timestamp information from signature payload") + } + signTime, err := strconv.ParseInt(matchedTime[1], 10, 64) + if err != nil || (time.Now().Unix()-signTime) > 60 { + return xerrors.New("sign account doesn't match") + } + + return nil +} diff --git a/util/crypto_graphy.go b/util/crypto_graphy.go index f8fc7d8..aebe800 100644 --- a/util/crypto_graphy.go +++ b/util/crypto_graphy.go @@ -15,15 +15,14 @@ func ValidSignatureAndGetTheAddress(signaturePayload string, signature string) ( if err != nil { return "", err } - publicKeyRecovered, err := RecoverPublicKeyFromPersonalSignature(signaturePayload, signByte) + publicKeyRecovered, err := RecoverPubkeyFromPersonalSignature(signaturePayload, signByte) if err != nil { return "", xerrors.Errorf("%w", err) } return crypto.PubkeyToAddress(*publicKeyRecovered).String(), nil } -// RecoverPublicKeyFromPersonalSignature extract a public key from signature -func RecoverPublicKeyFromPersonalSignature(payload string, signature []byte) (publicKey *ecdsa.PublicKey, err error) { +func RecoverPubkeyFromPersonalSignature(payload string, signature []byte) (pubkey *ecdsa.PublicKey, err error) { // Recover pubkey from signature if len(signature) != 65 { return nil, xerrors.Errorf("Error: Signature length invalid: %d instead of 65", len(signature)) @@ -35,16 +34,15 @@ func RecoverPublicKeyFromPersonalSignature(payload string, signature []byte) (pu if signature[64] != 0 && signature[64] != 1 { return nil, xerrors.Errorf("Error: Signature Recovery ID not supported: %d", signature[64]) } - - publicKeyRecovered, err := crypto.SigToPub(signPersonalHash([]byte(payload)), signature) + pubkeyRecovered, err := crypto.SigToPub(signPersonalHash([]byte(payload)), signature) if err != nil { - return nil, xerrors.Errorf("Error when recovering publicKey from signature: %s", err.Error()) + return nil, xerrors.Errorf("Error when recovering pubkey from signature: %s", err.Error()) } - return publicKeyRecovered, nil + return pubkeyRecovered, nil } func signPersonalHash(data []byte) []byte { - msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) - return crypto.Keccak256([]byte(msg)) + message := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(message)) }