From 4363d55cee635b7f62897317c396b795433c5e34 Mon Sep 17 00:00:00 2001 From: Andrea Fazzi Date: Thu, 10 Feb 2022 09:38:38 +0100 Subject: [PATCH] Initial support for JWT auth --- backend/config.go | 26 +++++++++++++++++++ backend/config.yaml | 4 +++ backend/go.mod | 2 ++ backend/go.sum | 1 + backend/main.go | 60 +++++++++++++++++++++++++++++++++++++++++++- backend/main_test.go | 35 ++++++++++++++++++++++++++ 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 backend/config.go create mode 100644 backend/config.yaml diff --git a/backend/config.go b/backend/config.go new file mode 100644 index 0000000..2d9eb13 --- /dev/null +++ b/backend/config.go @@ -0,0 +1,26 @@ +package main + +import ( + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +type Config struct { + Password string `yaml:"password"` + CookieStoreKey string `yaml:"cookie_store_key"` + JWTSigningKey string `yaml:"jwt_signing_key"` + JWTExpireTime uint `yaml:"jwt_expire_time"` +} + +// ReadFile reads the config file placed at the given path. +func ReadConfig(path string, config *Config) error { + cf, err := ioutil.ReadFile(path) + if err != nil { + return err + } + if err := yaml.Unmarshal(cf, config); err != nil { + return err + } + return nil +} diff --git a/backend/config.yaml b/backend/config.yaml new file mode 100644 index 0000000..118ac74 --- /dev/null +++ b/backend/config.yaml @@ -0,0 +1,4 @@ +password: "secret" +jwt_signing_key: "abracadabra" +jwt_expire_time: 60 +cookie_store_key: "abracadabra" diff --git a/backend/go.mod b/backend/go.mod index 00834a3..5539f8a 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,8 +3,10 @@ module git.andreafazzi.eu/andrea/youtube-dl-service go 1.16 require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.4 github.com/kkdai/youtube/v2 v2.7.4 github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 // indirect + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/backend/go.sum b/backend/go.sum index 72b6061..68d6502 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -75,6 +75,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/backend/main.go b/backend/main.go index 9b30d84..30db825 100644 --- a/backend/main.go +++ b/backend/main.go @@ -6,14 +6,21 @@ import ( "net/http" "path/filepath" "strconv" + "time" "git.andreafazzi.eu/andrea/youtube-dl-service/downloader" youtube_dl "git.andreafazzi.eu/andrea/youtube-dl-service/downloader/youtube-dl" "git.andreafazzi.eu/andrea/youtube-dl-service/task" + jwt "github.com/dgrijalva/jwt-go" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) +const ( + DefaultJWTExpireTime = 60 + DefaultSigningKey = "secret" +) + var tasks task.Tasks func init() { @@ -58,12 +65,51 @@ func data(c *gin.Context, id string) error { return nil } +func postLogin(c *gin.Context, config *Config) error { + err := c.Request.ParseForm() + if err != nil { + return err + } + + token, err := getToken(config, c.PostForm("password")) + if err != nil { + return err + } + + c.JSON(http.StatusOK, token) + + return nil +} + +func getToken(config *Config, password string) ([]byte, error) { + if password != config.Password { + return nil, fmt.Errorf("Wrong password!") + } + + /* Set token claims */ + claims := make(map[string]interface{}) + + // Token expire time + claims["exp"] = time.Now().Add(time.Minute * time.Duration(config.JWTExpireTime)).Unix() + + /* Create the token */ + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(claims)) + + /* Sign the token with our secret */ + tokenString, err := token.SignedString(config.JWTSigningKey) + if err != nil { + log.Println(err) + return nil, err + } + + return []byte(tokenString), nil +} + // API outline // // POST /task content: url=https://foo.org start a new download task // GET /task/:id get the status for the task // GET /task/:id/download download the file resulted from the task execution -// func main() { r := gin.Default() r.Use(cors.New(cors.Config{ @@ -76,6 +122,12 @@ func main() { AllowHeaders: []string{"*"}, })) + config := new(Config) + err := ReadConfig("config.yaml", config) + if err != nil { + panic(err) + } + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { if err, ok := recovered.(string); ok { c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) @@ -97,5 +149,11 @@ func main() { } }) + r.POST("/login", func(c *gin.Context) { + if err := postLogin(c, config); err != nil { + panic(err) + } + }) + r.Run() } diff --git a/backend/main_test.go b/backend/main_test.go index 806ba54..63037f3 100644 --- a/backend/main_test.go +++ b/backend/main_test.go @@ -7,6 +7,7 @@ import ( "net/url" "strings" "testing" + "time" "git.andreafazzi.eu/andrea/youtube-dl-service/downloader" "git.andreafazzi.eu/andrea/youtube-dl-service/logger" @@ -15,6 +16,8 @@ import ( "github.com/remogatto/prettytest" ) +var config *Config + // Start of setup type testSuite struct { prettytest.Suite @@ -30,6 +33,12 @@ func TestRunner(t *testing.T) { func (t *testSuite) BeforeAll() { gin.SetMode(gin.ReleaseMode) logger.SetLevel(logger.Disabled) + + config = new(Config) + err := ReadConfig("config.yaml", config) + if err != nil { + panic(err) + } } // Test the creation of a new task. A new task is created with a POST @@ -49,6 +58,9 @@ func (t *testSuite) TestGetTask() { panic(err) } + timer := time.NewTimer(time.Second * 10) + <-timer.C + // Create a new request req, err := http.NewRequest("GET", "/task/"+video.ID, nil) if err != nil { @@ -75,6 +87,29 @@ func (t *testSuite) TestGetTask() { t.Equal(task.StatusCompleted, ts.Status) } +func (t *testSuite) TestPostLogin() { + // Set form values + form := url.Values{} + form.Add("password", "secret") + + req, err := http.NewRequest("POST", "/login", strings.NewReader((form.Encode()))) + if err != nil { + panic(err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // Create an http recorder + rr := httptest.NewRecorder() + + // Set the context and call the handler + c, _ := gin.CreateTestContext(rr) + c.Request = req + + err = postLogin(c, config) + t.Nil(err) +} + func postTask(ytUrl string) (*downloader.Video, error) { // Set form values form := url.Values{}