Browse Source

Switched from viper to something selfmade: fix #44

dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
70d8ddb5dc
  1. 20
      build/config.yaml
  2. 16
      handlers/auth.go
  3. 5
      handlers/auth/github.go
  4. 5
      handlers/auth/google.go
  5. 5
      handlers/auth/microsoft.go
  6. 3
      handlers/auth_test.go
  7. 6
      handlers/handlers.go
  8. 4
      main.go
  9. 4
      main_test.go
  10. 5
      store/store.go
  11. 19
      store/store_test.go
  12. 134
      util/config.go
  13. 6
      util/config_test.go
  14. 2
      util/private.go
  15. 2
      util/private_test.go

20
build/config.yaml

@ -1,8 +1,14 @@
listen_addr: ':8080' # Consists of 'IP:Port', e.g. ':8080' listens on any IP and on Port 8080 ListenAddr: ':8080' # Consists of 'IP:Port', e.g. ':8080' listens on any IP and on Port 8080
base_url: 'http://localhost:3000' # Origin URL, required for the authentication via OAuth BaseURL: 'http://localhost:3000' # Origin URL, required for the authentication via OAuth
data_dir: ./data # Contains: the database and the private key DataDir: ./data # Contains: the database and the private key
enable_debug_mode: true # Activates more detailed logging EnableDebugMode: true # Activates more detailed logging
shorted_id_length: 4 # Length of the random generated ID which is used for new shortened URLs ShortedIDLength: 10 # Length of the random generated ID which is used for new shortened URLs
Google: Google:
ClientID: replace me # ClientID which you get from console.cloud.google.com ClientID: replace me
ClientSecret: replace me # ClientSecret which get from console.cloud.google.com ClientSecret: replace me
GitHub:
ClientID: replace me
ClientSecret: replace me
Microsoft:
ClientID: replace me
ClientSecret: 'replace me'

16
handlers/auth.go

