Browse Source

Fix #36

dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
9857081695
  1. 3
      .gitignore
  2. 2
      Makefile
  3. 17
      build/config.json
  4. 11
      build/config.yaml
  5. 92
      config/config.go
  6. 19
      handlers/auth.go
  7. 31
      handlers/auth_test.go
  8. 34
      handlers/handlers.go
  9. 14
      main.go
  10. 12
      store/store.go
  11. 30
      store/store_test.go
  12. 2
      store/util.go
  13. 54
      util/config.go
  14. 36
      util/private.go

3
.gitignore

@ -16,7 +16,8 @@ main.db
debug debug
*db.lock *db.lock
/config.json /config.*
/handlers/static.go /handlers/static.go
/handlers/tmpls/tmpls.go /handlers/tmpls/tmpls.go
/releases /releases
/data

2
Makefile

@ -23,5 +23,5 @@ getGoDependencies:
buildProject: buildProject:
@mkdir releases @mkdir releases
gox -output="releases/{{.Dir}}_{{.OS}}_{{.Arch}}/{{.Dir}}" gox -output="releases/{{.Dir}}_{{.OS}}_{{.Arch}}/{{.Dir}}"
find releases -maxdepth 1 -mindepth 1 -type d -exec cp build/config.json {} \; find releases -maxdepth 1 -mindepth 1 -type d -exec cp build/config.yaml {} \;
find releases -maxdepth 1 -mindepth 1 -type d -exec tar -cvjf {}.tar.bz2 {} \; find releases -maxdepth 1 -mindepth 1 -type d -exec tar -cvjf {}.tar.bz2 {} \;

17
build/config.json

@ -1,17 +0,0 @@
{
"Store": {
"DBPath": "main.db",
"ShortedIDLength": 4
},
"Handlers": {
"ListenAddr": ":8080",
"BaseURL": "http://localhost:3000",
"EnableGinDebugMode": false,
"OAuth": {
"Google": {
"ClientID": "",
"ClientSecret": ""
}
}
}
}

11
build/config.yaml

@ -0,0 +1,11 @@
http:
ListenAddr: ':8080'
BaseURL: 'http://localhost:3000'
General:
DBPath: main.db
EnableDebugMode: true
ShortedIDLength: 4
oAuth:
Google:
ClientID: replace me
ClientSecret: replace me

92
config/config.go

@ -1,92 +0,0 @@
package config
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
)
// Configuration holds all the needed parameters use
// the URL Shortener
type Configuration struct {
Store Store
Handlers Handlers
}
// Store contains the needed fields for the Store package
type Store struct {
DBPath string
ShortedIDLength uint
}
// Handlers contains the needed fields for the Handlers package
type Handlers struct {
ListenAddr string
BaseURL string
EnableDebugMode bool
Secret []byte
OAuth struct {
Google struct {
ClientID string
ClientSecret string
}
}
}
var (
config *Configuration
configPath string
)
// Get returns the configuration from a given file
func Get() *Configuration {
return config
}
// Preload loads the configuration file into the memory for further usage
func Preload() error {
var err error
configPath, err = getConfigPath()
if err != nil {
return errors.Wrap(err, "could not get configuration path")
}
if err = updateConfig(); err != nil {
return errors.Wrap(err, "could not update config")
}
return nil
}
func updateConfig() error {
file, err := ioutil.ReadFile(configPath)
if err != nil {
return errors.Wrap(err, "could not read configuration file")
}
if err = json.Unmarshal(file, &config); err != nil {
return errors.Wrap(err, "could not unmarshal configuration file")
}
return nil
}
func getConfigPath() (string, error) {
ex, err := os.Executable()
if err != nil {
return "", errors.Wrap(err, "could not get executable path")
}
return filepath.Join(filepath.Dir(ex), "config.json"), nil
}
// Set replaces the current configuration with the given one
func Set(conf *Configuration) error {
data, err := json.MarshalIndent(conf, "", " ")
if err != nil {
return err
}
if err = ioutil.WriteFile(configPath, data, 0644); err != nil {
return err
}
config = conf
return nil
}

19
handlers/auth.go

