summaryrefslogtreecommitdiffstats
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/dto/entry.go14
-rw-r--r--api/handlers/entry.go105
-rw-r--r--api/handlers/record.go35
-rw-r--r--api/middleware/auth.go51
-rw-r--r--api/middleware/cors.go12
-rw-r--r--api/router.go9
6 files changed, 184 insertions, 42 deletions
diff --git a/api/dto/entry.go b/api/dto/entry.go
index 85e39f3..8346b18 100644
--- a/api/dto/entry.go
+++ b/api/dto/entry.go
@@ -5,5 +5,17 @@ type CreateEntryRequest struct {
Kind string `json:"kind"`
Url string `json:"url"`
Description string `json:"description"`
- Token string `json:"token"`
+}
+
+type UpdateEntryRequest struct {
+ Id int64 `json:"id"`
+ Kind string `json:"kind"`
+}
+
+type DeleteEntryRequest struct {
+ Id int64 `json:"id"`
+}
+
+type GetEntryRequest struct {
+ Url string `json:"url"`
}
diff --git a/api/handlers/entry.go b/api/handlers/entry.go
new file mode 100644
index 0000000..e94dfea
--- /dev/null
+++ b/api/handlers/entry.go
@@ -0,0 +1,105 @@
+package handlers
+
+import (
+ "errors"
+ "net/http"
+
+ "git.leonardobishop.net/stash/api/dto"
+ "git.leonardobishop.net/stash/pkg/entries"
+)
+
+func GetEntryURLs(service entries.Service) http.HandlerFunc {
+ return dto.WrapResponseFunc(func(w http.ResponseWriter, r *http.Request) error {
+ entries, err := service.GetEntryURLs()
+ if err != nil {
+ return err
+ }
+
+ return &dto.OkResponse{
+ Code: http.StatusOK,
+ Data: entries,
+ }
+ })
+}
+
+func GetEntry(service entries.Service) http.HandlerFunc {
+ return dto.WrapResponseFunc(func(w http.ResponseWriter, r *http.Request) error {
+ var request dto.GetEntryRequest
+ if err := dto.ReadDto(r, &request); err != nil {
+ return err
+ }
+
+ entry, err := service.GetEntryByUrl(request.Url)
+ if err != nil {
+ if errors.Is(err, entries.ErrEntryNotFound) {
+ return &dto.ErrorResponse{
+ Code: http.StatusNotFound,
+ Message: "entry not found",
+ }
+ }
+ return err
+ }
+
+ return &dto.OkResponse{
+ Code: http.StatusOK,
+ Data: entry,
+ }
+ })
+}
+
+func RecordEntry(service entries.Service) http.HandlerFunc {
+ return dto.WrapResponseFunc(func(w http.ResponseWriter, r *http.Request) error {
+ var request dto.CreateEntryRequest
+ if err := dto.ReadDto(r, &request); err != nil {
+ return err
+ }
+
+ entry, err := service.CreateEntry(request.Title, request.Kind, request.Url, request.Description)
+ if err != nil {
+ return err
+ }
+
+ return &dto.OkResponse{
+ Code: http.StatusCreated,
+ Data: entry,
+ }
+ })
+}
+
+func UpdateEntry(service entries.Service) http.HandlerFunc {
+ return dto.WrapResponseFunc(func(w http.ResponseWriter, r *http.Request) error {
+ var request dto.UpdateEntryRequest
+ if err := dto.ReadDto(r, &request); err != nil {
+ return err
+ }
+
+ entry, err := service.UpdateEntryKind(request.Id, request.Kind)
+ if err != nil {
+ return err
+ }
+
+ return &dto.OkResponse{
+ Code: http.StatusOK,
+ Data: entry,
+ }
+ })
+}
+
+func DeleteEntry(service entries.Service) http.HandlerFunc {
+ return dto.WrapResponseFunc(func(w http.ResponseWriter, r *http.Request) error {
+ var request dto.DeleteEntryRequest
+ if err := dto.ReadDto(r, &request); err != nil {
+ return err
+ }
+
+ err := service.DeleteEntry(request.Id)
+ if err != nil {
+ return err
+ }
+
+ return &dto.OkResponse{
+ Code: http.StatusOK,
+ Data: nil,
+ }
+ })
+}
diff --git a/api/handlers/record.go b/api/handlers/record.go
deleted file mode 100644
index cccce6b..0000000
--- a/api/handlers/record.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package handlers
-
-import (
- "crypto/subtle"
- "net/http"
-
- "git.leonardobishop.net/stash/api/dto"
- "git.leonardobishop.net/stash/pkg/entries"
-)
-
-func RecordEntry(service entries.Service, token string) http.HandlerFunc {
- return dto.WrapResponseFunc(func(w http.ResponseWriter, r *http.Request) error {
- var request dto.CreateEntryRequest
- if err := dto.ReadDto(r, &request); err != nil {
- return err
- }
-
- if subtle.ConstantTimeCompare([]byte(token), []byte(request.Token)) != 1 {
- return &dto.ErrorResponse{
- Code: http.StatusForbidden,
- Message: "Forbidden",
- }
- }
-
- entry, err := service.CreateEntry(request.Title, request.Kind, request.Url, request.Description)
- if err != nil {
- return err
- }
-
- return &dto.OkResponse{
- Code: http.StatusCreated,
- Data: entry,
- }
- })
-}
diff --git a/api/middleware/auth.go b/api/middleware/auth.go
new file mode 100644
index 0000000..f6e5c4b
--- /dev/null
+++ b/api/middleware/auth.go
@@ -0,0 +1,51 @@
+package middleware
+
+import (
+ "crypto/subtle"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "git.leonardobishop.net/stash/api/dto"
+)
+
+func MustAuthenticate(token string) func(http.HandlerFunc) http.HandlerFunc {
+ return func(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ authHeader := r.Header.Get("Authorization")
+ givenToken, err := extractBearerToken(authHeader)
+ if err != nil {
+ dto.WriteDto(w, r, &dto.ErrorResponse{
+ Code: http.StatusUnauthorized,
+ Message: "Unauthorized",
+ })
+ return
+ }
+
+ if subtle.ConstantTimeCompare([]byte(token), []byte(givenToken)) != 1 {
+ dto.WriteDto(w, r, &dto.ErrorResponse{
+ Code: http.StatusForbidden,
+ Message: "Forbidden",
+ })
+ return
+ }
+
+ next(w, r)
+ }
+ }
+}
+
+func extractBearerToken(header string) (string, error) {
+ const prefix = "Bearer "
+ if header == "" {
+ return "", fmt.Errorf("authorization header missing")
+ }
+ if !strings.HasPrefix(header, prefix) {
+ return "", fmt.Errorf("invalid authorization scheme")
+ }
+ token := strings.TrimSpace(header[len(prefix):])
+ if token == "" {
+ return "", fmt.Errorf("token is empty")
+ }
+ return token, nil
+}
diff --git a/api/middleware/cors.go b/api/middleware/cors.go
index 926c1ed..1fbd850 100644
--- a/api/middleware/cors.go
+++ b/api/middleware/cors.go
@@ -2,11 +2,13 @@ package middleware
import "net/http"
-func Cors(next http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Origin", "*")
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+func PermissiveCors() func(http.HandlerFunc) http.HandlerFunc {
+ return func(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
- next(w, r)
+ next(w, r)
+ }
}
}
diff --git a/api/router.go b/api/router.go
index 076beec..89abbc0 100644
--- a/api/router.go
+++ b/api/router.go
@@ -20,7 +20,14 @@ type ApiServices struct {
func NewServer(api ApiServices) *http.ServeMux {
mux := http.NewServeMux()
- mux.HandleFunc("POST /record", middleware.Cors(handlers.RecordEntry(api.EntiresService, api.Config.Token)))
+ cors := middleware.PermissiveCors()
+ auth := middleware.MustAuthenticate(api.Config.Token)
+
+ mux.HandleFunc("POST /record", cors(auth(handlers.RecordEntry(api.EntiresService))))
+ mux.HandleFunc("PUT /record", cors(auth(handlers.UpdateEntry(api.EntiresService))))
+ mux.HandleFunc("DELETE /record", cors(auth(handlers.DeleteEntry(api.EntiresService))))
+ mux.HandleFunc("GET /entry", cors(auth(handlers.GetEntryURLs(api.EntiresService))))
+ mux.HandleFunc("POST /entry", cors(auth(handlers.GetEntry(api.EntiresService))))
mux.HandleFunc("GET /html", handlers.GetEntriesHtml(api.EntiresService, api.HtmlService))
mux.HandleFunc("GET /", handlers.Pong())