From 963d210507eea66b020471dfa118acd4806d3aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 31 Oct 2020 12:27:55 +0100 Subject: [PATCH] use custom JWT middleware. --- go.mod | 1 + internal/api/member/handler.go | 13 ++------ internal/api/room/handler.go | 17 +++++----- internal/api/router.go | 39 +++++++--------------- internal/api/utils/auth.go | 59 ++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 internal/api/utils/auth.go diff --git a/go.mod b/go.mod index 79bb48b6..ce55586f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module demodesk/neko go 1.13 require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-chi/chi v4.1.0+incompatible github.com/go-chi/render v1.0.1 diff --git a/internal/api/member/handler.go b/internal/api/member/handler.go index 427f096b..1330bbe8 100644 --- a/internal/api/member/handler.go +++ b/internal/api/member/handler.go @@ -3,6 +3,7 @@ package member import ( "github.com/go-chi/chi" + "demodesk/neko/internal/api/utils" "demodesk/neko/internal/types" ) @@ -24,18 +25,10 @@ func New( } func (h *MemberHandler) Router( - usersOnly func(chi.Router, func(chi.Router)), - adminsOnly func(chi.Router, func(chi.Router)), + usersOnly utils.HttpMiddleware, + adminsOnly utils.HttpMiddleware, ) *chi.Mux { r := chi.NewRouter() - usersOnly(r, func(r chi.Router) { - - }) - - adminsOnly(r, func(r chi.Router) { - - }) - return r } diff --git a/internal/api/room/handler.go b/internal/api/room/handler.go index f30af980..2dbbb455 100644 --- a/internal/api/room/handler.go +++ b/internal/api/room/handler.go @@ -3,6 +3,7 @@ package room import ( "github.com/go-chi/chi" + "demodesk/neko/internal/api/utils" "demodesk/neko/internal/types" ) @@ -30,18 +31,16 @@ func New( } func (h *RoomHandler) Router( - usersOnly func(chi.Router, func(chi.Router)), - adminsOnly func(chi.Router, func(chi.Router)), + usersOnly utils.HttpMiddleware, + adminsOnly utils.HttpMiddleware, ) *chi.Mux { r := chi.NewRouter() - - usersOnly(r, func(r chi.Router) { - r.Get("/screen", h.ScreenConfiguration) - }) - adminsOnly(r, func(r chi.Router) { - r.Post("/screen", h.ScreenConfigurationChange) - r.Get("/screen/configurations", h.ScreenConfigurationsList) + r.Route("/screen", func(r chi.Router) { + r.With(usersOnly).Get("/", h.ScreenConfiguration) + r.With(adminsOnly).Post("/", h.ScreenConfigurationChange) + + r.With(adminsOnly).Get("/configurations", h.ScreenConfigurationsList) }) return r diff --git a/internal/api/router.go b/internal/api/router.go index 9d04433c..551c1ba1 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -1,13 +1,15 @@ package api import ( + "net/http" + "github.com/go-chi/chi" - "github.com/go-chi/jwtauth" - + "demodesk/neko/internal/api/member" "demodesk/neko/internal/api/room" "demodesk/neko/internal/types" "demodesk/neko/internal/types/config" + "demodesk/neko/internal/api/utils" ) type API struct { @@ -17,8 +19,8 @@ type API struct { websocket types.WebSocketHandler } -var AdminToken *jwtauth.JWTAuth -var UserToken *jwtauth.JWTAuth +var AdminToken []byte +var UserToken []byte func New( sessions types.SessionManager, @@ -27,8 +29,8 @@ func New( websocket types.WebSocketHandler, conf *config.Server, ) *API { - AdminToken = jwtauth.New("HS256", []byte(conf.AdminToken), nil) - UserToken = jwtauth.New("HS256", []byte(conf.UserToken), nil) + AdminToken = []byte(conf.AdminToken) + UserToken = []byte(conf.UserToken) return &API{ sessions: sessions, @@ -46,27 +48,10 @@ func (a *API) Mount(r *chi.Mux) { r.Mount("/room", roomHandler.Router(UsersOnly, AdminsOnly)) } -func UsersOnly(r chi.Router, protectedRoutes func(r chi.Router)) { - r.Group(func(r chi.Router) { - // Verify JWT tokens - r.Use(jwtauth.Verifier(UserToken)) - r.Use(jwtauth.Verifier(AdminToken)) - - // Handle valid / invalid tokens. - r.Use(jwtauth.Authenticator) - - protectedRoutes(r) - }) +func UsersOnly(next http.Handler) http.Handler { + return utils.AuthMiddleware(next, UserToken, AdminToken) } -func AdminsOnly(r chi.Router, protectedRoutes func(r chi.Router)) { - r.Group(func(r chi.Router) { - // Verify JWT token - r.Use(jwtauth.Verifier(AdminToken)) - - // Handle valid / invalid tokens. - r.Use(jwtauth.Authenticator) - - protectedRoutes(r) - }) +func AdminsOnly(next http.Handler) http.Handler { + return utils.AuthMiddleware(next, AdminToken) } diff --git a/internal/api/utils/auth.go b/internal/api/utils/auth.go new file mode 100644 index 00000000..03f80dbb --- /dev/null +++ b/internal/api/utils/auth.go @@ -0,0 +1,59 @@ +package utils + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/go-chi/render" + "github.com/dgrijalva/jwt-go" +) + +func GetUserName(r *http.Request) interface{} { + props, _ := r.Context().Value("props").(jwt.MapClaims) + return props["user_name"] +} + +type HttpMiddleware = func(next http.Handler) http.Handler + +func AuthMiddleware(next http.Handler, jwtSecrets ...[]byte) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := strings.Split(r.Header.Get("Authorization"), "Bearer ") + if len(authHeader) != 2 { + render.Render(w, r, ErrMessage(401, "Malformed JWT token.")) + return + } + + jwtToken := authHeader[1] + var jwtVerified *jwt.Token + var err error + for _, jwtSecret := range jwtSecrets { + jwtVerified, err = jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + return jwtSecret, nil + }) + + if err == nil { + break + } + } + + if err != nil { + render.Render(w, r, ErrMessage(401, "Invalid JWT token.")) + return + } + + if claims, ok := jwtVerified.Claims.(jwt.MapClaims); ok && jwtVerified.Valid { + ctx := context.WithValue(r.Context(), "props", claims) + // Access context values in handlers like this + // props, _ := r.Context().Value("props").(jwt.MapClaims) + next.ServeHTTP(w, r.WithContext(ctx)) + } else { + render.Render(w, r, ErrMessage(401, "Unauthorized.")) + } + }) +}