@ -7,6 +7,9 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/maxibanki/golang-url-shortener/util"
"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"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -38,15 +41,15 @@ type checkResponse struct {
func (h *Handler) initOAuth() { func (h *Handler) initOAuth() {
h.oAuthConf = &oauth2.Config{ h.oAuthConf = &oauth2.Config{
ClientID: h.config.OAuth.Google.ClientID, ClientID: viper.GetString("oAuth.Google.ClientID"),
ClientSecret: h.config.OAuth.Google.ClientSecret, ClientSecret: viper.GetString("oAuth.Google.ClientSecret"),
RedirectURL: h.config.BaseURL + "/api/v1/callback", RedirectURL: viper.GetString("http.BaseURL") + "/api/v1/callback",
Scopes: []string{ Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.email",
}, },
Endpoint: google.Endpoint, Endpoint: google.Endpoint,
} }
h.engine.Use(sessions.Sessions("backend", sessions.NewCookieStore(h.config.Secret))) h.engine.Use(sessions.Sessions("backend", sessions.NewCookieStore(util.GetPrivateKey())))
h.engine.GET("/api/v1/login", h.handleGoogleRedirect) h.engine.GET("/api/v1/login", h.handleGoogleRedirect)
h.engine.GET("/api/v1/callback", h.handleGoogleCallback) h.engine.GET("/api/v1/callback", h.handleGoogleCallback)
h.engine.POST("/api/v1/check", h.handleGoogleCheck) h.engine.POST("/api/v1/check", h.handleGoogleCheck)
@ -67,7 +70,7 @@ func (h *Handler) authMiddleware(c *gin.Context) {
return errors.New("'Authorization' header not set") return errors.New("'Authorization' header not set")
} }
token, err := jwt.ParseWithClaims(authHeader, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(authHeader, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return h.config.Secret, nil return util.GetPrivateKey(), nil
}) })
if err != nil { if err != nil {
return fmt.Errorf("could not parse token: %v", err) return fmt.Errorf("could not parse token: %v", err)
@ -79,7 +82,7 @@ func (h *Handler) authMiddleware(c *gin.Context) {
return nil return nil
}() }()
if authError != nil { if authError != nil {
if h.config.EnableDebugMode { if viper.GetBool("General.EnableDebugMode") {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": fmt.Sprintf("token is not valid: %v", authError), "error": fmt.Sprintf("token is not valid: %v", authError),
}) })
@ -103,7 +106,7 @@ func (h *Handler) handleGoogleCheck(c *gin.Context) {
return return
} }
token, err := jwt.ParseWithClaims(data.Token, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(data.Token, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return h.config.Secret, nil return util.GetPrivateKey(), nil
}) })
if claims, ok := token.Claims.(*jwtClaims); ok && token.Valid { if claims, ok := token.Claims.(*jwtClaims); ok && token.Valid {
c.JSON(http.StatusOK, checkResponse{ c.JSON(http.StatusOK, checkResponse{
@ -159,7 +162,7 @@ func (h *Handler) handleGoogleCallback(c *gin.Context) {
user.Picture, user.Picture,
}) })
tokenString, err := token.SignedString(h.config.Secret) tokenString, err := token.SignedString(util.GetPrivateKey())
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("could not sign token: %v", err)}) c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("could not sign token: %v", err)})
return return

31
handlers/auth_test.go

