diff --git a/Makefile b/Makefile index ea90040..7dac2be 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ buildNodeFrontend: @cd static && rm build/static/**/*.map embedFrontend: - @cd handlers/tmpls && esc -o tmpls.go -pkg tmpls -include ^*\.tmpl . + @cd handlers/tmpls && esc -o tmpls.go -pkg tmpls -include ^*\.html . @cd handlers && esc -o static.go -pkg handlers -prefix ../static/build ../static/build bash build/info.sh diff --git a/handlers/auth.go b/handlers/auth.go index 18f2d0b..da671ee 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -33,7 +33,7 @@ func (h *Handler) initOAuth() { h.providers = append(h.providers, "microsoft") } - h.engine.POST("/api/v1/check", h.handleAuthCheck) + h.engine.POST("/api/v1/auth/check", h.handleAuthCheck) } func (h *Handler) parseJWT(wt string) (*auth.JWTClaims, error) { diff --git a/handlers/auth/auth.go b/handlers/auth/auth.go index 4b4eea9..fd7979d 100644 --- a/handlers/auth/auth.go +++ b/handlers/auth/auth.go @@ -88,7 +88,7 @@ func (a *AdapterWrapper) HandleCallback(c *gin.Context) { c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.HTML(http.StatusOK, "token.tmpl", gin.H{ + c.HTML(http.StatusOK, "token.html", gin.H{ "token": token, }) } diff --git a/handlers/auth_test.go b/handlers/auth_test.go index b56cb24..23303b0 100644 --- a/handlers/auth_test.go +++ b/handlers/auth_test.go @@ -99,7 +99,7 @@ func TestCheckToken(t *testing.T) { if err != nil { t.Fatalf("could not post to the backend: %v", err) } - resp, err := http.Post(server.URL+"/api/v1/check", "application/json", bytes.NewBuffer(body)) + resp, err := http.Post(server.URL+"/api/v1/auth/check", "application/json", bytes.NewBuffer(body)) if err != nil { t.Fatalf("could not execute get request: %v", err) } diff --git a/handlers/handlers.go b/handlers/handlers.go index fddda81..5399dad 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -62,8 +62,11 @@ func (h *Handler) setTemplateFromFS(name string) error { } func (h *Handler) setHandlers() error { - if err := h.setTemplateFromFS("token.tmpl"); err != nil { - return errors.Wrap(err, "could not set template from FS") + templates := []string{"token.html", "protected.html"} + for _, template := range templates { + if err := h.setTemplateFromFS(template); err != nil { + return errors.Wrapf(err, "could not set template %s from FS", template) + } } h.engine.Use(ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, false)) protected := h.engine.Group("/api/v1/protected") diff --git a/handlers/public.go b/handlers/public.go index cc2dec6..e9fd73a 100644 --- a/handlers/public.go +++ b/handlers/public.go @@ -14,14 +14,15 @@ import ( "github.com/maxibanki/golang-url-shortener/handlers/auth" "github.com/maxibanki/golang-url-shortener/store" "github.com/maxibanki/golang-url-shortener/util" + "golang.org/x/crypto/bcrypt" ) -// urlUtil is used to help in- and outgoing requests for json +// requestHelper is used to help in- and outgoing requests for json // un- and marshalling -type urlUtil struct { - URL string `binding:"required"` - ID, DeletionURL string - Expiration *time.Time +type requestHelper struct { + URL string `binding:"required"` + ID, DeletionURL, Password string + Expiration *time.Time } // handleLookup is the http handler for getting the infos @@ -52,7 +53,7 @@ func (h *Handler) handleLookup(c *gin.Context) { // handleAccess handles the access for incoming requests func (h *Handler) handleAccess(c *gin.Context) { id := c.Request.URL.Path[1:] - url, err := h.store.GetURLAndIncrease(id) + entry, err := h.store.GetEntryAndIncrease(id) if err == store.ErrNoEntryFound { return } else if err != nil { @@ -70,7 +71,31 @@ func (h *Handler) handleAccess(c *gin.Context) { UTMContent: c.Query("utm_content"), UTMTerm: c.Query("utm_term"), }) - c.Redirect(http.StatusTemporaryRedirect, url) + // No password set + if len(entry.Password) == 0 { + c.Redirect(http.StatusTemporaryRedirect, entry.Public.URL) + } else { + templateError := "" + if c.Request.Method == "POST" { + pw, exists := c.GetPostForm("password") + if exists { + if err := bcrypt.CompareHashAndPassword(entry.Password, []byte(pw)); err != nil { + templateError = fmt.Sprintf("could not validate password: %v", err) + } + } else { + templateError = "No password set" + } + if templateError == "" { + c.Redirect(http.StatusTemporaryRedirect, entry.Public.URL) + c.Abort() + return + } + } + c.HTML(http.StatusOK, "protected.html", gin.H{ + "ID": id, + "Error": templateError, + }) + } // There is a need to Abort in the current middleware to prevent // that the status code will be overridden by the default NoRoute handler c.Abort() @@ -78,7 +103,7 @@ func (h *Handler) handleAccess(c *gin.Context) { // handleCreate handles requests to create an entry func (h *Handler) handleCreate(c *gin.Context) { - var data urlUtil + var data requestHelper if err := c.ShouldBind(&data); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return @@ -92,13 +117,13 @@ func (h *Handler) handleCreate(c *gin.Context) { RemoteAddr: c.ClientIP(), OAuthProvider: user.OAuthProvider, OAuthID: user.OAuthID, - }, data.ID) + }, data.ID, data.Password) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } originURL := h.getURLOrigin(c) - c.JSON(http.StatusOK, urlUtil{ + c.JSON(http.StatusOK, requestHelper{ URL: fmt.Sprintf("%s/%s", originURL, id), DeletionURL: fmt.Sprintf("%s/d/%s/%s", originURL, id, url.QueryEscape(base64.RawURLEncoding.EncodeToString(delID))), }) diff --git a/handlers/public_test.go b/handlers/public_test.go index 1d3a72e..a54be73 100644 --- a/handlers/public_test.go +++ b/handlers/public_test.go @@ -25,7 +25,7 @@ func TestCreateEntry(t *testing.T) { ignoreResponse bool contentType string response gin.H - requestBody urlUtil + requestBody requestHelper statusCode int }{ { @@ -37,7 +37,7 @@ func TestCreateEntry(t *testing.T) { }, { name: "short URL generation", - requestBody: urlUtil{ + requestBody: requestHelper{ URL: "https://www.google.de/", }, statusCode: http.StatusOK, @@ -45,7 +45,7 @@ func TestCreateEntry(t *testing.T) { }, { name: "no valid URL", - requestBody: urlUtil{ + requestBody: requestHelper{ URL: "this is really not a URL", }, statusCode: http.StatusBadRequest, @@ -76,7 +76,7 @@ func TestCreateEntry(t *testing.T) { if tc.ignoreResponse { return } - var parsed urlUtil + var parsed requestHelper if err := json.Unmarshal(respBody, &parsed); err != nil { t.Fatalf("could not unmarshal data: %v", err) } @@ -96,7 +96,7 @@ func TestHandleInfo(t *testing.T) { t.Fatalf("could not marshal json: %v", err) } respBody := createEntryWithJSON(t, reqBody, "application/json; charset=utf-8", http.StatusOK) - var parsed urlUtil + var parsed requestHelper if err = json.Unmarshal(respBody, &parsed); err != nil { t.Fatalf("could not unmarshal data: %v", err) } @@ -257,7 +257,7 @@ func TestHandleDeletion(t *testing.T) { t.Fatalf("could not marshal json: %v", err) } respBody := createEntryWithJSON(t, reqBody, "application/json; charset=utf-8", http.StatusOK) - var body urlUtil + var body requestHelper if err := json.Unmarshal(respBody, &body); err != nil { t.Fatal("could not unmarshal create response") } diff --git a/handlers/tmpls/protected.html b/handlers/tmpls/protected.html new file mode 100644 index 0000000..f24e8c7 --- /dev/null +++ b/handlers/tmpls/protected.html @@ -0,0 +1,68 @@ + + + +
+ + + +The following authentication services are currently available:
{info &&There are currently no correct oAuth credentials maintained.
} - {info.providers.indexOf("google") !== -1 &&