From dde82cd37be969384b7ecb53a98a6c7d25c18768 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Tue, 8 Mar 2022 13:31:04 -0800 Subject: [PATCH] testing account configuration --- doc/api.oa3 | 4 +- net/server/internal/api_account.go | 5 - net/server/internal/api_addAccount.go | 2 +- net/server/internal/api_addAccountApp.go | 6 +- .../internal/api_addAccountAuthentication.go | 8 +- net/server/internal/api_addNodeAccount.go | 4 +- net/server/internal/api_getAccountStatus.go | 36 ++++++ net/server/internal/api_getChannelTopic.go | 2 +- net/server/internal/api_getChannelTopics.go | 2 +- net/server/internal/api_setAccountApp.go | 2 +- .../internal/api_setAccountAuthentication.go | 18 ++- net/server/internal/appValues.go | 9 +- net/server/internal/authUtil.go | 18 +-- net/server/internal/models.go | 4 +- net/server/internal/store/schema.go | 2 + net/server/internal/testApp.go | 20 +++- net/server/internal/testUtil.go | 2 +- net/server/internal/transcodeUtil.go | 6 + net/server/internal/ucAccountConfig_test.go | 111 ++++++++++++++++++ 19 files changed, 214 insertions(+), 47 deletions(-) create mode 100644 net/server/internal/api_getAccountStatus.go create mode 100644 net/server/internal/ucAccountConfig_test.go diff --git a/doc/api.oa3 b/doc/api.oa3 index fd4af4d5..5c837a6b 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -2979,10 +2979,10 @@ components: disabled: type: boolean storageUsed: - type: number + type: integer format: int64 storageAvailable: - type: number + type: integer format: int64 forwardingAddress: type: string diff --git a/net/server/internal/api_account.go b/net/server/internal/api_account.go index 853301a8..e6a69441 100644 --- a/net/server/internal/api_account.go +++ b/net/server/internal/api_account.go @@ -38,11 +38,6 @@ func GetAccountProfile(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func GetAccountStatus(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) -} - func RemoveAccount(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) diff --git a/net/server/internal/api_addAccount.go b/net/server/internal/api_addAccount.go index d6a31f81..7674f8ed 100644 --- a/net/server/internal/api_addAccount.go +++ b/net/server/internal/api_addAccount.go @@ -12,7 +12,7 @@ import ( func AddAccount(w http.ResponseWriter, r *http.Request) { token, res := BearerAccountToken(r); - if res != nil || token.TokenType != APP_ACCOUNTCREATE { + if res != nil || token.TokenType != APP_TOKENCREATE { ErrResponse(w, http.StatusUnauthorized, res) return } diff --git a/net/server/internal/api_addAccountApp.go b/net/server/internal/api_addAccountApp.go index 8ace1fa6..34245ab1 100644 --- a/net/server/internal/api_addAccountApp.go +++ b/net/server/internal/api_addAccountApp.go @@ -10,7 +10,7 @@ import ( func AddAccountApp(w http.ResponseWriter, r *http.Request) { - id, err := AccountLogin(r) + account, err := AccountLogin(r) if err != nil { ErrResponse(w, http.StatusUnauthorized, err) return @@ -24,8 +24,8 @@ func AddAccountApp(w http.ResponseWriter, r *http.Request) { token := hex.EncodeToString(data) accountToken := store.AccountToken{ - AccountID: id, - TokenType: APP_ACCOUNTATTACH, + AccountID: account.ID, + TokenType: APP_TOKENATTACH, Token: token, Expires: time.Now().Unix() + APP_ATTACHEXPIRE, } diff --git a/net/server/internal/api_addAccountAuthentication.go b/net/server/internal/api_addAccountAuthentication.go index 2fedaf8d..cc862346 100644 --- a/net/server/internal/api_addAccountAuthentication.go +++ b/net/server/internal/api_addAccountAuthentication.go @@ -10,13 +10,13 @@ import ( func AddAccountAuthentication(w http.ResponseWriter, r *http.Request) { - id, err := AccountLogin(r) + account, err := AccountLogin(r) if err != nil { ErrResponse(w, http.StatusUnauthorized, err) return } - data, res := securerandom.Bytes(4) + data, res := securerandom.Bytes(APP_RESETSIZE) if res != nil { ErrResponse(w, http.StatusInternalServerError, res) return @@ -24,8 +24,8 @@ func AddAccountAuthentication(w http.ResponseWriter, r *http.Request) { token := hex.EncodeToString(data) accountToken := store.AccountToken{ - AccountID: id, - TokenType: APP_ACCOUNTRESET, + AccountID: account.ID, + TokenType: APP_TOKENRESET, Token: token, Expires: time.Now().Unix() + APP_RESETEXPIRE, } diff --git a/net/server/internal/api_addNodeAccount.go b/net/server/internal/api_addNodeAccount.go index 90c74d47..d9f919e0 100644 --- a/net/server/internal/api_addNodeAccount.go +++ b/net/server/internal/api_addNodeAccount.go @@ -15,7 +15,7 @@ func AddNodeAccount(w http.ResponseWriter, r *http.Request) { return } - data, err := securerandom.Bytes(16) + data, err := securerandom.Bytes(APP_CREATESIZE) if err != nil { ErrResponse(w, http.StatusInternalServerError, err) return @@ -23,7 +23,7 @@ func AddNodeAccount(w http.ResponseWriter, r *http.Request) { token := hex.EncodeToString(data) accountToken := store.AccountToken{ - TokenType: "create", + TokenType: APP_TOKENCREATE, Token: token, Expires: time.Now().Unix() + APP_CREATEEXPIRE, }; diff --git a/net/server/internal/api_getAccountStatus.go b/net/server/internal/api_getAccountStatus.go new file mode 100644 index 00000000..0697f175 --- /dev/null +++ b/net/server/internal/api_getAccountStatus.go @@ -0,0 +1,36 @@ +package databag + +import ( + "net/http" + "databag/internal/store" +) + +func GetAccountStatus(w http.ResponseWriter, r *http.Request) { + + account, err := AccountLogin(r) + if err != nil { + ErrResponse(w, http.StatusUnauthorized, err) + return + } + + var assets []store.Asset; + if err = store.DB.Where("account_id = ?", account.ID).Find(&assets).Error; err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } + + // construct response + status := &AccountStatus{} + status.StorageAvailable = getNumConfigValue(CONFIG_STORAGE, 0); + for _, asset := range assets { + status.StorageUsed += asset.Size + } + status.Disabled = account.Disabled + status.ForwardingAddress = account.Forward + status.Searchable = account.Searchable + + WriteResponse(w, status) +} + + + diff --git a/net/server/internal/api_getChannelTopic.go b/net/server/internal/api_getChannelTopic.go index 7990ea67..82c6cd3d 100644 --- a/net/server/internal/api_getChannelTopic.go +++ b/net/server/internal/api_getChannelTopic.go @@ -22,7 +22,7 @@ func GetChannelTopic(w http.ResponseWriter, r *http.Request) { // load topic var topicSlot store.TopicSlot - if err = store.DB.Preload("Topic").Where("channel_id = ? AND topic_slot_id = ?", channelSlot.Channel.ID, topicId).First(&topicSlot).Error; err != nil { + if err = store.DB.Preload("Topic.Assets").Where("channel_id = ? AND topic_slot_id = ?", channelSlot.Channel.ID, topicId).First(&topicSlot).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { code = http.StatusNotFound } else { diff --git a/net/server/internal/api_getChannelTopics.go b/net/server/internal/api_getChannelTopics.go index d3ccd920..e4c6f94b 100644 --- a/net/server/internal/api_getChannelTopics.go +++ b/net/server/internal/api_getChannelTopics.go @@ -37,7 +37,7 @@ func GetChannelTopics(w http.ResponseWriter, r *http.Request) { } } else { var slots []store.TopicSlot - if err := store.DB.Preload("Topic").Where("channel_id = ?", channelSlot.Channel.ID).Find(&slots).Error; err != nil { + if err := store.DB.Preload("Topic.Assets").Where("channel_id = ?", channelSlot.Channel.ID).Find(&slots).Error; err != nil { ErrResponse(w, http.StatusInternalServerError, err) return } diff --git a/net/server/internal/api_setAccountApp.go b/net/server/internal/api_setAccountApp.go index 6e3be705..6a417c22 100644 --- a/net/server/internal/api_setAccountApp.go +++ b/net/server/internal/api_setAccountApp.go @@ -11,7 +11,7 @@ import ( func SetAccountApp(w http.ResponseWriter, r *http.Request) { token, res := BearerAccountToken(r); - if res != nil || token.TokenType != APP_ACCOUNTATTACH { + if res != nil || token.TokenType != APP_TOKENATTACH { ErrResponse(w, http.StatusUnauthorized, res) return } diff --git a/net/server/internal/api_setAccountAuthentication.go b/net/server/internal/api_setAccountAuthentication.go index e0a89226..e51c7fc0 100644 --- a/net/server/internal/api_setAccountAuthentication.go +++ b/net/server/internal/api_setAccountAuthentication.go @@ -3,13 +3,14 @@ package databag import ( "errors" "net/http" + "gorm.io/gorm" "databag/internal/store" ) func SetAccountAuthentication(w http.ResponseWriter, r *http.Request) { token, res := BearerAccountToken(r) - if res != nil || token.TokenType != APP_ACCOUNTRESET { + if res != nil || token.TokenType != APP_TOKENRESET { ErrResponse(w, http.StatusUnauthorized, res) return } @@ -19,14 +20,23 @@ func SetAccountAuthentication(w http.ResponseWriter, r *http.Request) { } username, password, ret := BasicCredentials(r) - if ret != nil { - ErrResponse(w, http.StatusUnauthorized, ret) + if ret != nil || username == "" || password == nil || len(password) == 0 { + ErrResponse(w, http.StatusBadRequest, errors.New("invalid credentials")) return } token.Account.Username = username; token.Account.Password = password; - if err := store.DB.Save(token.Account).Error; err != nil { + err := store.DB.Transaction(func(tx *gorm.DB) error { + if res := tx.Save(token.Account).Error; res != nil { + return res + } + if res := tx.Delete(token).Error; res != nil { + return res + } + return nil + }) + if err != nil { ErrResponse(w, http.StatusInternalServerError, err) return } diff --git a/net/server/internal/appValues.go b/net/server/internal/appValues.go index 5b8c6c5b..feff5d58 100644 --- a/net/server/internal/appValues.go +++ b/net/server/internal/appValues.go @@ -4,8 +4,11 @@ const APP_TOKENSIZE = 16 const APP_BODYLIMIT = 1048576 const APP_VERSION = "0.0.1" const APP_ATTACHEXPIRE = 300 +const APP_ATTACHSIZE = 4 const APP_CREATEEXPIRE = 86400 +const APP_CREATESIZE = 16 const APP_RESETEXPIRE = 86400 +const APP_RESETSIZE = 16 const APP_CONNECTEXPIRE = 30 const APP_KEYSIZE = 4096 const APP_RSA4096 = "RSA4096" @@ -27,6 +30,9 @@ const APP_NOTIFYCHANNEL = "channel" const APP_NOTIFYVIEW = "view" const APP_TOKENAPP = "app" const APP_TOKENCONTACT = "contact" +const APP_TOKENATTACH = "attach" +const APP_TOKENCREATE = "create" +const APP_TOKENRESET = "reset" const APP_NOTIFYBUFFER = 4096 const APP_TOPICUNCONFIRMED = "unconfirmed" const APP_TOPICCONFIRMED = "confirmed" @@ -41,9 +47,6 @@ const APP_QUEUEAUDIO = "audio" const APP_QUEUEVIDEO = "video" const APP_QUEUEPHOTO = "photo" const APP_QUEUEDEFAULT = "" -const APP_ACCOUNTATTACH = "attach" -const APP_ACCOUNTCREATE = "create" -const APP_ACCOUNTRESET = "reset" func AppCardStatus(status string) bool { if status == APP_CARDPENDING { diff --git a/net/server/internal/authUtil.go b/net/server/internal/authUtil.go index 89945426..3f14481b 100644 --- a/net/server/internal/authUtil.go +++ b/net/server/internal/authUtil.go @@ -11,12 +11,6 @@ import ( "databag/internal/store" ) -type accountLogin struct { - ID uint - Guid string - Password []byte -} - func AdminLogin(r *http.Request) error { // extract request auth @@ -44,26 +38,26 @@ func AdminLogin(r *http.Request) error { return nil } -func AccountLogin(r *http.Request) (uint, error) { +func AccountLogin(r *http.Request) (*store.Account, error) { // extract request auth username, password, ok := r.BasicAuth(); if !ok || username == "" || password == "" { - return 0, errors.New("invalid login") + return nil, errors.New("invalid login") } // find account - var account accountLogin + account := &store.Account{} if store.DB.Model(&store.Account{}).Where("Username = ?", username).First(&account).Error != nil { - return 0, errors.New("username not found"); + return nil, errors.New("username not found"); } // compare password if bcrypt.CompareHashAndPassword(account.Password, []byte(password)) != nil { - return 0, errors.New("invalid password"); + return nil, errors.New("invalid password"); } - return account.ID, nil + return account, nil } func BearerAccountToken(r *http.Request) (store.AccountToken, error) { diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 81b299c9..7d9de346 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -17,9 +17,9 @@ type AccountStatus struct { Disabled bool `json:"disabled"` - StorageUsed float64 `json:"storageUsed"` + StorageUsed int64 `json:"storageUsed"` - StorageAvailable float64 `json:"storageAvailable"` + StorageAvailable int64 `json:"storageAvailable"` ForwardingAddress string `json:"forwardingAddress"` diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index 2df64f05..d8444cd5 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -68,6 +68,8 @@ type Account struct { Created int64 `gorm:"autoCreateTime"` Updated int64 `gorm:"autoUpdateTime"` Disabled bool `gorm:"not null;default:false"` + Searchable bool `gorm:"not null;default:false"` + Forward string AccountDetail AccountDetail Apps []App } diff --git a/net/server/internal/testApp.go b/net/server/internal/testApp.go index b8783faf..f4b278c8 100644 --- a/net/server/internal/testApp.go +++ b/net/server/internal/testApp.go @@ -831,6 +831,8 @@ type TestApiParams struct { body interface{} tokenType string token string + authorization string + credentials string } type TestApiResponse struct { @@ -857,6 +859,12 @@ func TestApiRequest(endpoint func(http.ResponseWriter, *http.Request), params *T if params.token != "" { SetBearerAuth(r, params.token) } + if params.authorization != "" { + SetBasicAuth(r, params.authorization) + } + if params.credentials != "" { + SetCredentials(r, params.credentials) + } endpoint(w, r) res := w.Result() @@ -864,11 +872,13 @@ func TestApiRequest(endpoint func(http.ResponseWriter, *http.Request), params *T err = errors.New("response failed"); return } - resp.header = res.Header - if resp.data != nil { - dec := json.NewDecoder(res.Body) - if err = dec.Decode(resp.data); err != nil { - return + if resp != nil { + resp.header = res.Header + if resp.data != nil { + dec := json.NewDecoder(res.Body) + if err = dec.Decode(resp.data); err != nil { + return + } } } return diff --git a/net/server/internal/testUtil.go b/net/server/internal/testUtil.go index 976ffc3a..5e47d072 100644 --- a/net/server/internal/testUtil.go +++ b/net/server/internal/testUtil.go @@ -598,7 +598,7 @@ func AddTestAccount(username string) (guid string, token string, err error) { } // set account profile - if r, w, err = NewRequest("GET", "/account/profile", nil); err != nil { + if r, w, err = NewRequest("POST", "/account/profile", nil); err != nil { return } SetBearerAuth(r, access); diff --git a/net/server/internal/transcodeUtil.go b/net/server/internal/transcodeUtil.go index ec2e47cd..2ded3060 100644 --- a/net/server/internal/transcodeUtil.go +++ b/net/server/internal/transcodeUtil.go @@ -1,6 +1,7 @@ package databag import ( + "time" "os" "io" "hash/crc32" @@ -105,6 +106,8 @@ func transcodeAsset(asset *store.Asset) { cmd.Stdout = &stdout var stderr bytes.Buffer cmd.Stderr = &stderr +time.Sleep(time.Second); + if err := cmd.Run(); err != nil { LogMsg(stdout.String()) LogMsg(stderr.String()) @@ -149,6 +152,9 @@ func UpdateAsset(asset *store.Asset, status string, crc uint32, size int64) (err if res := tx.Model(&asset.Topic.TopicSlot).Update("revision", act.ChannelRevision + 1).Error; res != nil { return res } + if res := tx.Model(&asset.Channel).Update("topic_revision", act.ChannelRevision + 1).Error; res != nil { + return res + } if res := tx.Model(&asset.Channel.ChannelSlot).Update("revision", act.ChannelRevision + 1).Error; res != nil { return res } diff --git a/net/server/internal/ucAccountConfig_test.go b/net/server/internal/ucAccountConfig_test.go new file mode 100644 index 00000000..ffc7c5b1 --- /dev/null +++ b/net/server/internal/ucAccountConfig_test.go @@ -0,0 +1,111 @@ +package databag + +import ( + "testing" + "github.com/stretchr/testify/assert" + "encoding/json" + "encoding/base64" + "net/url" +) + +func TestAccountConfig(t *testing.T) { + var params *TestApiParams + var response *TestApiResponse + var channel *Channel + var topic *Topic + var assets *[]Asset + var subject *Subject + var pathParams *map[string]string + + // setup testing group + set, err := AddTestGroup("accountconfig") + assert.NoError(t, err) + + // allocate testing app + app := NewTestApp() + go app.Connect(set.A.Token) + + // asset to post + image := "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" + img, _ := base64.StdEncoding.DecodeString(image) + + // get reset token + var token string + params = &TestApiParams{ query: "/account/auth", authorization: "accountconfigA:pass" } + response = &TestApiResponse{ data: &token } + assert.NoError(t, TestApiRequest(AddAccountAuthentication, params, response)) + + // set reset token + params = &TestApiParams{ query: "/account/auth", tokenType: APP_TOKENRESET, token: token, credentials: "newguy:ssap" } + assert.NoError(t, TestApiRequest(SetAccountAuthentication, params, nil)) + + // fail getting reset token + params = &TestApiParams{ query: "/account/auth", authorization: "accountconfigA:pass" } + response = &TestApiResponse{ data: &token } + assert.Error(t, TestApiRequest(AddAccountAuthentication, params, response)) + + // create new channel + channel = &Channel{} + subject = &Subject{ Data: "channeldata", DataType: "channeldatatype" } + params = &TestApiParams{ query: "/content/channels", tokenType: APP_TOKENAPP, token: set.A.Token, body: subject } + response = &TestApiResponse{ data: channel } + assert.NoError(t, TestApiRequest(AddChannel, params, response)) + + // create new topic + topic = &Topic{} + subject = &Subject{ DataType: "topicdatatype", Data: "topicdata" } + params = &TestApiParams{ query: "/content/channels/{channelId}/topics", tokenType: APP_TOKENAPP, token: set.A.Token, + path: map[string]string{ "channelId": channel.Id }, body: subject } + response = &TestApiResponse{ data: topic } + assert.NoError(t, TestApiRequest(AddChannelTopic, params, response)) + + // add asset to topic + assets = &[]Asset{} + pathParams = &map[string]string{ "channelId": channel.Id, "topicId": topic.Id } + transforms, err := json.Marshal([]string{ "copy;photo", "copy;photo", }) + assert.NoError(t, err) + assert.NoError(t, ApiTestUpload(AddChannelTopicAsset, "POST", + "/content/channels/{channelId}/topics/{topicId}/assets?transforms=" + url.QueryEscape(string(transforms)), + pathParams, img, APP_TOKENAPP, set.A.Token, assets, nil)) + + // update topic + status := APP_TOPICCONFIRMED + params = &TestApiParams{ query: "/content/channels/{channelId}/topics/{topicId}", tokenType: APP_TOKENAPP, token: set.A.Token, + path: map[string]string{ "channelId": channel.Id, "topicId": topic.Id }, body: &status } + assert.NoError(t, TestApiRequest(SetChannelTopicConfirmed, params, nil)) + + // wait for assets + assert.NoError(t, app.WaitFor(func(testApp *TestApp)bool { + for _, testChannel := range testApp.channels { + if testChannel.channel.Id == channel.Id { + for _, testTopic := range testChannel.topics { + if testTopic.topic.Id == topic.Id { + detail := testTopic.topic.Data.TopicDetail + if detail.Status == APP_TOPICCONFIRMED && detail.Transform == APP_TRANSFORMCOMPLETE { + return true + } + } + } + } + } + + return false + })) + + // get account status + accountStatus := &AccountStatus{} + params = &TestApiParams{ query: "/account/status", authorization: "newguy:ssap", + path: map[string]string{ "channelId": channel.Id, "topicId": topic.Id } } + response = &TestApiResponse{ data: accountStatus } + assert.NoError(t, TestApiRequest(GetAccountStatus, params, response)) + + // add asset to topic + assets = &[]Asset{} + pathParams = &map[string]string{ "channelId": channel.Id, "topicId": topic.Id } + assert.Error(t, ApiTestUpload(AddChannelTopicAsset, "POST", + "/content/channels/{channelId}/topics/{topicId}/assets?transforms=" + url.QueryEscape(string(transforms)), + pathParams, img, APP_TOKENAPP, set.A.Token, assets, nil)) + +PrintMsg(accountStatus) + +}