From 7618acdd625dcfd2011df9bec8e77ade426adcce Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 18 Feb 2024 12:08:32 +0800 Subject: [PATCH 01/17] feat: created authenticator --- backend/go.mod | 10 ++++-- backend/go.sum | 30 ++++++++++++++++++ backend/src/authenticator/auth.go | 52 +++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 backend/src/authenticator/auth.go diff --git a/backend/go.mod b/backend/go.mod index 3fe5a2db..858ffc44 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -11,7 +11,10 @@ require ( ) require ( + github.com/coreos/go-oidc/v3 v3.9.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect @@ -19,7 +22,10 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 5a40a7bb..d7e5a5d1 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,11 +1,22 @@ +github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= +github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gookit/event v1.1.1 h1:heYaXz4N2/0bgvGIwpEEfdW3PAaSWesGHy9/C+ZnZRI= github.com/gookit/event v1.1.1/go.mod h1:n0XbUJ4kqoCwhLOMeyyitM1AKa+dPgUPuy1vSybHXjg= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -20,6 +31,8 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6f github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -34,18 +47,25 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -60,11 +80,21 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/src/authenticator/auth.go b/backend/src/authenticator/auth.go new file mode 100644 index 00000000..090650cc --- /dev/null +++ b/backend/src/authenticator/auth.go @@ -0,0 +1,52 @@ +package authenticator + +import ( + "context" + "errors" + "os" + + "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" +) + +// Authenticator is used to authenticate our users. +type Authenticator struct { + *oidc.Provider + oauth2.Config +} + +func New() (*Authenticator, error) { + provider, err := oidc.NewProvider( + context.Background(), + "https://"+os.Getenv("AUTH0_DOMAIN")+"/", + ) + if err != nil { + return nil, err + } + + conf := oauth2.Config{ + ClientID: os.Getenv("AUTH0_CLIENT_ID"), + ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"), + RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"), + Endpoint: provider.Endpoint(), + Scopes: []string{oidc.ScopeOpenID, "profile"}, + } + + return &Authenticator{ + Provider: provider, + Config: conf, + }, nil +} + +func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) { + rawIDToken, ok := token.Extra("id_token").(string) + if !ok { + return nil, errors.New("no id_token field in oauth2 token") + } + + oidcConfig := &oidc.Config{ + ClientID: a.ClientID, + } + + return a.Verifier(oidcConfig).Verify(ctx, rawIDToken) +} From e37a5e814786c1d9d889b04af0ebff478f1872b6 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 18 Feb 2024 17:35:50 +0800 Subject: [PATCH 02/17] feat: added middleware to authenticate user --- backend/go.mod | 3 + backend/go.sum | 4 ++ backend/src/main.go | 3 + backend/src/middleware/auth_middleware.go | 82 +++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 backend/src/middleware/auth_middleware.go diff --git a/backend/go.mod b/backend/go.mod index 858ffc44..651c09d8 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -10,12 +10,15 @@ require ( go.mongodb.org/mongo-driver v1.13.1 ) +require github.com/gorilla/securecookie v1.1.2 // indirect + require ( github.com/coreos/go-oidc/v3 v3.9.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.1 // indirect + github.com/gorilla/sessions v1.2.2 github.com/klauspost/compress v1.13.6 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index d7e5a5d1..0295fd67 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -23,6 +23,10 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= diff --git a/backend/src/main.go b/backend/src/main.go index d7292af7..3c461329 100644 --- a/backend/src/main.go +++ b/backend/src/main.go @@ -7,6 +7,7 @@ import ( "github.com/gorilla/mux" "github.com/joshtyf/flowforge/src/api" "github.com/joshtyf/flowforge/src/execute" + "github.com/joshtyf/flowforge/src/middleware" ) func main() { @@ -17,6 +18,8 @@ func main() { srm.Start() r := mux.NewRouter() + r.Use(middleware.IsAuthenticated) + r.HandleFunc("/api/healthcheck", api.NewHandler(api.HealthCheck)).Methods("GET") r.HandleFunc("/api/service_request", api.NewHandler(api.CreateServiceRequest)).Methods("POST").Headers("Content-Type", "application/json") r.HandleFunc("/api/service_request/{requestId}", api.NewHandler(api.GetServiceRequest)).Methods("GET") diff --git a/backend/src/middleware/auth_middleware.go b/backend/src/middleware/auth_middleware.go new file mode 100644 index 00000000..34978757 --- /dev/null +++ b/backend/src/middleware/auth_middleware.go @@ -0,0 +1,82 @@ +package middleware + +import ( + "encoding/json" + "errors" + "net/http" + "os" + + "github.com/gorilla/sessions" + + "github.com/joshtyf/flowforge/src/api" + "github.com/joshtyf/flowforge/src/authenticator" + "github.com/joshtyf/flowforge/src/logger" + "golang.org/x/oauth2" +) + +var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) + +func IsAuthenticated(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "application/json") + accessToken := r.Header.Get("Authorization") + session, _ := store.Get(r, accessToken) + + var profile map[string]interface{} + profile, ok := session.Values["profile"].(map[string]interface{}) + + if ok { + logger.Info("[Authorization] Previously authorised", nil) + next.ServeHTTP(w, r) + } + + authToken := &oauth2.Token{AccessToken: accessToken} + if accessToken == "" { + authError := &api.HandlerError{Error: errors.New("authorization token missing"), Code: http.StatusUnauthorized} + w.WriteHeader(authError.Code) + json.NewEncoder(w).Encode(authError) + return + } + + auth, err := authenticator.New() + if err != nil { + logger.Error("[Authorization] Failed to initialize authenticator", map[string]interface{}{"err": err}) + authError := &api.HandlerError{Error: api.ErrInternalServerError, Code: http.StatusInternalServerError} + w.WriteHeader(authError.Code) + json.NewEncoder(w).Encode(authError) + return + } + + idToken, err := auth.VerifyIDToken(r.Context(), authToken) + if err != nil { + logger.Error("[Authorization] Unable to verify token", map[string]interface{}{"err": err}) + authError := &api.HandlerError{Error: errors.New("unable to verify token"), Code: http.StatusUnauthorized} + w.WriteHeader(authError.Code) + json.NewEncoder(w).Encode(authError) + return + } + + err = idToken.Claims(&profile) + if err != nil { + logger.Error("[Authorization] Unable retrieve profile", map[string]interface{}{"err": err}) + authError := &api.HandlerError{Error: errors.New("unable to retrieve profile"), Code: http.StatusInternalServerError} + w.WriteHeader(authError.Code) + json.NewEncoder(w).Encode(authError) + return + } + + session.Values["access_token"] = authToken.AccessToken + session.Values["profile"] = profile + err = session.Save(r, w) + if err != nil { + logger.Error("[Authorization] Unable save session", map[string]interface{}{"err": err}) + authError := &api.HandlerError{Error: errors.New("unable to save session"), Code: http.StatusInternalServerError} + w.WriteHeader(authError.Code) + json.NewEncoder(w).Encode(authError) + return + } + + // Call the next middleware function or final handler + next.ServeHTTP(w, r) + }) +} From 493fd968f8fd2facb4155b9ff3436c2f9a222fcf Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 25 Feb 2024 19:19:50 +0800 Subject: [PATCH 03/17] refactor: updated handler error according to PR#68 --- backend/src/authenticator/auth.go | 1 - backend/src/middleware/auth_middleware.go | 11 +++++------ compose.yaml | 1 + 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/backend/src/authenticator/auth.go b/backend/src/authenticator/auth.go index 090650cc..5f148b10 100644 --- a/backend/src/authenticator/auth.go +++ b/backend/src/authenticator/auth.go @@ -27,7 +27,6 @@ func New() (*Authenticator, error) { conf := oauth2.Config{ ClientID: os.Getenv("AUTH0_CLIENT_ID"), ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"), - RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"), Endpoint: provider.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile"}, } diff --git a/backend/src/middleware/auth_middleware.go b/backend/src/middleware/auth_middleware.go index 34978757..20bd5149 100644 --- a/backend/src/middleware/auth_middleware.go +++ b/backend/src/middleware/auth_middleware.go @@ -2,7 +2,6 @@ package middleware import ( "encoding/json" - "errors" "net/http" "os" @@ -32,7 +31,7 @@ func IsAuthenticated(next http.Handler) http.Handler { authToken := &oauth2.Token{AccessToken: accessToken} if accessToken == "" { - authError := &api.HandlerError{Error: errors.New("authorization token missing"), Code: http.StatusUnauthorized} + authError := &api.HandlerError{Message: "authorization token missing", Code: http.StatusUnauthorized} w.WriteHeader(authError.Code) json.NewEncoder(w).Encode(authError) return @@ -41,7 +40,7 @@ func IsAuthenticated(next http.Handler) http.Handler { auth, err := authenticator.New() if err != nil { logger.Error("[Authorization] Failed to initialize authenticator", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Error: api.ErrInternalServerError, Code: http.StatusInternalServerError} + authError := &api.HandlerError{Message: api.ErrInternalServerError.Error(), Code: http.StatusInternalServerError} w.WriteHeader(authError.Code) json.NewEncoder(w).Encode(authError) return @@ -50,7 +49,7 @@ func IsAuthenticated(next http.Handler) http.Handler { idToken, err := auth.VerifyIDToken(r.Context(), authToken) if err != nil { logger.Error("[Authorization] Unable to verify token", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Error: errors.New("unable to verify token"), Code: http.StatusUnauthorized} + authError := &api.HandlerError{Message: "unable to verify token", Code: http.StatusUnauthorized} w.WriteHeader(authError.Code) json.NewEncoder(w).Encode(authError) return @@ -59,7 +58,7 @@ func IsAuthenticated(next http.Handler) http.Handler { err = idToken.Claims(&profile) if err != nil { logger.Error("[Authorization] Unable retrieve profile", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Error: errors.New("unable to retrieve profile"), Code: http.StatusInternalServerError} + authError := &api.HandlerError{Message: "unable to retrieve profile", Code: http.StatusInternalServerError} w.WriteHeader(authError.Code) json.NewEncoder(w).Encode(authError) return @@ -70,7 +69,7 @@ func IsAuthenticated(next http.Handler) http.Handler { err = session.Save(r, w) if err != nil { logger.Error("[Authorization] Unable save session", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Error: errors.New("unable to save session"), Code: http.StatusInternalServerError} + authError := &api.HandlerError{Message: "unable to save session", Code: http.StatusInternalServerError} w.WriteHeader(authError.Code) json.NewEncoder(w).Encode(authError) return diff --git a/compose.yaml b/compose.yaml index 43af84c1..a5aa6d2f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -20,6 +20,7 @@ services: - be build: context: backend + env_file: .env environment: - MONGO_URI=${MONGO_URI} From ada7e139ee5a128d83f8d7cdbc74569c041d78c3 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 25 Feb 2024 19:22:44 +0800 Subject: [PATCH 04/17] refactor: removed session store implementation for authentication --- backend/src/middleware/auth_middleware.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/backend/src/middleware/auth_middleware.go b/backend/src/middleware/auth_middleware.go index 20bd5149..f6d77559 100644 --- a/backend/src/middleware/auth_middleware.go +++ b/backend/src/middleware/auth_middleware.go @@ -3,9 +3,6 @@ package middleware import ( "encoding/json" "net/http" - "os" - - "github.com/gorilla/sessions" "github.com/joshtyf/flowforge/src/api" "github.com/joshtyf/flowforge/src/authenticator" @@ -13,21 +10,12 @@ import ( "golang.org/x/oauth2" ) -var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) - func IsAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") accessToken := r.Header.Get("Authorization") - session, _ := store.Get(r, accessToken) var profile map[string]interface{} - profile, ok := session.Values["profile"].(map[string]interface{}) - - if ok { - logger.Info("[Authorization] Previously authorised", nil) - next.ServeHTTP(w, r) - } authToken := &oauth2.Token{AccessToken: accessToken} if accessToken == "" { @@ -64,17 +52,6 @@ func IsAuthenticated(next http.Handler) http.Handler { return } - session.Values["access_token"] = authToken.AccessToken - session.Values["profile"] = profile - err = session.Save(r, w) - if err != nil { - logger.Error("[Authorization] Unable save session", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Message: "unable to save session", Code: http.StatusInternalServerError} - w.WriteHeader(authError.Code) - json.NewEncoder(w).Encode(authError) - return - } - // Call the next middleware function or final handler next.ServeHTTP(w, r) }) From 946605bca85c6fbe324a7336892b69af9f6d9061 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 25 Feb 2024 22:48:43 +0800 Subject: [PATCH 05/17] refactor: moved auth middleware to middleware.go under server package and updated http err handling per PR#73 --- backend/src/middleware/auth_middleware.go | 58 ----------------------- backend/src/server/errors.go | 4 ++ backend/src/server/middleware.go | 41 ++++++++++++++++ 3 files changed, 45 insertions(+), 58 deletions(-) delete mode 100644 backend/src/middleware/auth_middleware.go diff --git a/backend/src/middleware/auth_middleware.go b/backend/src/middleware/auth_middleware.go deleted file mode 100644 index f6d77559..00000000 --- a/backend/src/middleware/auth_middleware.go +++ /dev/null @@ -1,58 +0,0 @@ -package middleware - -import ( - "encoding/json" - "net/http" - - "github.com/joshtyf/flowforge/src/api" - "github.com/joshtyf/flowforge/src/authenticator" - "github.com/joshtyf/flowforge/src/logger" - "golang.org/x/oauth2" -) - -func IsAuthenticated(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") - accessToken := r.Header.Get("Authorization") - - var profile map[string]interface{} - - authToken := &oauth2.Token{AccessToken: accessToken} - if accessToken == "" { - authError := &api.HandlerError{Message: "authorization token missing", Code: http.StatusUnauthorized} - w.WriteHeader(authError.Code) - json.NewEncoder(w).Encode(authError) - return - } - - auth, err := authenticator.New() - if err != nil { - logger.Error("[Authorization] Failed to initialize authenticator", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Message: api.ErrInternalServerError.Error(), Code: http.StatusInternalServerError} - w.WriteHeader(authError.Code) - json.NewEncoder(w).Encode(authError) - return - } - - idToken, err := auth.VerifyIDToken(r.Context(), authToken) - if err != nil { - logger.Error("[Authorization] Unable to verify token", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Message: "unable to verify token", Code: http.StatusUnauthorized} - w.WriteHeader(authError.Code) - json.NewEncoder(w).Encode(authError) - return - } - - err = idToken.Claims(&profile) - if err != nil { - logger.Error("[Authorization] Unable retrieve profile", map[string]interface{}{"err": err}) - authError := &api.HandlerError{Message: "unable to retrieve profile", Code: http.StatusInternalServerError} - w.WriteHeader(authError.Code) - json.NewEncoder(w).Encode(authError) - return - } - - // Call the next middleware function or final handler - next.ServeHTTP(w, r) - }) -} diff --git a/backend/src/server/errors.go b/backend/src/server/errors.go index 238da78b..79c0340a 100644 --- a/backend/src/server/errors.go +++ b/backend/src/server/errors.go @@ -12,4 +12,8 @@ var ( ErrServiceRequestNotStarted = errors.New("service request not started") ErrServiceRequestAlreadyStarted = errors.New("service request already started") ErrServiceRequestAlreadyCompleted = errors.New("service request already completed") + + ErrBearerTokenNotFound = errors.New("bearer token not found") + ErrUnableToVerifyBearerToken = errors.New("unable to verify token") + ErrUnableToRetrieveProfile = errors.New("unable to retrieve profile") ) diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index f9c4dd2d..8b70abed 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -3,9 +3,11 @@ package server import ( "net/http" + "github.com/joshtyf/flowforge/src/authenticator" "github.com/joshtyf/flowforge/src/database/models" "github.com/joshtyf/flowforge/src/logger" "github.com/joshtyf/flowforge/src/validation" + "golang.org/x/oauth2" ) func validateCreatePipelineRequest(next http.Handler) http.Handler { @@ -27,3 +29,42 @@ func validateCreatePipelineRequest(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +func IsAuthenticated(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "application/json") + accessToken := r.Header.Get("Authorization") + + authToken := &oauth2.Token{AccessToken: accessToken} + if accessToken == "" { + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrBearerTokenNotFound, http.StatusUnauthorized)) + return + } + + auth, err := authenticator.New() + if err != nil { + logger.Error("[Authorization] Failed to initialize authenticator", map[string]interface{}{"err": err}) + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrInternalServerError, http.StatusInternalServerError)) + return + } + + idToken, err := auth.VerifyIDToken(r.Context(), authToken) + if err != nil { + logger.Error("[Authorization] Unable to verify token", map[string]interface{}{"err": err}) + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnableToVerifyBearerToken, http.StatusUnauthorized)) + return + } + + // TODO: Review to see if this fits flow once frontend side of the flow is finalised + var profile map[string]interface{} + err = idToken.Claims(&profile) + if err != nil { + logger.Error("[Authorization] Unable retrieve profile", map[string]interface{}{"err": err}) + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnableToRetrieveProfile, http.StatusInternalServerError)) + return + } + + // Call the next middleware function or final handler + next.ServeHTTP(w, r) + }) +} From c5100ed5190a789368c3da827795011c3a38b86a Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 25 Feb 2024 22:51:40 +0800 Subject: [PATCH 06/17] fix: added auth middleware to server instantiation --- backend/src/server/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 80425256..4ab53f00 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -35,6 +35,7 @@ func addRoutes(r *mux.Router) { func New() http.Handler { router := mux.NewRouter() addRoutes(router) + router.Use(IsAuthenticated) handlers.CORS( handlers.AllowedOrigins([]string{"http://localhost:3000"}), handlers.AllowedHeaders([]string{ From 87d36b7896ef3552935671e929d91e5835a4c246 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 25 Feb 2024 23:00:50 +0800 Subject: [PATCH 07/17] fix: added string parsing to get access token from bearer token --- backend/src/server/middleware.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index 8b70abed..dbac041b 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -2,6 +2,7 @@ package server import ( "net/http" + "strings" "github.com/joshtyf/flowforge/src/authenticator" "github.com/joshtyf/flowforge/src/database/models" @@ -33,7 +34,7 @@ func validateCreatePipelineRequest(next http.Handler) http.Handler { func IsAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") - accessToken := r.Header.Get("Authorization") + accessToken := strings.TrimSpace(strings.Replace(r.Header.Get("Authorization"), "Bearer", "", 1)) authToken := &oauth2.Token{AccessToken: accessToken} if accessToken == "" { From 9adb4ab22f80d10548d7adca72f8455f97143ac6 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 25 Feb 2024 23:09:58 +0800 Subject: [PATCH 08/17] fix: added missing logging to auth middleware --- backend/src/server/middleware.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index dbac041b..f6150f1f 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -35,9 +35,10 @@ func IsAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") accessToken := strings.TrimSpace(strings.Replace(r.Header.Get("Authorization"), "Bearer", "", 1)) - authToken := &oauth2.Token{AccessToken: accessToken} + if accessToken == "" { + logger.Error("[Authorization] Bearer token not found", nil) encode(w, r, http.StatusInternalServerError, newHandlerError(ErrBearerTokenNotFound, http.StatusUnauthorized)) return } From 7f087b3b9059d381c487cc20dbbb38e78e5731c6 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Fri, 1 Mar 2024 19:09:06 +0800 Subject: [PATCH 09/17] refactor: changed to auth functions for backend api instead of application --- backend/go.mod | 12 ++-- backend/go.sum | 10 ++++ backend/src/authenticator/auth.go | 51 ---------------- backend/src/server/errors.go | 5 +- backend/src/server/helper.go | 13 +++++ backend/src/server/middleware.go | 97 +++++++++++++++++++++++-------- backend/src/server/server.go | 1 - 7 files changed, 106 insertions(+), 83 deletions(-) delete mode 100644 backend/src/authenticator/auth.go diff --git a/backend/go.mod b/backend/go.mod index 6d7ae395..f4198ea1 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -9,7 +9,11 @@ require ( go.mongodb.org/mongo-driver v1.13.1 ) -require github.com/gorilla/securecookie v1.1.2 // indirect +require ( + github.com/auth0/go-jwt-middleware/v2 v2.2.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.2 // indirect +) require ( github.com/coreos/go-oidc/v3 v3.9.0 // indirect @@ -24,10 +28,10 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - golang.org/x/crypto v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 957c3619..662782db 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,3 +1,5 @@ +github.com/auth0/go-jwt-middleware/v2 v2.2.1 h1:pqxEIwlCztD0T9ZygGfOrw4NK/F9iotnCnPJVADKbkE= +github.com/auth0/go-jwt-middleware/v2 v2.2.1/go.mod h1:CSi0tuu0QrALbWdiQZwqFL8SbBhj4e2MJzkvNfjY0Us= github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -55,6 +57,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -66,6 +70,8 @@ golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5H golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -84,6 +90,8 @@ golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -97,6 +105,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-jose/go-jose.v2 v2.6.2 h1:Rl5+9rA0kG3vsO1qhncMPRT5eHICihAMQYJkD7u/i4M= +gopkg.in/go-jose/go-jose.v2 v2.6.2/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/src/authenticator/auth.go b/backend/src/authenticator/auth.go deleted file mode 100644 index 5f148b10..00000000 --- a/backend/src/authenticator/auth.go +++ /dev/null @@ -1,51 +0,0 @@ -package authenticator - -import ( - "context" - "errors" - "os" - - "github.com/coreos/go-oidc/v3/oidc" - "golang.org/x/oauth2" -) - -// Authenticator is used to authenticate our users. -type Authenticator struct { - *oidc.Provider - oauth2.Config -} - -func New() (*Authenticator, error) { - provider, err := oidc.NewProvider( - context.Background(), - "https://"+os.Getenv("AUTH0_DOMAIN")+"/", - ) - if err != nil { - return nil, err - } - - conf := oauth2.Config{ - ClientID: os.Getenv("AUTH0_CLIENT_ID"), - ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"), - Endpoint: provider.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "profile"}, - } - - return &Authenticator{ - Provider: provider, - Config: conf, - }, nil -} - -func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) { - rawIDToken, ok := token.Extra("id_token").(string) - if !ok { - return nil, errors.New("no id_token field in oauth2 token") - } - - oidcConfig := &oidc.Config{ - ClientID: a.ClientID, - } - - return a.Verifier(oidcConfig).Verify(ctx, rawIDToken) -} diff --git a/backend/src/server/errors.go b/backend/src/server/errors.go index 79c0340a..252175e0 100644 --- a/backend/src/server/errors.go +++ b/backend/src/server/errors.go @@ -13,7 +13,6 @@ var ( ErrServiceRequestAlreadyStarted = errors.New("service request already started") ErrServiceRequestAlreadyCompleted = errors.New("service request already completed") - ErrBearerTokenNotFound = errors.New("bearer token not found") - ErrUnableToVerifyBearerToken = errors.New("unable to verify token") - ErrUnableToRetrieveProfile = errors.New("unable to retrieve profile") + ErrUnableToValidateJWT = errors.New("unable to validate JWT") + ErrUnauthorised = errors.New("user does not have required permissions") ) diff --git a/backend/src/server/helper.go b/backend/src/server/helper.go index 5131cb4f..4ba0ba50 100644 --- a/backend/src/server/helper.go +++ b/backend/src/server/helper.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" ) func encode[T any](w http.ResponseWriter, r *http.Request, status int, v T) error { @@ -27,3 +28,15 @@ func serverHealthy() bool { // TODO: Include database health check return true } + +// HasScope checks whether our claims have a specific scope. +func (c CustomClaims) HasScope(expectedScope string) bool { + result := strings.Split(c.Scope, " ") + for i := range result { + if result[i] == expectedScope { + return true + } + } + + return false +} diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index f6150f1f..ef001470 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -1,14 +1,18 @@ package server import ( + "context" "net/http" - "strings" + "net/url" + "os" + "time" - "github.com/joshtyf/flowforge/src/authenticator" + jwtmiddleware "github.com/auth0/go-jwt-middleware/v2" + "github.com/auth0/go-jwt-middleware/v2/jwks" + "github.com/auth0/go-jwt-middleware/v2/validator" "github.com/joshtyf/flowforge/src/database/models" "github.com/joshtyf/flowforge/src/logger" "github.com/joshtyf/flowforge/src/validation" - "golang.org/x/oauth2" ) func validateCreatePipelineRequest(next http.Handler) http.Handler { @@ -31,42 +35,87 @@ func validateCreatePipelineRequest(next http.Handler) http.Handler { }) } +// CustomClaims contains custom data we want from the token. +type CustomClaims struct { + Scope string `json:"scope"` +} + +// Validate does nothing for this example, but we need +// it to satisfy validator.CustomClaims interface. +func (c CustomClaims) Validate(ctx context.Context) error { + return nil +} + func IsAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") - accessToken := strings.TrimSpace(strings.Replace(r.Header.Get("Authorization"), "Bearer", "", 1)) - authToken := &oauth2.Token{AccessToken: accessToken} - - if accessToken == "" { - logger.Error("[Authorization] Bearer token not found", nil) - encode(w, r, http.StatusInternalServerError, newHandlerError(ErrBearerTokenNotFound, http.StatusUnauthorized)) + issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/") + logger.Info(issuerURL.String(), nil) + if err != nil { + logger.Error("[Authentication] Failed to parse the issuer url", map[string]interface{}{"err": err}) + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrInternalServerError, http.StatusInternalServerError)) return } - auth, err := authenticator.New() + provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute) + + jwtValidator, err := validator.New( + provider.KeyFunc, + validator.RS256, + issuerURL.String(), + []string{os.Getenv("AUTH0_AUDIENCE")}, + validator.WithCustomClaims( + func() validator.CustomClaims { + return &CustomClaims{} + }, + ), + validator.WithAllowedClockSkew(time.Minute), + ) if err != nil { - logger.Error("[Authorization] Failed to initialize authenticator", map[string]interface{}{"err": err}) + logger.Error("[Authentication] Failed to set up jwt validator", map[string]interface{}{"err": err}) encode(w, r, http.StatusInternalServerError, newHandlerError(ErrInternalServerError, http.StatusInternalServerError)) return } - idToken, err := auth.VerifyIDToken(r.Context(), authToken) - if err != nil { - logger.Error("[Authorization] Unable to verify token", map[string]interface{}{"err": err}) - encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnableToVerifyBearerToken, http.StatusUnauthorized)) - return + errorHandler := func(w http.ResponseWriter, r *http.Request, err error) { + logger.Error("[Authentication] Encountered error while validating JWT", map[string]interface{}{"err": err}) + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnableToValidateJWT, http.StatusUnauthorized)) } - // TODO: Review to see if this fits flow once frontend side of the flow is finalised - var profile map[string]interface{} - err = idToken.Claims(&profile) - if err != nil { - logger.Error("[Authorization] Unable retrieve profile", map[string]interface{}{"err": err}) - encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnableToRetrieveProfile, http.StatusInternalServerError)) + middleware := jwtmiddleware.New( + jwtValidator.ValidateToken, + jwtmiddleware.WithErrorHandler(errorHandler), + ) + logger.Info("Validating", nil) + middleware.CheckJWT(next).ServeHTTP(w, r) + }) + +} + +// TODO: To be tested once frontend is ready +func isAuthorisedAdmin(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims) + claims := token.CustomClaims.(*CustomClaims) + logger.Info(claims.Scope, nil) + if !claims.HasScope("admin") { + logger.Error("[Authorization] User not authorized admin", nil) + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnauthorised, http.StatusForbidden)) return } + next.ServeHTTP(w, r) + }) +} - // Call the next middleware function or final handler +func isAuthorisedUser(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger.Info("Authorizing", nil) + token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims) + claims := token.CustomClaims.(*CustomClaims) + if !claims.HasScope("test") { + logger.Error("[Authorization] User not authorized user", nil) + encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnauthorised, http.StatusForbidden)) + return + } next.ServeHTTP(w, r) }) } diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 4ab53f00..80425256 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -35,7 +35,6 @@ func addRoutes(r *mux.Router) { func New() http.Handler { router := mux.NewRouter() addRoutes(router) - router.Use(IsAuthenticated) handlers.CORS( handlers.AllowedOrigins([]string{"http://localhost:3000"}), handlers.AllowedHeaders([]string{ From f31b28eb2cb22abac3d586d86595d13efd2e126e Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Fri, 1 Mar 2024 19:11:10 +0800 Subject: [PATCH 10/17] fix: changed healthcheck message sent to string instead of bytes as encode cannot encode bytes properly --- backend/src/server/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/server/handlers.go b/backend/src/server/handlers.go index ac805373..bfee2477 100644 --- a/backend/src/server/handlers.go +++ b/backend/src/server/handlers.go @@ -26,7 +26,7 @@ func newHandlerError(err error, code int) *HandlerError { func handleHealthCheck() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if serverHealthy() { - encode(w, r, http.StatusOK, []byte("Server working!")) + encode(w, r, http.StatusOK, "Server working!") return } encode(w, r, http.StatusInternalServerError, newHandlerError(ErrInternalServerError, http.StatusInternalServerError)) From e490f66c99f012ae878160f4f1a47480be5a9c21 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 3 Mar 2024 16:19:47 +0800 Subject: [PATCH 11/17] refactor: applied respective authentication function to routes --- backend/src/server/middleware.go | 2 +- backend/src/server/server.go | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index ef001470..d0e4cbb8 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -46,7 +46,7 @@ func (c CustomClaims) Validate(ctx context.Context) error { return nil } -func IsAuthenticated(next http.Handler) http.Handler { +func isAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/") logger.Info(issuerURL.String(), nil) diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 80425256..21606855 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -15,21 +15,21 @@ func addRoutes(r *mux.Router) { } // Health Check - r.Handle("/api/healthcheck", handleHealthCheck()).Methods("GET") + r.Handle("/api/healthcheck", isAuthenticated(isAuthorisedAdmin(handleHealthCheck()))).Methods("GET") // Service Request - r.Handle("/api/service_request", handleGetAllServiceRequest(mongoClient)).Methods("GET") - r.Handle("/api/service_request/{requestId}", handleGetServiceRequest(mongoClient)).Methods("GET") - r.Handle("/api/service_request", handleCreateServiceRequest(mongoClient)).Methods("POST").Headers("Content-Type", "application/json") - r.Handle("/api/service_request/{requestId}", handleUpdateServiceRequest(mongoClient)).Methods("PATCH").Headers("Content-Type", "application/json") - r.Handle("/api/service_request/{requestId}/cancel", handleCancelStartedServiceRequest(mongoClient)).Methods("GET") - r.Handle("/api/service_request/{requestId}/start", handleStartServiceRequest(mongoClient)).Methods("GET") - r.Handle("/api/service_request/{requestId}/approve", handleApproveServiceRequest(mongoClient)).Methods("POST").Headers("Content-Type", "application/json") + r.Handle("/api/service_request", isAuthenticated(isAuthorisedUser(handleGetAllServiceRequest(mongoClient)))).Methods("GET") + r.Handle("/api/service_request/{requestId}", isAuthenticated(isAuthorisedUser(handleGetServiceRequest(mongoClient)))).Methods("GET") + r.Handle("/api/service_request", isAuthenticated(isAuthorisedUser(handleCreateServiceRequest(mongoClient)))).Methods("POST").Headers("Content-Type", "application/json") + r.Handle("/api/service_request/{requestId}", isAuthenticated(isAuthorisedUser(handleUpdateServiceRequest(mongoClient)))).Methods("PATCH").Headers("Content-Type", "application/json") + r.Handle("/api/service_request/{requestId}/cancel", isAuthenticated(isAuthorisedUser(handleCancelStartedServiceRequest(mongoClient)))).Methods("GET") + r.Handle("/api/service_request/{requestId}/start", isAuthenticated(isAuthorisedUser(handleStartServiceRequest(mongoClient)))).Methods("GET") + r.Handle("/api/service_request/{requestId}/approve", isAuthenticated(isAuthorisedAdmin(handleApproveServiceRequest(mongoClient)))).Methods("POST").Headers("Content-Type", "application/json") // Pipeline - r.Handle("/api/pipeline", handleGetAllPipelines(mongoClient)).Methods("GET") - r.Handle("/api/pipeline/{pipelineId}", handleGetPipeline(mongoClient)).Methods("GET") - r.Handle("/api/pipeline", validateCreatePipelineRequest(handleCreatePipeline(mongoClient))).Methods("POST").Headers("Content-Type", "application/json") + r.Handle("/api/pipeline", isAuthenticated(isAuthorisedUser(handleGetAllPipelines(mongoClient)))).Methods("GET") + r.Handle("/api/pipeline/{pipelineId}", isAuthenticated(isAuthorisedUser(handleGetPipeline(mongoClient)))).Methods("GET") + r.Handle("/api/pipeline", isAuthenticated(isAuthorisedAdmin(validateCreatePipelineRequest(handleCreatePipeline(mongoClient))))).Methods("POST").Headers("Content-Type", "application/json") } func New() http.Handler { From 2afbff7a99270d72b273488d65c5f1d77a18147e Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 3 Mar 2024 17:02:49 +0800 Subject: [PATCH 12/17] refactor: removed env_file setting from docker compose and specified env variables required --- compose.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index a5aa6d2f..1525e62b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -20,9 +20,10 @@ services: - be build: context: backend - env_file: .env environment: - MONGO_URI=${MONGO_URI} + - AUTH0_DOMAIN=${AUTH0_DOMAIN} + - AUTH0_AUDIENCE=${AUTH0_AUDIENCE} postgres: profiles: From a56bd7fd167e685012a45915be4ad973029bb091 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Sun, 3 Mar 2024 17:04:17 +0800 Subject: [PATCH 13/17] refactor: removed uneccessary logging from auth functions --- backend/src/server/middleware.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index d0e4cbb8..f374edfb 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -85,7 +85,6 @@ func isAuthenticated(next http.Handler) http.Handler { jwtValidator.ValidateToken, jwtmiddleware.WithErrorHandler(errorHandler), ) - logger.Info("Validating", nil) middleware.CheckJWT(next).ServeHTTP(w, r) }) @@ -96,7 +95,6 @@ func isAuthorisedAdmin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims) claims := token.CustomClaims.(*CustomClaims) - logger.Info(claims.Scope, nil) if !claims.HasScope("admin") { logger.Error("[Authorization] User not authorized admin", nil) encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnauthorised, http.StatusForbidden)) @@ -108,7 +106,6 @@ func isAuthorisedAdmin(next http.Handler) http.Handler { func isAuthorisedUser(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logger.Info("Authorizing", nil) token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims) claims := token.CustomClaims.(*CustomClaims) if !claims.HasScope("test") { From 59bd6858e05d20b5bae2cad596189405645cd7b7 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Thu, 7 Mar 2024 23:47:03 +0800 Subject: [PATCH 14/17] refactor: reduced authorization functions to only one to differentiate between admin and user --- backend/src/server/helper.go | 9 +++++---- backend/src/server/middleware.go | 17 ++--------------- backend/src/server/server.go | 16 ++++++++-------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/backend/src/server/helper.go b/backend/src/server/helper.go index 4ba0ba50..b861e75d 100644 --- a/backend/src/server/helper.go +++ b/backend/src/server/helper.go @@ -29,11 +29,12 @@ func serverHealthy() bool { return true } -// HasScope checks whether our claims have a specific scope. -func (c CustomClaims) HasScope(expectedScope string) bool { - result := strings.Split(c.Scope, " ") +// HasPermission checks whether our claims have a specific permission. +// In our case, since we are using this to check if user is admin, will be checking for approve:pipeline_step permission +func (c CustomClaims) HasPermission(expectedPermission string) bool { + result := strings.Split(c.Permissions, ",") for i := range result { - if result[i] == expectedScope { + if result[i] == expectedPermission { return true } } diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index f374edfb..8106bc31 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -37,7 +37,7 @@ func validateCreatePipelineRequest(next http.Handler) http.Handler { // CustomClaims contains custom data we want from the token. type CustomClaims struct { - Scope string `json:"scope"` + Permissions string `json:"permissions"` } // Validate does nothing for this example, but we need @@ -95,7 +95,7 @@ func isAuthorisedAdmin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims) claims := token.CustomClaims.(*CustomClaims) - if !claims.HasScope("admin") { + if !claims.HasPermission("approve:pipeline_step") { logger.Error("[Authorization] User not authorized admin", nil) encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnauthorised, http.StatusForbidden)) return @@ -103,16 +103,3 @@ func isAuthorisedAdmin(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } - -func isAuthorisedUser(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims) - claims := token.CustomClaims.(*CustomClaims) - if !claims.HasScope("test") { - logger.Error("[Authorization] User not authorized user", nil) - encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnauthorised, http.StatusForbidden)) - return - } - next.ServeHTTP(w, r) - }) -} diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 21606855..16f5e061 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -18,17 +18,17 @@ func addRoutes(r *mux.Router) { r.Handle("/api/healthcheck", isAuthenticated(isAuthorisedAdmin(handleHealthCheck()))).Methods("GET") // Service Request - r.Handle("/api/service_request", isAuthenticated(isAuthorisedUser(handleGetAllServiceRequest(mongoClient)))).Methods("GET") - r.Handle("/api/service_request/{requestId}", isAuthenticated(isAuthorisedUser(handleGetServiceRequest(mongoClient)))).Methods("GET") - r.Handle("/api/service_request", isAuthenticated(isAuthorisedUser(handleCreateServiceRequest(mongoClient)))).Methods("POST").Headers("Content-Type", "application/json") - r.Handle("/api/service_request/{requestId}", isAuthenticated(isAuthorisedUser(handleUpdateServiceRequest(mongoClient)))).Methods("PATCH").Headers("Content-Type", "application/json") - r.Handle("/api/service_request/{requestId}/cancel", isAuthenticated(isAuthorisedUser(handleCancelStartedServiceRequest(mongoClient)))).Methods("GET") - r.Handle("/api/service_request/{requestId}/start", isAuthenticated(isAuthorisedUser(handleStartServiceRequest(mongoClient)))).Methods("GET") + r.Handle("/api/service_request", isAuthenticated(handleGetAllServiceRequest(mongoClient))).Methods("GET") + r.Handle("/api/service_request/{requestId}", isAuthenticated(handleGetServiceRequest(mongoClient))).Methods("GET") + r.Handle("/api/service_request", isAuthenticated(handleCreateServiceRequest(mongoClient))).Methods("POST").Headers("Content-Type", "application/json") + r.Handle("/api/service_request/{requestId}", isAuthenticated(handleUpdateServiceRequest(mongoClient))).Methods("PATCH").Headers("Content-Type", "application/json") + r.Handle("/api/service_request/{requestId}/cancel", isAuthenticated(handleCancelStartedServiceRequest(mongoClient))).Methods("GET") + r.Handle("/api/service_request/{requestId}/start", isAuthenticated(handleStartServiceRequest(mongoClient))).Methods("GET") r.Handle("/api/service_request/{requestId}/approve", isAuthenticated(isAuthorisedAdmin(handleApproveServiceRequest(mongoClient)))).Methods("POST").Headers("Content-Type", "application/json") // Pipeline - r.Handle("/api/pipeline", isAuthenticated(isAuthorisedUser(handleGetAllPipelines(mongoClient)))).Methods("GET") - r.Handle("/api/pipeline/{pipelineId}", isAuthenticated(isAuthorisedUser(handleGetPipeline(mongoClient)))).Methods("GET") + r.Handle("/api/pipeline", isAuthenticated(handleGetAllPipelines(mongoClient))).Methods("GET") + r.Handle("/api/pipeline/{pipelineId}", isAuthenticated(handleGetPipeline(mongoClient))).Methods("GET") r.Handle("/api/pipeline", isAuthenticated(isAuthorisedAdmin(validateCreatePipelineRequest(handleCreatePipeline(mongoClient))))).Methods("POST").Headers("Content-Type", "application/json") } From bd45275e35ee1dc79051e245221f134afee07a1b Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Thu, 7 Mar 2024 23:51:09 +0800 Subject: [PATCH 15/17] fix: restricted pipeline resources to admins and unrestricted healthcheck resource --- backend/src/server/middleware.go | 3 --- backend/src/server/server.go | 6 +++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index 8106bc31..52b20b48 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -40,8 +40,6 @@ type CustomClaims struct { Permissions string `json:"permissions"` } -// Validate does nothing for this example, but we need -// it to satisfy validator.CustomClaims interface. func (c CustomClaims) Validate(ctx context.Context) error { return nil } @@ -49,7 +47,6 @@ func (c CustomClaims) Validate(ctx context.Context) error { func isAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/") - logger.Info(issuerURL.String(), nil) if err != nil { logger.Error("[Authentication] Failed to parse the issuer url", map[string]interface{}{"err": err}) encode(w, r, http.StatusInternalServerError, newHandlerError(ErrInternalServerError, http.StatusInternalServerError)) diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 16f5e061..9c2722f4 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -15,7 +15,7 @@ func addRoutes(r *mux.Router) { } // Health Check - r.Handle("/api/healthcheck", isAuthenticated(isAuthorisedAdmin(handleHealthCheck()))).Methods("GET") + r.Handle("/api/healthcheck", handleHealthCheck()).Methods("GET") // Service Request r.Handle("/api/service_request", isAuthenticated(handleGetAllServiceRequest(mongoClient))).Methods("GET") @@ -27,8 +27,8 @@ func addRoutes(r *mux.Router) { r.Handle("/api/service_request/{requestId}/approve", isAuthenticated(isAuthorisedAdmin(handleApproveServiceRequest(mongoClient)))).Methods("POST").Headers("Content-Type", "application/json") // Pipeline - r.Handle("/api/pipeline", isAuthenticated(handleGetAllPipelines(mongoClient))).Methods("GET") - r.Handle("/api/pipeline/{pipelineId}", isAuthenticated(handleGetPipeline(mongoClient))).Methods("GET") + r.Handle("/api/pipeline", isAuthenticated(isAuthorisedAdmin(handleGetAllPipelines(mongoClient)))).Methods("GET") + r.Handle("/api/pipeline/{pipelineId}", isAuthenticated(isAuthorisedAdmin(handleGetPipeline(mongoClient)))).Methods("GET") r.Handle("/api/pipeline", isAuthenticated(isAuthorisedAdmin(validateCreatePipelineRequest(handleCreatePipeline(mongoClient))))).Methods("POST").Headers("Content-Type", "application/json") } From cdac4e5c123b4d5d2560e31b89524dcc095e4f89 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Thu, 7 Mar 2024 23:54:08 +0800 Subject: [PATCH 16/17] fix: merge conflict in compose.yaml --- compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/compose.yaml b/compose.yaml index 1525e62b..986610ea 100644 --- a/compose.yaml +++ b/compose.yaml @@ -22,6 +22,7 @@ services: context: backend environment: - MONGO_URI=${MONGO_URI} + - POSTGRES_URI=${POSTGRES_URI} - AUTH0_DOMAIN=${AUTH0_DOMAIN} - AUTH0_AUDIENCE=${AUTH0_AUDIENCE} From c3963939caa51a283b437ee76674cc9070219568 Mon Sep 17 00:00:00 2001 From: Zheng_Zhi_Qiang Date: Fri, 8 Mar 2024 17:58:12 +0800 Subject: [PATCH 17/17] refactor: moved permission check function from helper to middleware --- backend/src/server/helper.go | 14 -------------- backend/src/server/middleware.go | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/src/server/helper.go b/backend/src/server/helper.go index b861e75d..5131cb4f 100644 --- a/backend/src/server/helper.go +++ b/backend/src/server/helper.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "strings" ) func encode[T any](w http.ResponseWriter, r *http.Request, status int, v T) error { @@ -28,16 +27,3 @@ func serverHealthy() bool { // TODO: Include database health check return true } - -// HasPermission checks whether our claims have a specific permission. -// In our case, since we are using this to check if user is admin, will be checking for approve:pipeline_step permission -func (c CustomClaims) HasPermission(expectedPermission string) bool { - result := strings.Split(c.Permissions, ",") - for i := range result { - if result[i] == expectedPermission { - return true - } - } - - return false -} diff --git a/backend/src/server/middleware.go b/backend/src/server/middleware.go index 52b20b48..e6ab70b3 100644 --- a/backend/src/server/middleware.go +++ b/backend/src/server/middleware.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "os" + "strings" "time" jwtmiddleware "github.com/auth0/go-jwt-middleware/v2" @@ -44,6 +45,19 @@ func (c CustomClaims) Validate(ctx context.Context) error { return nil } +// HasPermission checks whether our claims have a specific permission. +// In our case, since we are using this to check if user is admin, will be checking for approve:pipeline_step permission +func (c CustomClaims) HasPermission(expectedPermission string) bool { + result := strings.Split(c.Permissions, ",") + for i := range result { + if result[i] == expectedPermission { + return true + } + } + + return false +} + func isAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")