testing node configuration

This commit is contained in:
Roland Osborne 2022-01-12 13:12:40 -08:00
parent 000d98d934
commit 9debd23bf2
9 changed files with 192 additions and 29 deletions

View File

@ -124,11 +124,6 @@ paths:
description: permission denide description: permission denide
'500': '500':
description: internal server error description: internal server error
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/NodeConfig'
/admin/accounts: /admin/accounts:
get: get:
@ -3595,6 +3590,10 @@ components:
NodeConfig: NodeConfig:
type: object type: object
required:
- domain
- publicLimit
- accountStorage
properties: properties:
domain: domain:
type: string type: string

View File

@ -7,6 +7,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.3 // indirect github.com/jinzhu/now v1.1.3 // indirect
github.com/mattn/go-sqlite3 v1.14.9 // indirect github.com/mattn/go-sqlite3 v1.14.9 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
gorm.io/driver/sqlite v1.2.6 // indirect gorm.io/driver/sqlite v1.2.6 // indirect
gorm.io/gorm v1.22.4 // indirect gorm.io/gorm v1.22.4 // indirect
) )

View File

@ -12,6 +12,8 @@ github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4= gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=

View File

@ -10,10 +10,12 @@
package databag package databag
import ( import (
"errors" "log"
"encoding/json"
"net/http" "net/http"
"gorm.io/gorm" "gorm.io/gorm"
store "databag/internal/store" "golang.org/x/crypto/bcrypt"
"databag/internal/store"
) )
func AddNodeAccount(w http.ResponseWriter, r *http.Request) { func AddNodeAccount(w http.ResponseWriter, r *http.Request) {
@ -32,14 +34,11 @@ func GetNodeAccounts(w http.ResponseWriter, r *http.Request) {
} }
func GetNodeClaimable(w http.ResponseWriter, r *http.Request) { func GetNodeClaimable(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var config store.Config if _configured {
err := store.DB.Where("config_id = ?", CONFIG_CLAIMED).First(&config).Error;
if errors.Is(err, gorm.ErrRecordNotFound) {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusNotAcceptable) w.WriteHeader(http.StatusNotAcceptable)
} else {
w.WriteHeader(http.StatusOK)
} }
} }
@ -59,11 +58,97 @@ func SetNodeAccount(w http.ResponseWriter, r *http.Request) {
} }
func SetNodeClaim(w http.ResponseWriter, r *http.Request) { func SetNodeClaim(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
// confirm node is claimable
if _configured {
w.WriteHeader(http.StatusUnauthorized)
return
}
// extract credentials
username, password, ok := r.BasicAuth();
if !ok {
w.WriteHeader(http.StatusBadRequest)
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
log.Printf("SetNodeClaim - failed to hash password");
w.WriteHeader(http.StatusInternalServerError)
return
}
// store credentials
err = store.DB.Transaction(func(tx *gorm.DB) error {
if res := tx.Create(&store.Config{ConfigId: CONFIG_USERNAME, StrValue: username}).Error; res != nil {
return res
}
if res := tx.Create(&store.Config{ConfigId: CONFIG_PASSWORD, BinValue: hashedPassword}).Error; res != nil {
return res
}
return nil;
})
if(err != nil) {
log.Printf("SetNodeCalim - failed to store credentials");
w.WriteHeader(http.StatusInternalServerError)
return
}
// set global values
_adminUsername = username
_adminPassword = hashedPassword
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func SetNodeConfig(w http.ResponseWriter, r *http.Request) { func SetNodeConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
// validate admin password
username, password, ok := r.BasicAuth();
if !ok {
w.WriteHeader(http.StatusBadRequest)
return
}
if username != _adminUsername || bcrypt.CompareHashAndPassword(_adminPassword, []byte(password)) != nil {
log.Printf("SetNodeConfig - invalid admin credentials");
w.WriteHeader(http.StatusUnauthorized);
return
}
// parse node config
r.Body = http.MaxBytesReader(w, r.Body, CONFIG_BODYLIMIT)
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
var config NodeConfig;
res := dec.Decode(&config);
if res != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// store credentials
err := store.DB.Transaction(func(tx *gorm.DB) error {
if res := tx.Create(&store.Config{ConfigId: CONFIG_DOMAIN, StrValue: config.Domain}).Error; res != nil {
return res
}
if res := tx.Create(&store.Config{ConfigId: CONFIG_PUBLICLIMIT, NumValue: config.PublicLimit}).Error; res != nil {
return res
}
if res := tx.Create(&store.Config{ConfigId: CONFIG_STORAGE, NumValue: config.AccountStorage}).Error; res != nil {
return res
}
return nil;
})
if(err != nil) {
log.Printf("SetNodeConfig - failed to store config");
w.WriteHeader(http.StatusInternalServerError)
return
}
// set global values
_nodeDomain = config.Domain
_publicLimit = config.PublicLimit
_accountStorage = config.AccountStorage
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }

View File

@ -1,7 +1,10 @@
package databag package databag
const CONFIG_CLAIMED = "claimed"; const CONFIG_BODYLIMIT = 1048576
const CONFIG_CONFIGURED = "configured"
const CONFIG_USERNAME = "username" const CONFIG_USERNAME = "username"
const CONFIG_PASSWORD = "password" const CONFIG_PASSWORD = "password"
const CONFIG_SATL = "salt" const CONFIG_DOMAIN = "domain"
const CONFIG_PUBLICLIMIT = "public_limit"
const CONFIG_STORAGE = "storage"

View File

@ -0,0 +1,50 @@
package databag
import (
"errors"
"gorm.io/gorm"
"databag/internal/store"
)
var _configured bool
var _adminUsername string
var _adminPassword []byte
var _nodeDomain string
var _accountStorage int64
var _publicLimit int64
func getStrConfigValue(configId string, empty string) string {
var config store.Config
err := store.DB.Where("config_id = ?", config).First(&config).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return empty
}
return config.StrValue
}
func getNumConfigValue(configId string, empty int64) int64 {
var config store.Config
err := store.DB.Where("config_id = ?", config).First(&config).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return empty
}
return config.NumValue
}
func getBoolConfigValue(configId string, empty bool) bool {
var config store.Config
err := store.DB.Where("config_id = ?", config).First(&config).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return empty
}
return config.BoolValue
}
func getBinConfigValue(configId string, empty []byte) []byte {
var config store.Config
err := store.DB.Where("config_id = ?", config).First(&config).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return empty
}
return config.BinValue
}

