From a138378f199ffc7469648db75b52853e9fdd30d2 Mon Sep 17 00:00:00 2001 From: Laura Date: Sat, 16 Aug 2025 16:03:36 +0200 Subject: [PATCH] env tweaks --- env.go | 88 +++++++++++++++++++++++++++++++++++++++++++--- example.config.yml | 22 ++++++++---- go.mod | 1 + go.sum | 2 ++ 4 files changed, 103 insertions(+), 10 deletions(-) diff --git a/env.go b/env.go index 9c9c7c7..9337df5 100644 --- a/env.go +++ b/env.go @@ -1,13 +1,19 @@ package main import ( + "bytes" + "crypto/rand" + "encoding/base64" "errors" + "io" "os" "github.com/goccy/go-yaml" + "golang.org/x/crypto/bcrypt" ) type EnvTokens struct { + Secret string `json:"secret"` OpenRouter string `json:"openrouter"` Exa string `json:"exa"` } @@ -17,10 +23,21 @@ type EnvSettings struct { MaxIterations uint `json:"iterations"` } +type EnvUser struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type EnvAuthentication struct { + Enabled bool `json:"enabled"` + Users []EnvUser `json:"users"` +} + type Environment struct { - Debug bool `json:"debug"` - Tokens EnvTokens `json:"tokens"` - Settings EnvSettings `json:"settings"` + Debug bool `json:"debug"` + Tokens EnvTokens `json:"tokens"` + Settings EnvSettings `json:"settings"` + Authentication EnvAuthentication `json:"authentication"` } var env Environment @@ -46,6 +63,27 @@ func (e *Environment) Init() error { // check max iterations e.Settings.MaxIterations = max(e.Settings.MaxIterations, 1) + // check if server secret is set + if e.Tokens.Secret == "" { + log.Warning("Missing tokens.secret, generating new...") + + key := make([]byte, 32) + + _, err := io.ReadFull(rand.Reader, key) + if err != nil { + return err + } + + e.Tokens.Secret = base64.StdEncoding.EncodeToString(key) + + err = e.Store() + if err != nil { + return err + } + + log.Info("Stored new tokens.secret") + } + // check if openrouter token is set if e.Tokens.OpenRouter == "" { return errors.New("missing tokens.openrouter") @@ -53,8 +91,50 @@ func (e *Environment) Init() error { // check if exa token is set if e.Tokens.Exa == "" { - log.Warning("missing token.exa, web search unavailable") + log.Warning("Missing token.exa, web search unavailable") } return nil } + +func (e *Environment) Authenticate(username, password string) bool { + for _, user := range e.Authentication.Users { + if user.Username == username { + return bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) == nil + } + } + + return false +} + +func (e *Environment) Store() error { + var ( + buffer bytes.Buffer + comments = yaml.CommentMap{ + "$.debug": {yaml.HeadComment(" enable verbose logging and diagnostics")}, + + "$.tokens": {yaml.HeadComment("")}, + "$.settings": {yaml.HeadComment("")}, + "$.authentication": {yaml.HeadComment("")}, + + "$.tokens.secret": {yaml.HeadComment(" server secret for signing auth tokens; auto-generated if empty")}, + "$.tokens.openrouter": {yaml.HeadComment(" openrouter.ai api token (required)")}, + "$.tokens.exa": {yaml.HeadComment(" exa search api token (optional; used by search tools)")}, + + "$.settings.cleanup": {yaml.HeadComment(" normalize unicode in assistant output (optional; default: false)")}, + "$.settings.iterations": {yaml.HeadComment(" max model turns per request (optional; default: 3)")}, + + "$.authentication.enabled": {yaml.HeadComment(" require login with username and password")}, + "$.authentication.users": {yaml.HeadComment(" list of users with bcrypt password hashes")}, + } + ) + + err := yaml.NewEncoder(&buffer, yaml.WithComment(comments)).Encode(e) + if err != nil { + return err + } + + body := bytes.ReplaceAll(buffer.Bytes(), []byte("#\n"), []byte("\n")) + + return os.WriteFile("config.yml", body, 0644) +} diff --git a/example.config.yml b/example.config.yml index 037b29a..77d10fb 100644 --- a/example.config.yml +++ b/example.config.yml @@ -1,14 +1,24 @@ -# Enable verbose logging and extra diagnostics +# enable verbose logging and diagnostics debug: false tokens: - # Your openrouter.ai token (required) + # server secret for signing auth tokens; auto-generated if empty + secret: "" + # openrouter.ai api token (required) openrouter: "" - # Your exa-search token (optional, for search tools) + # exa search api token (optional; used by search tools) exa: "" settings: - # Replace unicode quotes, dashes, etc. in the assistants output (optional, default: false) + # normalize unicode in assistant output (optional; default: false) cleanup: true - # How many messages/tool calls before the model is cut off (optional, default: 3) - iterations: 3 \ No newline at end of file + # max model turns per request (optional; default: 3) + iterations: 3 + +authentication: + # require login with username and password + enabled: false + # list of users with bcrypt password hashes + users: + - username: admin + password: $2a$12$eH6Du2grC7aOUDmff2SrC.yKPWea/fq0d76c3JsvhGxhGCEOnWTRy diff --git a/go.mod b/go.mod index 4af87c0..bbfba9e 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-chi/chi/v5 v5.2.2 github.com/goccy/go-yaml v1.18.0 github.com/revrost/go-openrouter v0.2.1 + golang.org/x/crypto v0.38.0 ) require ( diff --git a/go.sum b/go.sum index a4c968c..f88992f 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=