From 7e824d19b33a46097f5af685ddbef290ce95f36f Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Thu, 10 Mar 2022 23:57:27 -0800 Subject: [PATCH] changing default asset path --- doc/api.oa3 | 7 --- net/server/internal/api_addAccount.go | 40 ++++++++++++++--- .../internal/api_addChannelTopicAsset.go | 2 +- .../internal/api_getAccountAvailable.go | 29 +++++++----- net/server/internal/api_getAccountUsername.go | 27 ++++++++---- .../internal/api_getChannelTopicAsset.go | 2 +- net/server/internal/api_removeAccount.go | 2 +- net/server/internal/api_removeNodeAccount.go | 2 +- net/server/internal/appValues.go | 1 + net/server/internal/authUtil.go | 8 ++-- net/server/internal/garbageUtil.go | 2 +- net/server/internal/transcodeUtil.go | 2 +- net/web/src/App.js | 44 ++++++++++++++++++- 13 files changed, 123 insertions(+), 45 deletions(-) diff --git a/doc/api.oa3 b/doc/api.oa3 index f08ff0b4..3a723e6c 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -273,13 +273,6 @@ paths: - account description: Check if any public accounts are available operationId: get-account-available - parameters: - - name: public - in: query - description: for open access accounts - required: false - schema: - type: boolean responses: '200': description: available public accounts diff --git a/net/server/internal/api_addAccount.go b/net/server/internal/api_addAccount.go index 7674f8ed..a1e3b491 100644 --- a/net/server/internal/api_addAccount.go +++ b/net/server/internal/api_addAccount.go @@ -2,6 +2,7 @@ package databag import ( "os" + "errors" "net/http" "crypto/sha256" "encoding/hex" @@ -10,11 +11,23 @@ import ( ) func AddAccount(w http.ResponseWriter, r *http.Request) { + var token *store.AccountToken - token, res := BearerAccountToken(r); - if res != nil || token.TokenType != APP_TOKENCREATE { - ErrResponse(w, http.StatusUnauthorized, res) - return + if r.Header.Get("Authorization") == "" { + if available, err := getAvailableAccounts(); err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } else if available == 0 { + ErrResponse(w, http.StatusForbidden, errors.New("no open accounts available")) + return + } + } else { + var err error + token, err = BearerAccountToken(r); + if err != nil || token.TokenType != APP_TOKENCREATE { + ErrResponse(w, http.StatusUnauthorized, err) + return + } } username, password, ret := BasicCredentials(r); @@ -23,6 +36,17 @@ func AddAccount(w http.ResponseWriter, r *http.Request) { return } + // check if username is taken + var count int64 + if err := store.DB.Model(&store.Account{}).Where("username = ?", username).Count(&count).Error; err != nil { + ErrResponse(w, http.StatusInternalServerError, err); + return + } + if count != 0 { + ErrResponse(w, http.StatusConflict, errors.New("username already taken")) + return + } + // generate account key privateKey, publicKey, keyType, err := GenerateRsaKeyPair() if err != nil { @@ -42,7 +66,7 @@ func AddAccount(w http.ResponseWriter, r *http.Request) { fingerprint := hex.EncodeToString(hash[:]) // create path for account data - path := getStrConfigValue(CONFIG_ASSETPATH, ".") + "/" + fingerprint + path := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) + "/" + fingerprint if err := os.Mkdir(path, os.ModePerm); err != nil { ErrResponse(w, http.StatusInternalServerError, err) } @@ -68,8 +92,10 @@ func AddAccount(w http.ResponseWriter, r *http.Request) { if res := tx.Create(&account).Error; res != nil { return res; } - if res := tx.Delete(token).Error; res != nil { - return res; + if token != nil { + if res := tx.Delete(token).Error; res != nil { + return res; + } } return nil; }); diff --git a/net/server/internal/api_addChannelTopicAsset.go b/net/server/internal/api_addChannelTopicAsset.go index e6b08289..1f8b7eae 100644 --- a/net/server/internal/api_addChannelTopicAsset.go +++ b/net/server/internal/api_addChannelTopicAsset.go @@ -70,7 +70,7 @@ func AddChannelTopicAsset(w http.ResponseWriter, r *http.Request) { // save new file id := uuid.New().String() - path := getStrConfigValue(CONFIG_ASSETPATH, ".") + "/" + channelSlot.Account.Guid + "/" + id + path := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) + "/" + channelSlot.Account.Guid + "/" + id if err := r.ParseMultipartForm(32 << 20); err != nil { ErrResponse(w, http.StatusBadRequest, err) return diff --git a/net/server/internal/api_getAccountAvailable.go b/net/server/internal/api_getAccountAvailable.go index c7cd97ed..f3b7eb93 100644 --- a/net/server/internal/api_getAccountAvailable.go +++ b/net/server/internal/api_getAccountAvailable.go @@ -7,21 +7,28 @@ import ( func GetAccountAvailable(w http.ResponseWriter, r *http.Request) { - public := r.FormValue("public") == "true" - open := getBoolConfigValue(CONFIG_OPENACCESS, true) - limit := getNumConfigValue(CONFIG_ACCOUNTLIMIT, 16) - - var count int64 - if err := store.DB.Model(&store.Account{}).Count(&count).Error; err != nil { + available, err := getAvailableAccounts() + if err != nil { ErrResponse(w, http.StatusInternalServerError, err) return } - var available int64 - if (!public || open) && limit > count { - available = limit - count - } - WriteResponse(w, &available) } +func getAvailableAccounts() (available int64, err error) { + + open := getBoolConfigValue(CONFIG_OPENACCESS, true) + limit := getNumConfigValue(CONFIG_ACCOUNTLIMIT, 16) + + var count int64 + if err = store.DB.Model(&store.Account{}).Count(&count).Error; err != nil { + return + } + + if open && limit > count { + available = limit - count + } + + return +} diff --git a/net/server/internal/api_getAccountUsername.go b/net/server/internal/api_getAccountUsername.go index e6060c76..4b9ae04a 100644 --- a/net/server/internal/api_getAccountUsername.go +++ b/net/server/internal/api_getAccountUsername.go @@ -1,6 +1,7 @@ package databag import ( + "errors" "net/http" "databag/internal/store" ) @@ -10,23 +11,33 @@ type accountUsername struct { } func GetAccountUsername(w http.ResponseWriter, r *http.Request) { + var token *store.AccountToken - token, err := BearerAccountToken(r); - if err != nil || (token.TokenType != "create" && token.TokenType != "reset") { - LogMsg("invalid token") - w.WriteHeader(http.StatusUnauthorized) - return + if r.Header.Get("Authorization") == "" { + if available, err := getAvailableAccounts(); err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } else if available == 0 { + ErrResponse(w, http.StatusUnauthorized, errors.New("no open accounts available")) + return + } + } else { + var err error + token, err = BearerAccountToken(r); + if err != nil || token.TokenType != APP_TOKENCREATE { + ErrResponse(w, http.StatusUnauthorized, err) + return + } } username := r.URL.Query().Get("username") if username == "" { - LogMsg("invalid username") - w.WriteHeader(http.StatusBadRequest) + ErrResponse(w, http.StatusBadRequest, errors.New("specify a username")) return } var accounts []accountUsername; - err = store.DB.Model(&store.Account{}).Where("username = ?", username).Find(&accounts).Error + err := store.DB.Model(&store.Account{}).Where("username = ?", username).Find(&accounts).Error if err != nil { LogMsg("failed to query accounts") w.WriteHeader(http.StatusInternalServerError) diff --git a/net/server/internal/api_getChannelTopicAsset.go b/net/server/internal/api_getChannelTopicAsset.go index 137201eb..44127e8c 100644 --- a/net/server/internal/api_getChannelTopicAsset.go +++ b/net/server/internal/api_getChannelTopicAsset.go @@ -42,7 +42,7 @@ func GetChannelTopicAsset(w http.ResponseWriter, r *http.Request) { } // construct file path - path := getStrConfigValue(CONFIG_ASSETPATH, ".") + "/" + act.Guid + "/" + asset.AssetId + path := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) + "/" + act.Guid + "/" + asset.AssetId http.ServeFile(w, r, path) } diff --git a/net/server/internal/api_removeAccount.go b/net/server/internal/api_removeAccount.go index 53402802..ffd39081 100644 --- a/net/server/internal/api_removeAccount.go +++ b/net/server/internal/api_removeAccount.go @@ -78,7 +78,7 @@ func RemoveAccount(w http.ResponseWriter, r *http.Request) { } // delete asset files - path := getStrConfigValue(CONFIG_ASSETPATH, ".") + "/" + account.Guid + path := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) + "/" + account.Guid if err = os.RemoveAll(path); err != nil { ErrMsg(err) } diff --git a/net/server/internal/api_removeNodeAccount.go b/net/server/internal/api_removeNodeAccount.go index 83835107..caf0d6b2 100644 --- a/net/server/internal/api_removeNodeAccount.go +++ b/net/server/internal/api_removeNodeAccount.go @@ -98,7 +98,7 @@ func RemoveNodeAccount(w http.ResponseWriter, r *http.Request) { } // delete asset files - path := getStrConfigValue(CONFIG_ASSETPATH, ".") + "/" + account.Guid + path := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) + "/" + account.Guid if err = os.RemoveAll(path); err != nil { ErrMsg(err) } diff --git a/net/server/internal/appValues.go b/net/server/internal/appValues.go index feff5d58..de737a37 100644 --- a/net/server/internal/appValues.go +++ b/net/server/internal/appValues.go @@ -47,6 +47,7 @@ const APP_QUEUEAUDIO = "audio" const APP_QUEUEVIDEO = "video" const APP_QUEUEPHOTO = "photo" const APP_QUEUEDEFAULT = "" +const APP_DEFAULTPATH = "./asset" func AppCardStatus(status string) bool { if status == APP_CARDPENDING { diff --git a/net/server/internal/authUtil.go b/net/server/internal/authUtil.go index 3f14481b..2f5e564c 100644 --- a/net/server/internal/authUtil.go +++ b/net/server/internal/authUtil.go @@ -60,7 +60,7 @@ func AccountLogin(r *http.Request) (*store.Account, error) { return account, nil } -func BearerAccountToken(r *http.Request) (store.AccountToken, error) { +func BearerAccountToken(r *http.Request) (*store.AccountToken, error) { // parse bearer authentication auth := r.Header.Get("Authorization") @@ -69,12 +69,12 @@ func BearerAccountToken(r *http.Request) (store.AccountToken, error) { // find token record var accountToken store.AccountToken if err := store.DB.Preload("Account").Where("token = ?", token).First(&accountToken).Error; err != nil { - return accountToken, err + return nil, err } if accountToken.Expires < time.Now().Unix() { - return accountToken, errors.New("expired token") + return nil, errors.New("expired token") } - return accountToken, nil + return &accountToken, nil } func BearerAppToken(r *http.Request, detail bool) (*store.Account, int, error) { diff --git a/net/server/internal/garbageUtil.go b/net/server/internal/garbageUtil.go index e8cdd403..64a95037 100644 --- a/net/server/internal/garbageUtil.go +++ b/net/server/internal/garbageUtil.go @@ -13,7 +13,7 @@ func garbageCollect(act *store.Account) { defer garbageSync.Unlock() // get all asset files - dir := getStrConfigValue(CONFIG_ASSETPATH, ".") + "/" + act.Guid + dir := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) + "/" + act.Guid files, err := os.ReadDir(dir) if err != nil { ErrMsg(err) diff --git a/net/server/internal/transcodeUtil.go b/net/server/internal/transcodeUtil.go index 2ded3060..82a22826 100644 --- a/net/server/internal/transcodeUtil.go +++ b/net/server/internal/transcodeUtil.go @@ -89,7 +89,7 @@ func transcodeDefault() { func transcodeAsset(asset *store.Asset) { // prepare script path - data := getStrConfigValue(CONFIG_ASSETPATH, ".") + data := getStrConfigValue(CONFIG_ASSETPATH, APP_DEFAULTPATH) script := getStrConfigValue(CONFIG_SCRIPTPATH, ".") re := regexp.MustCompile("^[a-zA-Z0-9_]*$") diff --git a/net/web/src/App.js b/net/web/src/App.js index f0fda75a..b3783be0 100644 --- a/net/web/src/App.js +++ b/net/web/src/App.js @@ -1,14 +1,54 @@ -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' import login from './login.png'; import { Input, Button } from 'antd'; import { UserOutlined, LockOutlined } from '@ant-design/icons'; import 'antd/dist/antd.css'; +const FETCH_TIMEOUT = 15000; + +function checkResponse(response) { + if(response.status >= 400 && response.status < 600) { + throw new Error(response.url + " failed"); + } +} + +async function fetchWithTimeout(url, options) { + return Promise.race([ + fetch(url, options).catch(err => { throw new Error(url + ' failed'); }), + new Promise((_, reject) => setTimeout(() => reject(new Error(url + ' timeout')), FETCH_TIMEOUT)) + ]); +} + +async function getAvailable() { + let available = await fetchWithTimeout("/account/available", { method: 'GET', timeout: FETCH_TIMEOUT } ); + checkResponse(available); + return await available.json() +} + function App() { + const [available, setAvailable] = useState(0) const [username, setUsername] = useState('') const [password, setPassword] = useState('') + useEffect(() => { + + getAvailable().then(a => { + setAvailable(a) + console.log(a) + }).catch(err => { + console.log(err) + }) + + }, []) + + const Create = () => { + if (available > 0) { + return + } + return <> + } + const onLogin = () => { console.log(username) console.log(password) @@ -31,7 +71,7 @@ function App() { setPassword(e.target.value)} placeholder="password" prefix={} style={{ marginTop: '16px' }} /> - + );