Skip to content

Commit

Permalink
fixes and token
Browse files Browse the repository at this point in the history
  • Loading branch information
gi8lino committed Dec 14, 2023
1 parent e7af1bc commit 7128dc2
Show file tree
Hide file tree
Showing 27 changed files with 444 additions and 84 deletions.
4 changes: 4 additions & 0 deletions .vimbin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
server:
api:
address: http://localhost:8080
token: mytoken
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch file",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "main.go",
"args": [
"--config"
,"./.vimbin.yaml"
,"--trace"
]
}
]
}
8 changes: 4 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"os"
"time"
"vimbin/internal/config"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
Expand All @@ -28,7 +29,7 @@ import (
)

const (
version = "v0.0.4"
version = "v0.0.5"
)

var (
Expand Down Expand Up @@ -84,7 +85,7 @@ var rootCmd = &cobra.Command{
os.Exit(0)
} else {
// Display the root command's help message
cmd.Help()
_ = cmd.Help() // Make the linter happy
}
},
}
Expand Down Expand Up @@ -127,8 +128,7 @@ func initConfig() {

viper.AutomaticEnv() // Read in environment variables that match

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
if err := config.App.Read(viper.ConfigFileUsed()); err != nil {
log.Fatal().Msgf("Error reading config file: %v", err)
}
}
2 changes: 1 addition & 1 deletion cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ var serveCmd = &cobra.Command{

// Collect handlers and start the server
handlers.Collect()
server.Run(config.App.Server.Web.Address)
server.Run(config.App.Server.Web.Address, config.App.Server.Api.Token.Get())
},
}

Expand Down
2 changes: 1 addition & 1 deletion deploy/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ spec:
service:
name: vimbin
port:
name: vimbin
name: http
10 changes: 10 additions & 0 deletions internal/config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"os"
"path"
"vimbin/internal/utils"

"github.com/rs/zerolog/log"
)

// Parse reads and processes the configuration settings.
Expand Down Expand Up @@ -48,5 +50,13 @@ func (c *Config) Parse() (err error) {
return fmt.Errorf("Unable to extract hostname and port: %s", err)
}

// Check if the API token is valid
if c.Server.Api.Token.Get() == "" {
if err := c.Server.Api.Token.Generate(32); err != nil {
return fmt.Errorf("Unable to generate API token: %s", err)
}
log.Debug().Msgf("Generated API token: %s", c.Server.Api.Token.Get())
}

return nil
}
2 changes: 0 additions & 2 deletions internal/config/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,4 @@ func TestParse(t *testing.T) {
if cfg.Storage.Path != tempStoragePath {
t.Errorf("Storage path not set correctly. Expected: %s, Got: %s", tempStoragePath, cfg.Storage.Path)
}

// Add more assertions based on your specific requirements
}
8 changes: 6 additions & 2 deletions internal/config/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ func (c *Config) Read(configPath string) error {
return fmt.Errorf("Failed to read config file: %v", err)
}

// Unmarshal the config into the Config struct
if err := viper.Unmarshal(c, func(d *mapstructure.DecoderConfig) { d.ZeroFields = true }); err != nil {
if err := viper.Unmarshal(c, func(d *mapstructure.DecoderConfig) {
d.ZeroFields = true // Zero out any existing fields
d.DecodeHook = mapstructure.ComposeDecodeHookFunc(
customTokenDecodeHook, // Custom decoder hook for the Token field
)
}); err != nil {
return fmt.Errorf("Failed to unmarshal config file: %v", err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/config/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ server:
cfg := &Config{}
err = cfg.Read(filePath.Name())
assert.Error(t, err) // We expect an error because the file has incorrect content
assert.Contains(t, err.Error(), "Failed to unmarshal config file: 1 error(s) decoding:\n\n* cannot parse 'Server.Api.SkipInsecureVerify' as bool: strconv.ParseBool: parsing \"not_a_boolean\": invalid syntax")
assert.EqualError(t, err, "Failed to unmarshal config file: 1 error(s) decoding:\n\n* cannot parse 'server.api.skipInsecureVerify' as bool: strconv.ParseBool: parsing \"not_a_boolean\": invalid syntax")
})
}
75 changes: 60 additions & 15 deletions internal/config/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,92 @@ package config
import (
"sync"
"text/template"
"vimbin/internal/utils"
)