View File

@ -27,6 +27,14 @@ type Routes []Route
func NewRouter() *mux.Router { func NewRouter() *mux.Router {
// populate context
_configured = getBoolConfigValue(CONFIG_CONFIGURED, false);
_adminUsername = getStrConfigValue(CONFIG_USERNAME, "");
_adminPassword = getBinConfigValue(CONFIG_PASSWORD, nil);
_nodeDomain = getStrConfigValue(CONFIG_DOMAIN, "");
_publicLimit = getNumConfigValue(CONFIG_PUBLICLIMIT, 0);
_accountStorage = getNumConfigValue(CONFIG_STORAGE, 0);
router := mux.NewRouter().StrictSlash(true) router := mux.NewRouter().StrictSlash(true)
for _, route := range routes { for _, route := range routes {
var handler http.Handler var handler http.Handler

View File

@ -31,6 +31,7 @@ type Config struct {
StrValue string StrValue string
NumValue int64 NumValue int64
BoolValue bool BoolValue bool
BinValue []byte
} }
type AccountToken struct { type AccountToken struct {
@ -46,8 +47,7 @@ type Account struct {
ID uint `gorm:"primaryKey;not null;unique;autoIncrement"` ID uint `gorm:"primaryKey;not null;unique;autoIncrement"`
Did string `gorm:"not null"` Did string `gorm:"not null"`
Username string `gorm:"not null;uniqueIndex"` Username string `gorm:"not null;uniqueIndex"`
Password string `gorm:"not null"` Password []byte `gorm:"not null"`
Salt string `gorm:"not null"`
Name string Name string
Description string Description string
Location string Location string

View File

@ -1,38 +1,53 @@
package main package main
import ( import (
"fmt" "strings"
"testing" "testing"
"net/http/httptest" "net/http/httptest"
"encoding/base64" "encoding/base64"
"encoding/json"
app "databag/internal" app "databag/internal"
store "databag/internal/store" "databag/internal/store"
) )
func TestSetup(t *testing.T) { func TestSetup(t *testing.T) {
store.SetPath("file::memory:?cache=shared"); store.SetPath("file::memory:?cache=shared");
//store.SetPath("databag.db");
Claimable(t); Claimable(t);
Claim(t); Claim(t);
Config(t);
} }
func Claimable(t *testing.T) { func Claimable(t *testing.T) {
r := httptest.NewRequest("GET", "/claimable", nil) r := httptest.NewRequest("GET", "/admin/claimable", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
app.GetNodeClaimable(w, r); app.GetNodeClaimable(w, r)
if w.Code != 200 { if w.Code != 200 {
t.Errorf("server not initially claimable"); t.Errorf("server not initially claimable")
} }
} }
func Claim(t *testing.T) { func Claim(t *testing.T) {
auth := base64.StdEncoding.EncodeToString([]byte("admin:pass")) auth := base64.StdEncoding.EncodeToString([]byte("admin:pass"))
r := httptest.NewRequest("GET", "/claimable", nil) r := httptest.NewRequest("PUT", "/admin/claim", nil)
r.Header.Add("Authorization","Basic " + auth) r.Header.Add("Authorization","Basic " + auth)
w := httptest.NewRecorder() w := httptest.NewRecorder()
app.GetNodeClaimable(w, r); app.SetNodeClaim(w, r)
if w.Code != 200 { if w.Code != 200 {
t.Errorf("server not initially claimable"); t.Errorf("server not initially claimable")
}
}
func Config(t *testing.T) {
config := app.NodeConfig{Domain: "example.com", PublicLimit: 1024, AccountStorage: 4096}
auth := base64.StdEncoding.EncodeToString([]byte("admin:pass"))
body,_ := json.Marshal(config)
r := httptest.NewRequest("PUT", "/admin/config", strings.NewReader(string(body)))
r.Header.Add("Authorization","Basic " + auth)
w := httptest.NewRecorder()
app.SetNodeConfig(w, r);
if w.Code != 200 {
t.Errorf("failed to set node config")
} }
} }