@ -6,16 +6,16 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"strings" "strings"
"testing" "testing"
"time" "time"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"github.com/maxibanki/golang-url-shortener/config"
"github.com/maxibanki/golang-url-shortener/store" "github.com/maxibanki/golang-url-shortener/store"
"github.com/maxibanki/golang-url-shortener/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
) )
@ -24,7 +24,7 @@ const (
) )
var ( var (
secret = []byte("our really great secret") secret []byte
server *httptest.Server server *httptest.Server
closeServer func() error closeServer func() error
handler *Handler handler *Handler
@ -41,18 +41,22 @@ var (
) )
func TestCreateBackend(t *testing.T) { func TestCreateBackend(t *testing.T) {
store, err := store.New(config.Store{ secret = util.GetPrivateKey()
DBPath: testingDBName, viper.SetConfigName("config")
ShortedIDLength: 4, viper.AddConfigPath("../")
}, logrus.New()) util.SetConfigDefaults()
err := viper.ReadInConfig()
if err != nil {
t.Fatalf("could not reload config file: %v", err)
}
if err := util.CheckForDatadir(); err != nil {
t.Fatalf("could not reload config file: %v", err)
}
store, err := store.New(logrus.New())
if err != nil { if err != nil {
t.Fatalf("could not create store: %v", err) t.Fatalf("could not create store: %v", err)
} }
handler, err := New(config.Handlers{ handler, err := New(*store, logrus.New(), true)
ListenAddr: ":8080",
Secret: secret,
BaseURL: "http://127.0.0.1",
}, *store, logrus.New(), true)
if err != nil { if err != nil {
t.Fatalf("could not create handler: %v", err) t.Fatalf("could not create handler: %v", err)
} }
@ -62,9 +66,6 @@ func TestCreateBackend(t *testing.T) {
if err := handler.CloseStore(); err != nil { if err := handler.CloseStore(); err != nil {
return errors.Wrap(err, "could not close store") return errors.Wrap(err, "could not close store")
} }
if err := os.Remove(testingDBName); err != nil {
return errors.Wrap(err, "could not remove testing db")
}
return nil return nil
} }
} }

34
handlers/handlers.go

