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' }} />
-
+
);