@ -6,7 +6,6 @@ import (
"github.com/maxibanki/golang-url-shortener/handlers/auth" "github.com/maxibanki/golang-url-shortener/handlers/auth"
"github.com/maxibanki/golang-url-shortener/util" "github.com/maxibanki/golang-url-shortener/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/contrib/sessions"
@ -18,16 +17,19 @@ func (h *Handler) initOAuth() {
h.engine.Use(sessions.Sessions("backend", sessions.NewCookieStore(util.GetPrivateKey()))) h.engine.Use(sessions.Sessions("backend", sessions.NewCookieStore(util.GetPrivateKey())))
h.providers = []string{} h.providers = []string{}
if viper.GetString("Google.ClientSecret") != "" { google := util.GetConfig().Google
auth.WithAdapterWrapper(auth.NewGoogleAdapter(viper.GetString("Google.ClientID"), viper.GetString("Google.ClientSecret"), viper.GetString("base_url")), h.engine.Group("/api/v1/auth/google")) if google.Enabled() {
auth.WithAdapterWrapper(auth.NewGoogleAdapter(google.ClientID, google.ClientSecret), h.engine.Group("/api/v1/auth/google"))
h.providers = append(h.providers, "google") h.providers = append(h.providers, "google")
} }
if viper.GetString("GitHub.ClientSecret") != "" { github := util.GetConfig().GitHub
auth.WithAdapterWrapper(auth.NewGithubAdapter(viper.GetString("GitHub.ClientID"), viper.GetString("GitHub.ClientSecret"), viper.GetString("base_url")), h.engine.Group("/api/v1/auth/github")) if github.Enabled() {
auth.WithAdapterWrapper(auth.NewGithubAdapter(github.ClientID, github.ClientSecret), h.engine.Group("/api/v1/auth/github"))
h.providers = append(h.providers, "github") h.providers = append(h.providers, "github")
} }
if viper.GetString("Microsoft.ClientSecret") != "" { microsoft := util.GetConfig().Microsoft
auth.WithAdapterWrapper(auth.NewMicrosoftAdapter(viper.GetString("Microsoft.ClientID"), viper.GetString("Microsoft.ClientSecret"), viper.GetString("base_url")), h.engine.Group("/api/v1/auth/microsoft")) if microsoft.Enabled() {
auth.WithAdapterWrapper(auth.NewMicrosoftAdapter(microsoft.ClientID, microsoft.ClientSecret), h.engine.Group("/api/v1/auth/microsoft"))
h.providers = append(h.providers, "microsoft") h.providers = append(h.providers, "microsoft")
} }

5
handlers/auth/github.go

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/maxibanki/golang-url-shortener/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/oauth2/github" "golang.org/x/oauth2/github"
@ -17,11 +18,11 @@ type githubAdapter struct {
} }
// NewGithubAdapter creates an oAuth adapter out of the credentials and the baseURL // NewGithubAdapter creates an oAuth adapter out of the credentials and the baseURL
func NewGithubAdapter(clientID, clientSecret, baseURL string) Adapter { func NewGithubAdapter(clientID, clientSecret string) Adapter {
return &githubAdapter{&oauth2.Config{ return &githubAdapter{&oauth2.Config{
ClientID: clientID, ClientID: clientID,
ClientSecret: clientSecret, ClientSecret: clientSecret,
RedirectURL: baseURL + "/api/v1/auth/github/callback", RedirectURL: util.GetConfig().BaseURL + "/api/v1/auth/github/callback",
Scopes: []string{ Scopes: []string{
"(no scope)", "(no scope)",
}, },

5
handlers/auth/google.go

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/maxibanki/golang-url-shortener/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
@ -14,11 +15,11 @@ type googleAdapter struct {
} }
// NewGoogleAdapter creates an oAuth adapter out of the credentials and the baseURL // NewGoogleAdapter creates an oAuth adapter out of the credentials and the baseURL
func NewGoogleAdapter(clientID, clientSecret, baseURL string) Adapter { func NewGoogleAdapter(clientID, clientSecret string) Adapter {
return &googleAdapter{&oauth2.Config{ return &googleAdapter{&oauth2.Config{
ClientID: clientID, ClientID: clientID,
ClientSecret: clientSecret, ClientSecret: clientSecret,
RedirectURL: baseURL + "/api/v1/auth/google/callback", RedirectURL: util.GetConfig().BaseURL + "/api/v1/auth/google/callback",
Scopes: []string{ Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.email",
}, },

5
handlers/auth/microsoft.go

@ -7,6 +7,7 @@ import (
"golang.org/x/oauth2/microsoft" "golang.org/x/oauth2/microsoft"
"github.com/maxibanki/golang-url-shortener/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -18,11 +19,11 @@ type microsoftAdapter struct {
} }
// NewMicrosoftAdapter creates an oAuth adapter out of the credentials and the baseURL // NewMicrosoftAdapter creates an oAuth adapter out of the credentials and the baseURL
func NewMicrosoftAdapter(clientID, clientSecret, baseURL string) Adapter { func NewMicrosoftAdapter(clientID, clientSecret string) Adapter {
return &microsoftAdapter{&oauth2.Config{ return &microsoftAdapter{&oauth2.Config{
ClientID: clientID, ClientID: clientID,
ClientSecret: clientSecret, ClientSecret: clientSecret,
RedirectURL: baseURL + "/api/v1/auth/microsoft/callback", RedirectURL: util.GetConfig().BaseURL + "/api/v1/auth/microsoft/callback",
Scopes: []string{ Scopes: []string{
"wl.basic", "wl.basic",
}, },

3
handlers/auth_test.go

@ -15,7 +15,6 @@ import (
"github.com/maxibanki/golang-url-shortener/store" "github.com/maxibanki/golang-url-shortener/store"
"github.com/maxibanki/golang-url-shortener/util" "github.com/maxibanki/golang-url-shortener/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/viper"
) )
var ( var (
@ -36,8 +35,6 @@ var (
func TestCreateBackend(t *testing.T) { func TestCreateBackend(t *testing.T) {
secret = util.GetPrivateKey() secret = util.GetPrivateKey()
viper.SetConfigName("test")
util.DoNotSetConfigName = true
if err := util.ReadInConfig(); err != nil { if err := util.ReadInConfig(); err != nil {
t.Fatalf("could not reload config file: %v", err) t.Fatalf("could not reload config file: %v", err)
} }

6
handlers/handlers.go

@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/contrib/ginrus" "github.com/gin-gonic/contrib/ginrus"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/maxibanki/golang-url-shortener/handlers/tmpls" "github.com/maxibanki/golang-url-shortener/handlers/tmpls"
@ -30,7 +29,7 @@ var DoNotPrivateKeyChecking = false
// New initializes the http handlers // New initializes the http handlers
func New(store store.Store) (*Handler, error) { func New(store store.Store) (*Handler, error) {
if !viper.GetBool("enable_debug_mode") { if !util.GetConfig().EnableDebugMode {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
h := &Handler{ h := &Handler{
@ -82,13 +81,14 @@ func (h *Handler) setHandlers() error {
h.engine.NoRoute(h.handleAccess, func(c *gin.Context) { h.engine.NoRoute(h.handleAccess, func(c *gin.Context) {
c.Header("Vary", "Accept-Encoding") c.Header("Vary", "Accept-Encoding")
c.Header("Cache-Control", "public, max-age=2592000") c.Header("Cache-Control", "public, max-age=2592000")
c.Header("ETag", util.VersionInfo["commit"])
}, gin.WrapH(http.FileServer(FS(false)))) }, gin.WrapH(http.FileServer(FS(false))))
return nil return nil
} }
// Listen starts the http server // Listen starts the http server
func (h *Handler) Listen() error { func (h *Handler) Listen() error {
return h.engine.Run(viper.GetString("listen_addr")) return h.engine.Run(util.GetConfig().ListenAddr)
} }
// CloseStore stops the http server and the closes the db gracefully // CloseStore stops the http server and the closes the db gracefully

4
main.go

@ -6,7 +6,6 @@ import (
"github.com/shiena/ansicolor" "github.com/shiena/ansicolor"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/maxibanki/golang-url-shortener/handlers" "github.com/maxibanki/golang-url-shortener/handlers"
"github.com/maxibanki/golang-url-shortener/store" "github.com/maxibanki/golang-url-shortener/store"
@ -15,6 +14,7 @@ import (
) )
func main() { func main() {
os.Setenv("GUS_SHORTED_ID_LENGTH", "4")
stop := make(chan os.Signal, 1) stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt) signal.Notify(stop, os.Interrupt)
logrus.SetFormatter(&logrus.TextFormatter{ logrus.SetFormatter(&logrus.TextFormatter{
@ -34,7 +34,7 @@ func initShortener() (func(), error) {
if err := util.ReadInConfig(); err != nil { if err := util.ReadInConfig(); err != nil {
return nil, errors.Wrap(err, "could not reload config file") return nil, errors.Wrap(err, "could not reload config file")
} }
if viper.GetBool("enable_debug_mode") { if util.GetConfig().EnableDebugMode {
logrus.SetLevel(logrus.DebugLevel) logrus.SetLevel(logrus.DebugLevel)
} }
store, err := store.New() store, err := store.New()

4
main_test.go

@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/spf13/viper" "github.com/maxibanki/golang-url-shortener/util"
) )
func TestInitShortener(t *testing.T) { func TestInitShortener(t *testing.T) {
@ -15,7 +15,7 @@ func TestInitShortener(t *testing.T) {
} }
time.Sleep(time.Millisecond * 200) // Give the http server a second to boot up time.Sleep(time.Millisecond * 200) // Give the http server a second to boot up
// We expect there a port is in use error // We expect there a port is in use error
if _, err := net.Listen("tcp", viper.GetString("listen_addr")); err == nil { if _, err := net.Listen("tcp", util.GetConfig().ListenAddr); err == nil {
t.Fatalf("port is not in use: %v", err) t.Fatalf("port is not in use: %v", err)
} }
close() close()

5
store/store.go

@ -12,7 +12,6 @@ import (
"github.com/maxibanki/golang-url-shortener/util" "github.com/maxibanki/golang-url-shortener/util"
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/asaskevich/govalidator" "github.com/asaskevich/govalidator"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
@ -66,7 +65,7 @@ var (
// New initializes the store with the db // New initializes the store with the db
func New() (*Store, error) { func New() (*Store, error) {
db, err := bolt.Open(filepath.Join(util.GetDataDir(), "main.db"), 0644, &bolt.Options{Timeout: 1 * time.Second}) db, err := bolt.Open(filepath.Join(util.GetConfig().DataDir, "main.db"), 0644, &bolt.Options{Timeout: 1 * time.Second})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not open bolt DB database") return nil, errors.Wrap(err, "could not open bolt DB database")
} }
@ -79,7 +78,7 @@ func New() (*Store, error) {
} }
return &Store{ return &Store{
db: db, db: db,
idLength: viper.GetInt("shorted_id_length"), idLength: util.GetConfig().ShortedIDLength,
}, nil }, nil
} }

19
store/store_test.go

@ -1,20 +1,17 @@
package store package store
import ( import (
"os"
"strings" "strings"
"testing" "testing"
"github.com/spf13/viper" "github.com/maxibanki/golang-url-shortener/util"
)
const (
testingDBName = "test.db"
) )
func TestGenerateRandomString(t *testing.T) { func TestGenerateRandomString(t *testing.T) {
viper.SetDefault("data_dir", "./data") util.SetConfig(util.Configuration{
viper.SetDefault("shorted_id_length", 4) DataDir: "./data",
ShortedIDLength: 4,
})
tt := []struct { tt := []struct {
name string name string
length int length int
@ -41,6 +38,9 @@ func TestGenerateRandomString(t *testing.T) {
func TestNewStore(t *testing.T) { func TestNewStore(t *testing.T) {
t.Run("create store with correct arguments", func(r *testing.T) { t.Run("create store with correct arguments", func(r *testing.T) {
if err := util.ReadInConfig(); err != nil {
t.Fatalf("could not read in config: %v", err)
}
store, err := New() store, err := New()
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -121,7 +121,6 @@ func TestIncreaseVisitCounter(t *testing.T) {
} }
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
viper.Set("shorted_id_length", 4)
store, err := New() store, err := New()
if err != nil { if err != nil {
t.Fatalf("could not create store: %v", err) t.Fatalf("could not create store: %v", err)
@ -144,7 +143,6 @@ func TestDelete(t *testing.T) {
} }
func TestGetURLAndIncrease(t *testing.T) { func TestGetURLAndIncrease(t *testing.T) {
viper.Set("shorted_id_length", 4)
store, err := New() store, err := New()
if err != nil { if err != nil {
t.Fatalf("could not create store: %v", err) t.Fatalf("could not create store: %v", err)
@ -181,5 +179,4 @@ func TestGetURLAndIncrease(t *testing.T) {
func cleanup(s *Store) { func cleanup(s *Store) {
s.Close() s.Close()
os.Remove(testingDBName)
} }

134
util/config.go

@ -1,71 +1,119 @@
package util package util
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "reflect"
"strconv"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/viper" "gopkg.in/yaml.v2"
) )
type Configuration struct {
ListenAddr string `yaml:"ListenAddr" env:"LISTEN_ADDR"`
BaseURL string `yaml:"BaseURL" env:"BASE_URL"`
DataDir string `yaml:"DataDir" env:"DATA_DIR"`
EnableDebugMode bool `yaml:"EnableDebugMode" env:"ENABLE_DEBUG_MODE"`
ShortedIDLength int `yaml:"ShortedIDLength" env:"SHORTED_ID_LENGTH"`
Google oAuthConf `yaml:"Google" env:"GOOGLE"`
GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"`
Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"`
}
type oAuthConf struct {
ClientID string `yaml:"ClientID" env:"CLIENT_ID"`
ClientSecret string `yaml:"ClientSecret" env:"CLIENT_SECRET"`
}
var ( var (
dataDirPath string config = Configuration{
// DoNotSetConfigName is used to predefine if the name of the config should be set. ListenAddr: ":8080",
// Used for unit testing BaseURL: "http://localhost:3000",
DoNotSetConfigName = false DataDir: "data",
EnableDebugMode: false,
ShortedIDLength: 4,
}
) )
// ReadInConfig loads the configuration and other needed folders for further usage // ReadInConfig loads the Configuration and other needed folders for further usage
func ReadInConfig() error { func ReadInConfig() error {
viper.AutomaticEnv() file, err := ioutil.ReadFile("config.yaml")
viper.SetEnvPrefix("gus") if err == nil {
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) if err := yaml.Unmarshal(file, &config); err != nil {
if !DoNotSetConfigName { return errors.Wrap(err, "could not unmarshal yaml file")
viper.SetConfigName("config")
} }
viper.AddConfigPath(".") } else if !os.IsNotExist(err) {
setConfigDefaults()
switch err := viper.ReadInConfig(); err.(type) {
case viper.ConfigFileNotFoundError:
logrus.Info("No configuration file found, using defaults and environment overrides.")
break
case nil:
break
default:
return errors.Wrap(err, "could not read config file") return errors.Wrap(err, "could not read config file")
} }
return checkForDatadir() if err := config.ApplyEnvironmentConfig(); err != nil {
} return errors.Wrap(err, "could not apply environment configuration")
}
// setConfigDefaults sets the default values for the configuration config.DataDir, err = filepath.Abs(config.DataDir)
func setConfigDefaults() { if err != nil {
viper.SetDefault("listen_addr", ":8080") return errors.Wrap(err, "could not get relative data dir path")
viper.SetDefault("base_url", "http://localhost:3000") }
if _, err = os.Stat(config.DataDir); os.IsNotExist(err) {
viper.SetDefault("data_dir", "data") if err = os.MkdirAll(config.DataDir, 0755); err != nil {
viper.SetDefault("enable_debug_mode", true) return errors.Wrap(err, "could not create config directory")
viper.SetDefault("shorted_id_length", 4) }
}
return nil
} }
// GetDataDir returns the absolute path of the data directory func (c *Configuration) ApplyEnvironmentConfig() error {
func GetDataDir() string { return c.setDefaultValue(reflect.ValueOf(c), reflect.TypeOf(*c), reflect.StructField{}, "GUS")
return dataDirPath
} }
// checkForDatadir checks for the data dir and creates it if it not exists func (c *Configuration) setDefaultValue(v reflect.Value, t reflect.Type, f reflect.StructField, prefix string) error {
func checkForDatadir() error { if v.Kind() != reflect.Ptr {
var err error return errors.New("Not a pointer value")
dataDirPath, err = filepath.Abs(viper.GetString("data_dir")) }
v = reflect.Indirect(v)
fieldEnv, exists := f.Tag.Lookup("env")
env := os.Getenv(prefix + fieldEnv)
if exists && env != "" {
switch v.Kind() {
case reflect.Int:
envI, err := strconv.Atoi(env)
if err != nil { if err != nil {
return errors.Wrap(err, "could not get relative data dir path") logrus.Warningf("could not parse to int: %v", err)
break
}
v.SetInt(int64(envI))
case reflect.String:
v.SetString(env)
case reflect.Bool:
envB, err := strconv.ParseBool(env)
if err != nil {
logrus.Warningf("could not parse to bool: %v", err)
break
}
v.SetBool(envB)
}
}
if v.Kind() == reflect.Struct {
// Iterate over the struct fields
for i := 0; i < v.NumField(); i++ {
if err := c.setDefaultValue(v.Field(i).Addr(), t, t.Field(i), prefix+fieldEnv+"_"); err != nil {
return err
} }
if _, err = os.Stat(dataDirPath); os.IsNotExist(err) {
if err = os.MkdirAll(dataDirPath, 0755); err != nil {
return errors.Wrap(err, "could not create config directory")
} }
} }
return nil return nil
} }
func (o oAuthConf) Enabled() bool {
return o.ClientSecret != ""
}
func GetConfig() Configuration {
return config
}
func SetConfig(c Configuration) {
config = c
}

6
util/config_test.go

@ -2,14 +2,12 @@ package util
import ( import (
"testing" "testing"
"github.com/spf13/viper"
) )
func TestReadInConfig(t *testing.T) { func TestReadInConfig(t *testing.T) {
DoNotSetConfigName = true
viper.SetConfigFile("test.yaml")
if err := ReadInConfig(); err != nil { if err := ReadInConfig(); err != nil {
t.Fatalf("could not read in config file: %v", err) t.Fatalf("could not read in config file: %v", err)
} }
config := config
config.DataDir = "./test"
} }

2
util/private.go

@ -14,7 +14,7 @@ var privateKey []byte
// CheckForPrivateKey checks if already an private key exists, if not it will // CheckForPrivateKey checks if already an private key exists, if not it will
// be randomly generated and saved as a private.dat file in the data directory // be randomly generated and saved as a private.dat file in the data directory
func CheckForPrivateKey() error { func CheckForPrivateKey() error {
privateDatPath := filepath.Join(GetDataDir(), "private.dat") privateDatPath := filepath.Join(config.DataDir, "private.dat")
privateDatContent, err := ioutil.ReadFile(privateDatPath) privateDatContent, err := ioutil.ReadFile(privateDatPath)
if err == nil { if err == nil {
privateKey = privateDatContent privateKey = privateDatContent

2
util/private_test.go

@ -14,7 +14,7 @@ func TestCheckforPrivateKey(t *testing.T) {
if GetPrivateKey() == nil { if GetPrivateKey() == nil {
t.Fatalf("private key is nil") t.Fatalf("private key is nil")
} }
if err := os.RemoveAll(GetDataDir()); err != nil { if err := os.RemoveAll(config.DataDir); err != nil {
t.Fatalf("could not remove data dir: %v", err) t.Fatalf("could not remove data dir: %v", err)
} }
} }

Loading…
Cancel
Save