diff --git a/go.work b/go.work index 19c8897bd2b739a73a2cf80d186f3e2d6411a607..dcea92909512d2a9c9a27bf8ce438202182d95c5 100644 --- a/go.work +++ b/go.work @@ -1,3 +1,4 @@ go 1.24.2 use ./src/myaktion +use ./src/genjwt diff --git a/src/genjwt/go.mod b/src/genjwt/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..82681670a517365d728335efa17bea29f0aae37b --- /dev/null +++ b/src/genjwt/go.mod @@ -0,0 +1,5 @@ +module gitlab.reutlingen-university.de/petrinov/myaktion-go/src/genjwt + +go 1.24.2 + +require github.com/golang-jwt/jwt v3.2.2+incompatible diff --git a/src/genjwt/go.sum b/src/genjwt/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..efdb2a9a1c9fc7151e30281a38c6ffc7e71f6fc9 --- /dev/null +++ b/src/genjwt/go.sum @@ -0,0 +1,2 @@ +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= diff --git a/src/genjwt/main.go b/src/genjwt/main.go new file mode 100644 index 0000000000000000000000000000000000000000..5530449d2420e8e9d1573ca6897539ac43266097 --- /dev/null +++ b/src/genjwt/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/golang-jwt/jwt" +) + +var secretKey = []byte("myaktion-go-secret-key") + +func createToken(username string) (string, error) { + claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": username, + "iss": "myaktion-go", + "aud": "organizer", + "exp": time.Now().Add(time.Hour).Unix(), + "iat": time.Now().Unix(), + }) + fmt.Printf("Token claims added: %+v\n", claims) + tokenString, err := claims.SignedString(secretKey) + if err != nil { + return "", err + } + return tokenString, nil +} + +func main() { + if len(os.Args) >= 2 { + organizerName := os.Args[1] + tokenString, err := createToken(organizerName) + if err != nil { + fmt.Printf("Unable to create token: %+v\n", err) + } else { + fmt.Printf("%+v\n", tokenString) + } + } else { + fmt.Println("Please pass a username as command-line argument") + } +} diff --git a/src/myaktion/go.mod b/src/myaktion/go.mod index c264c6ba589036dc69399b01052363e198f79442..65ae3da3becae449b0e0ec14a87521050a606f95 100644 --- a/src/myaktion/go.mod +++ b/src/myaktion/go.mod @@ -12,6 +12,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect golang.org/x/sys v0.5.0 // indirect diff --git a/src/myaktion/go.sum b/src/myaktion/go.sum index 73258fa27d750b62d2bb532e4c9403502550d213..99cba316e4e6f963dc137d2b5635d43672a81b4c 100644 --- a/src/myaktion/go.sum +++ b/src/myaktion/go.sum @@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= diff --git a/src/myaktion/handler/campaign.go b/src/myaktion/handler/campaign.go index db13402861063d10df0f24b0f11ae9aaa79a3d36..4e990050688f6d8048061761686e6a5ebb909671 100644 --- a/src/myaktion/handler/campaign.go +++ b/src/myaktion/handler/campaign.go @@ -64,8 +64,8 @@ func GetCampaign(w http.ResponseWriter, r *http.Request) { sendJson(w, campaign) } -func GetCampaigns(w http.ResponseWriter, _ *http.Request) { - campaigns, err := service.GetCampaigns() +func GetCampaigns(w http.ResponseWriter, r *http.Request) { + campaigns, err := service.GetCampaigns(getOrganizerName(r)) if err != nil { log.Printf("Error calling service GetCampaigns: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -118,6 +118,7 @@ func getCampaign(r *http.Request) (*model.Campaign, error) { log.Errorf("Can't serialize request body to campaign struct: %v", err) return nil, err } + campaign.OrganizerName = getOrganizerName(r) return &campaign, nil } @@ -159,3 +160,7 @@ func PatchCampaign(w http.ResponseWriter, r *http.Request) { }*/ } + +func getOrganizerName(r *http.Request) string { + return r.Context().Value("organizerName").(string) +} diff --git a/src/myaktion/main.go b/src/myaktion/main.go index dd990e4f9c02b8b118d0f4bd3714c588dd84a5df..be4edb3c2b31f85de2f7e51f554cfde74098f103 100644 --- a/src/myaktion/main.go +++ b/src/myaktion/main.go @@ -1,14 +1,68 @@ package main import ( + "context" + "fmt" "net/http" "os" + "strings" + "github.com/golang-jwt/jwt" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" "gitlab.reutlingen-university.de/petrinov/myaktion-go/handler" ) +var secretKey = []byte("myaktion-go-secret-key") + +func authMW(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tokenString := r.Header.Get("Authorization") + if tokenString == "" { + w.WriteHeader(http.StatusUnauthorized) + return + } + log.Info(tokenString) + tokenString = strings.Replace(tokenString, "Bearer ", "", 1) + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method") + } + return []byte(secretKey), nil + }) + if err != nil { + log.Errorf("Can' parse token string: %+v\n", err) + w.WriteHeader(http.StatusUnauthorized) + return + } + if !token.Valid { + log.Errorf("Token is not valid: %+v\n", err) + w.WriteHeader(http.StatusUnauthorized) + return + } + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + log.Errorf("Can' extract claims from token: %+v\n", err) + w.WriteHeader(http.StatusUnauthorized) + return + } + organizerName, ok := claims["sub"].(string) + if !ok { + log.Errorf("Can' read organizerName from token: %+v\n", err) + w.WriteHeader(http.StatusUnauthorized) + return + } + if organizerName == "" { + log.Errorf("OrganizerName is an empty string: %+v\n", err) + w.WriteHeader(http.StatusUnauthorized) + return + } + ctx := context.WithValue(r.Context(), "organizerName", organizerName) + next(w, r.WithContext(ctx)) + } +} + func init() { log.SetFormatter(&log.TextFormatter{}) log.SetReportCaller(true) @@ -25,9 +79,9 @@ func main() { router := mux.NewRouter() router.HandleFunc("/health", handler.Health).Methods("GET") - router.HandleFunc("/campaigns", handler.CreateCampaign).Methods("POST") + router.HandleFunc("/campaigns", authMW(handler.CreateCampaign)).Methods("POST") router.HandleFunc("/campaigns/{id}", handler.UpdateCampaign).Methods("PUT") - router.HandleFunc("/campaigns/{id}", handler.GetCampaign).Methods("GET") + router.HandleFunc("/campaigns/{id}", authMW(handler.GetCampaign)).Methods("GET") router.HandleFunc("/campaigns", handler.GetCampaigns).Methods("GET") router.HandleFunc("/campaigns/{id}", handler.DeleteCampaign).Methods("DELETE") router.HandleFunc("/campaigns/{id}", handler.PatchCampaign).Methods("PATCH") diff --git a/src/myaktion/service/campaign.go b/src/myaktion/service/campaign.go index 36e077a99368a224595b8e2ffe529fe26288ac65..8c0c9d6e12b69fef5cffd42135005db704fecf6f 100644 --- a/src/myaktion/service/campaign.go +++ b/src/myaktion/service/campaign.go @@ -19,9 +19,9 @@ func CreateCampaign(campaign *model.Campaign) error { } -func GetCampaigns() ([]model.Campaign, error) { +func GetCampaigns(organizerName string) ([]model.Campaign, error) { var campaigns []model.Campaign - result := db.DB.Preload("Donations").Find(&campaigns) + result := db.DB.Preload("Donations").Where("organizer_name = ?", organizerName).Find(&campaigns) if result.Error != nil { return nil, result.Error