@ -2,17 +2,17 @@
package handlers package handlers
import ( import (
"crypto/rand"
"html/template" "html/template"
"net/http" "net/http"
"time" "time"
"github.com/gin-gonic/contrib/ginrus" "github.com/gin-gonic/contrib/ginrus"
"github.com/spf13/viper"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/maxibanki/golang-url-shortener/config"
"github.com/maxibanki/golang-url-shortener/handlers/tmpls" "github.com/maxibanki/golang-url-shortener/handlers/tmpls"
"github.com/maxibanki/golang-url-shortener/store" "github.com/maxibanki/golang-url-shortener/store"
"github.com/maxibanki/golang-url-shortener/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -21,21 +21,18 @@ import (
// Handler holds the funcs and attributes for the // Handler holds the funcs and attributes for the
// http communication // http communication
type Handler struct { type Handler struct {
config config.Handlers
store store.Store store store.Store
engine *gin.Engine engine *gin.Engine
oAuthConf *oauth2.Config oAuthConf *oauth2.Config
log *logrus.Logger log *logrus.Logger
DoNotCheckConfigViaGet bool // DoNotCheckConfigViaGet is for the unit testing usage
} }
// New initializes the http handlers // New initializes the http handlers
func New(handlerConfig config.Handlers, store store.Store, log *logrus.Logger, testing bool) (*Handler, error) { func New(store store.Store, log *logrus.Logger, testing bool) (*Handler, error) {
if !handlerConfig.EnableDebugMode { if !viper.GetBool("General.EnableDebugMode") {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
h := &Handler{ h := &Handler{
config: handlerConfig,
store: store, store: store,
log: log, log: log,
engine: gin.New(), engine: gin.New(),
@ -44,8 +41,8 @@ func New(handlerConfig config.Handlers, store store.Store, log *logrus.Logger, t
return nil, errors.Wrap(err, "could not set handlers") return nil, errors.Wrap(err, "could not set handlers")
} }
if !testing { if !testing {
if err := h.checkIfSecretExist(); err != nil { if err := util.CheckForPrivateKey(); err != nil {
return nil, errors.Wrap(err, "could not check if secret exist") return nil, errors.Wrap(err, "could not check for privat key")
} }
} }
h.initOAuth() h.initOAuth()
@ -65,23 +62,6 @@ func (h *Handler) setTemplateFromFS(name string) error {
return nil return nil
} }
func (h *Handler) checkIfSecretExist() error {
if !h.DoNotCheckConfigViaGet {
conf := config.Get()
if conf.Handlers.Secret == nil {
b := make([]byte, 128)
if _, err := rand.Read(b); err != nil {
return err
}
conf.Handlers.Secret = b
if err := config.Set(conf); err != nil {
return err
}
}
}
return nil
}
func (h *Handler) setHandlers() error { func (h *Handler) setHandlers() error {
if err := h.setTemplateFromFS("token.tmpl"); err != nil { if err := h.setTemplateFromFS("token.tmpl"); err != nil {
return errors.Wrap(err, "could not set template from FS") return errors.Wrap(err, "could not set template from FS")
@ -98,7 +78,7 @@ func (h *Handler) setHandlers() error {
// Listen starts the http server // Listen starts the http server
func (h *Handler) Listen() error { func (h *Handler) Listen() error {
return h.engine.Run(h.config.ListenAddr) return h.engine.Run(viper.GetString("http.ListenAddr"))
} }
// CloseStore stops the http server and the closes the db gracefully // CloseStore stops the http server and the closes the db gracefully

14
main.go

@ -6,10 +6,11 @@ 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/config"
"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"
"github.com/maxibanki/golang-url-shortener/util"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -31,18 +32,17 @@ func main() {
} }
func initShortener(log *logrus.Logger) (func(), error) { func initShortener(log *logrus.Logger) (func(), error) {
if err := config.Preload(); err != nil { if err := util.ReadInConfig(); err != nil {
return nil, errors.Wrap(err, "could not get config") return nil, errors.Wrap(err, "could not reload config file")
} }
conf := config.Get() if viper.GetBool("General.EnableDebugMode") {
if conf.Handlers.EnableDebugMode {
log.SetLevel(logrus.DebugLevel) log.SetLevel(logrus.DebugLevel)
} }
store, err := store.New(conf.Store, log) store, err := store.New(log)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not create store") return nil, errors.Wrap(err, "could not create store")
} }
handler, err := handlers.New(conf.Handlers, *store, log, false) handler, err := handlers.New(*store, log, false)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "could not create handlers") return nil, errors.Wrap(err, "could not create handlers")
} }

12
store/store.go

@ -3,13 +3,15 @@ package store
import ( import (
"encoding/json" "encoding/json"
"path/filepath"
"time" "time"
"github.com/maxibanki/golang-url-shortener/util"
"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"
"github.com/maxibanki/golang-url-shortener/config"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -17,7 +19,7 @@ import (
type Store struct { type Store struct {
db *bolt.DB db *bolt.DB
bucketName []byte bucketName []byte
idLength uint idLength int
log *logrus.Logger log *logrus.Logger
} }
@ -47,8 +49,8 @@ var ErrGeneratingIDFailed = errors.New("could not generate unique id, all ten tr
var ErrIDIsEmpty = errors.New("the given ID is empty") var ErrIDIsEmpty = errors.New("the given ID is empty")
// New initializes the store with the db // New initializes the store with the db
func New(storeConfig config.Store, log *logrus.Logger) (*Store, error) { func New(log *logrus.Logger) (*Store, error) {
db, err := bolt.Open(storeConfig.DBPath, 0644, &bolt.Options{Timeout: 1 * time.Second}) db, err := bolt.Open(filepath.Join(util.GetDataDir(), "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")
} }
@ -62,7 +64,7 @@ func New(storeConfig config.Store, log *logrus.Logger) (*Store, error) {
} }
return &Store{ return &Store{
db: db, db: db,
idLength: storeConfig.ShortedIDLength, idLength: viper.GetInt("General.ShortedIDLength"),
bucketName: bucketName, bucketName: bucketName,
log: log, log: log,
}, nil }, nil

30
store/store_test.go

@ -2,27 +2,22 @@ package store
import ( import (
"os" "os"
"strings"
"testing" "testing"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/maxibanki/golang-url-shortener/config"
) )
const ( const (
testingDBName = "test.db" testingDBName = "test.db"
) )
var validConfig = config.Store{
DBPath: testingDBName,
ShortedIDLength: 4,
}
func TestGenerateRandomString(t *testing.T) { func TestGenerateRandomString(t *testing.T) {
viper.SetDefault("General.DataDir", "data")
viper.SetDefault("General.ShortedIDLength", 4)
tt := []struct { tt := []struct {
name string name string
length uint length int
}{ }{
{"fourtytwo long", 42}, {"fourtytwo long", 42},
{"sixteen long", 16}, {"sixteen long", 16},
@ -45,14 +40,8 @@ func TestGenerateRandomString(t *testing.T) {
} }
func TestNewStore(t *testing.T) { func TestNewStore(t *testing.T) {
t.Run("create store without file name provided", func(r *testing.T) {
_, err := New(config.Store{}, logrus.New())
if !strings.Contains(err.Error(), "could not open bolt DB database") {
t.Fatalf("unexpected error: %v", err)
}
})
t.Run("create store with correct arguments", func(r *testing.T) { t.Run("create store with correct arguments", func(r *testing.T) {
store, err := New(validConfig, logrus.New()) store, err := New(logrus.New())
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -61,10 +50,7 @@ func TestNewStore(t *testing.T) {
} }
func TestCreateEntry(t *testing.T) { func TestCreateEntry(t *testing.T) {
store, err := New(config.Store{ store, err := New(logrus.New())
DBPath: testingDBName,
ShortedIDLength: 1,
}, logrus.New())
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -86,7 +72,7 @@ func TestCreateEntry(t *testing.T) {
} }
func TestGetEntryByID(t *testing.T) { func TestGetEntryByID(t *testing.T) {
store, err := New(validConfig, logrus.New()) store, err := New(logrus.New())
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -102,7 +88,7 @@ func TestGetEntryByID(t *testing.T) {
} }
func TestIncreaseVisitCounter(t *testing.T) { func TestIncreaseVisitCounter(t *testing.T) {
store, err := New(validConfig, logrus.New()) store, err := New(logrus.New())
if err != nil { if err != nil {
t.Fatalf("could not create store: %v", err) t.Fatalf("could not create store: %v", err)
} }

2
store/util.go

@ -48,7 +48,7 @@ func (s *Store) createEntry(entry Entry, givenID string) (string, error) {
} }
// generateRandomString generates a random string with an predefined length // generateRandomString generates a random string with an predefined length
func generateRandomString(length uint) (string, error) { func generateRandomString(length int) (string, error) {
var result string var result string
for len(result) < int(length) { for len(result) < int(length) {
num, err := rand.Int(rand.Reader, big.NewInt(int64(127))) num, err := rand.Int(rand.Reader, big.NewInt(int64(127)))

54
util/config.go

@ -0,0 +1,54 @@
package util
import (
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/spf13/viper"
)
var dataDirPath string
func ReadInConfig() error {
viper.AutomaticEnv()
viper.SetEnvPrefix("gus")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetConfigName("config")
viper.AddConfigPath(".")
SetConfigDefaults()
err := viper.ReadInConfig()
if err != nil {
return errors.Wrap(err, "could not reload config file")
}
return CheckForDatadir()
}
func SetConfigDefaults() {
viper.SetDefault("http.ListenAddr", ":8080")
viper.SetDefault("http.BaseURL", "http://localhost:3000")
viper.SetDefault("General.DataDir", "data")
viper.SetDefault("General.EnableDebugMode", true)
viper.SetDefault("General.ShortedIDLength", 4)
}
func GetDataDir() string {
return dataDirPath
}
func CheckForDatadir() error {
var err error
dataDirPath, err = filepath.Abs(viper.GetString("General.DataDir"))
if err != nil {
return errors.Wrap(err, "could not get relative data dir path")
}
if _, err = os.Stat(dataDirPath); os.IsNotExist(err) {
err = os.MkdirAll(dataDirPath, 0755)
if err != nil {
return errors.Wrap(err, "could not create config directory")
}
}
return nil
}

36
util/private.go

@ -0,0 +1,36 @@
package util
import (
"crypto/rand"
"io/ioutil"
"os"
"path/filepath"
"github.com/pkg/errors"
)
var privateKey []byte
func CheckForPrivateKey() error {
privateDat := filepath.Join(GetDataDir(), "private.dat")
d, err := ioutil.ReadFile(privateDat)
if err == nil {
privateKey = d
} else if os.IsNotExist(err) {
b := make([]byte, 256)
if _, err := rand.Read(b); err != nil {
return errors.Wrap(err, "could not read random bytes")
}
if err = ioutil.WriteFile(privateDat, b, 0644); err != nil {
return errors.Wrap(err, "could not write private key")
}
privateKey = b
} else if err != nil {
return errors.Wrap(err, "could not read private key")
}
return nil
}
func GetPrivateKey() []byte {
return privateKey
}
Loading…
Cancel
Save