// App is the global configuration instance.
var App Config

// Config represents the application configuration.
type Config struct {
HtmlTemplate *template.Template `yaml:"-"` // HtmlTemplate contains the HTML template content.
Server Server `yaml:"server"` // Server represents the server configuration.
Storage Storage `yaml:"storage"` // Storage represents the storage configuration.
HtmlTemplate *template.Template `mapstructure:"-"` // HtmlTemplate contains the HTML template content.
Server Server `mapstructure:"server"` // Server represents the server configuration.
Storage Storage `mapstructure:"storage"` // Storage represents the storage configuration.
}

// Web represents the web configuration.
type Web struct {
Theme string `yaml:"server"` // Theme is the theme to use for the web interface.
Address string `yaml:"address"` // Address is the address to listen on for HTTP requests.
Theme string `mapstructure:"server"` // Theme is the theme to use for the web interface.
Address string `mapstructure:"address"` // Address is the address to listen on for HTTP requests.
}

// Token represents the API token.
type Token struct {
value string
}

// Get retrieves the current token value.
//
// Returns:
// - string
// The current token value.
func (t *Token) Get() string {
return t.value
}

// Set sets the token to the specified value.
//
// Parameters:
// - token: string
// The value to set as the token.
func (t *Token) Set(token string) {
t.value = token
}

// Generate generates a new random token of the specified length and updates the token value.
//
// Parameters:
// - len: int
// The length of the new token.
//
// Returns:
// - error
// An error, if any, encountered during token generation.
func (t *Token) Generate(len int) error {
tokenString, err := utils.GenerateRandomToken(len)
if err != nil {
return err
}
t.value = tokenString

return nil
}

// Api represents the api configuration.
type Api struct {
SkipInsecureVerify bool `yaml:"skipInsecureVerify"` // SkipInsecureVerify skips the verification of TLS certificates.
Address string `yaml:"address"` // Address is the address to push/fetch content from.
Token Token `mapstructure:"token"` // Token is the API token.
SkipInsecureVerify bool `mapstructure:"skipInsecureVerify"` // SkipInsecureVerify skips the verification of TLS certificates.
Address string `mapstructure:"address"` // Address is the address to push/fetch content from.
}

// Server represents the server configuration.
type Server struct {
Web Web `yaml:"web"` // Web represents the web configuration.
Api Api `yaml:"api"` // Api represents the api configuration.
Web Web `mapstructure:"web"` // Web represents the web configuration.
Api Api `mapstructure:"api"` // Api represents the api configuration.
}

// Storage represents the storage configuration.
type Storage struct {
Name string `yaml:"name"` // Name is the name of the storage file.
Directory string `yaml:"directory"` // Directory is the directory path for storage file.
Path string `yaml:"-"` // Path is the full path to the storage file.
Content Content `yaml:"-"` // Content represents the content stored in the storage file.
Name string `mapstructure:"name"` // Name is the name of the storage file.
Directory string `mapstructure:"directory"` // Directory is the directory path for storage file.
Path string `mapstructure:"-"` // Path is the full path to the storage file.
Content Content `mapstructure:"-"` // Content represents the content stored in the storage file.
}

// Content represents the content stored in the storage with thread-safe methods.
type Content struct {
text string `yaml:"-"` // text is the stored content.
mutext sync.RWMutex `yaml:"-"` // mutext is a read-write mutex for concurrent access control.
text string `mapstructure:"-"` // text is the stored content.
mutext sync.RWMutex `mapstructure:"-"` // mutext is a read-write mutex for concurrent access control.
}

// Set sets the content to the specified text.
Expand Down
36 changes: 36 additions & 0 deletions internal/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package config
import (
"fmt"
"os"
"reflect"

"github.com/mitchellh/mapstructure"
"github.com/rs/zerolog/log"
)

Expand Down Expand Up @@ -58,3 +60,37 @@ func checkStorageFile(filePath string) error {

return nil
}

// customTokenDecodeHook is a custom mapstructure DecodeHookFunc for decoding YAML data
// into the Token struct. It converts the data into a string and initializes a Token with it.
//
// Parameters:
// - from: reflect.Type
// The type of the source data.
// - to: reflect.Type
// The type of the target data.
// - data: interface{}
// The data to be decoded.
//
// Returns:
// - interface{}
// The decoded data.
// - error
// An error, if any, encountered during the decoding process.
func customTokenDecodeHook(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
// If the target type is not Token, return the data as is
if to != reflect.TypeOf(Token{}) {
return data, nil
}

var tokenValue string
// Decode the data into a string
if err := mapstructure.Decode(data, &tokenValue); err != nil {
return nil, fmt.Errorf("Unable to decode Token. %v", err)
}

// Initialize a Token with the decoded string
var token Token
token.Set(tokenValue)
return token, nil
}
40 changes: 40 additions & 0 deletions internal/config/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"os"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -60,3 +61,42 @@ func TestCheckStorageFile(t *testing.T) {
assert.Equal(t, err.Error(), "Unable to create storage file: open /non_existent_path/test_storage.txt: no such file or directory")
})
}

func TestCustomTokenDecodeHook(t *testing.T) {
t.Run("Decode hook converts string to Token successfully", func(t *testing.T) {
data := "mytoken"
fromType := reflect.TypeOf(data)
toType := reflect.TypeOf(Token{})

result, err := customTokenDecodeHook(fromType, toType, data)
assert.NoError(t, err)

// Check if the result is a Token with the correct value
token, ok := result.(Token)
assert.True(t, ok)
assert.Equal(t, "mytoken", token.Get())
})

t.Run("Decode hook passes through non-Token types", func(t *testing.T) {
data := 42
fromType := reflect.TypeOf(data)
toType := reflect.TypeOf(42)

result, err := customTokenDecodeHook(fromType, toType, data)
assert.NoError(t, err)

// Check if the result is the same as the input data
assert.Equal(t, data, result)
})

t.Run("Decode hook returns error for invalid Token value", func(t *testing.T) {
data := 42
fromType := reflect.TypeOf(data)
toType := reflect.TypeOf(Token{})

result, err := customTokenDecodeHook(fromType, toType, data)
assert.Error(t, err)
assert.Nil(t, result)
assert.EqualError(t, err, "Unable to decode Token. '' expected type 'string', got unconvertible type 'int', value: '42'")
})
}
2 changes: 1 addition & 1 deletion internal/handlers/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

func init() {
server.Register("/append", Append, "Append content to storage file", "POST")
server.Register("/append", "Append content to storage file", true, Append, "POST")
}

// Append handles HTTP requests for appending content to a file.
Expand Down
4 changes: 2 additions & 2 deletions internal/handlers/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func init() {
server.Register("/fetch", Fetch, "Fetch content from storage file", "GET")
server.Register("/fetch", "Fetch content from storage file", true, Fetch, "GET")
}

// Fetch handles HTTP requests for fetching content.
Expand All @@ -25,7 +25,7 @@ func init() {
// - r: *http.Request
// The HTTP request being processed.
func Fetch(w http.ResponseWriter, r *http.Request) {
LogRequest(r)
log.Trace().Msg(generateHTTPRequestLogEntry(r))

w.Header().Set("Content-Type", "application/text")

Expand Down
7 changes: 5 additions & 2 deletions internal/handlers/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"net/http"
"vimbin/internal/config"
"vimbin/internal/server"

"github.com/rs/zerolog/log"
)

func init() {
server.Register("/", Home, "Home site with editor", "GET")
server.Register("/", "Home site with editor", false, Home, "GET")
}

// Home handles HTTP requests for the home page.
Expand All @@ -22,11 +24,12 @@ func init() {
// - r: *http.Request
// The HTTP request being processed.
func Home(w http.ResponseWriter, r *http.Request) {
LogRequest(r)
log.Trace().Msg(generateHTTPRequestLogEntry(r))

page := Page{
Title: "vimbin - a pastebin with vim motion",
Content: config.App.Storage.Content.Get(),
Token: config.App.Server.Api.Token.Get(),
}

if err := config.App.HtmlTemplate.Execute(w, page); err != nil {
Expand Down
Loading

0 comments on commit 7128dc2

Please sign in to comment.