use custom JWT middleware.

This commit is contained in:
Miroslav Šedivý 2020-10-31 12:27:55 +01:00
parent c609a28a38
commit 963d210507
5 changed files with 83 additions and 46 deletions

1
go.mod
View File

@ -3,6 +3,7 @@ module demodesk/neko
go 1.13 go 1.13
require ( require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-chi/chi v4.1.0+incompatible github.com/go-chi/chi v4.1.0+incompatible
github.com/go-chi/render v1.0.1 github.com/go-chi/render v1.0.1

View File

@ -3,6 +3,7 @@ package member
import ( import (
"github.com/go-chi/chi" "github.com/go-chi/chi"
"demodesk/neko/internal/api/utils"
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
) )
@ -24,18 +25,10 @@ func New(
} }
func (h *MemberHandler) Router( func (h *MemberHandler) Router(
usersOnly func(chi.Router, func(chi.Router)), usersOnly utils.HttpMiddleware,
adminsOnly func(chi.Router, func(chi.Router)), adminsOnly utils.HttpMiddleware,
) *chi.Mux { ) *chi.Mux {
r := chi.NewRouter() r := chi.NewRouter()
usersOnly(r, func(r chi.Router) {
})
adminsOnly(r, func(r chi.Router) {
})
return r return r
} }

View File

@ -3,6 +3,7 @@ package room
import ( import (
"github.com/go-chi/chi" "github.com/go-chi/chi"
"demodesk/neko/internal/api/utils"
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
) )
@ -30,18 +31,16 @@ func New(
} }
func (h *RoomHandler) Router( func (h *RoomHandler) Router(
usersOnly func(chi.Router, func(chi.Router)), usersOnly utils.HttpMiddleware,
adminsOnly func(chi.Router, func(chi.Router)), adminsOnly utils.HttpMiddleware,
) *chi.Mux { ) *chi.Mux {
r := chi.NewRouter() r := chi.NewRouter()
usersOnly(r, func(r chi.Router) {
r.Get("/screen", h.ScreenConfiguration)
})
adminsOnly(r, func(r chi.Router) { r.Route("/screen", func(r chi.Router) {
r.Post("/screen", h.ScreenConfigurationChange) r.With(usersOnly).Get("/", h.ScreenConfiguration)
r.Get("/screen/configurations", h.ScreenConfigurationsList) r.With(adminsOnly).Post("/", h.ScreenConfigurationChange)
r.With(adminsOnly).Get("/configurations", h.ScreenConfigurationsList)
}) })
return r return r

View File

@ -1,13 +1,15 @@
package api package api
import ( import (
"net/http"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/jwtauth"
"demodesk/neko/internal/api/member" "demodesk/neko/internal/api/member"
"demodesk/neko/internal/api/room" "demodesk/neko/internal/api/room"
"demodesk/neko/internal/types" "demodesk/neko/internal/types"
"demodesk/neko/internal/types/config" "demodesk/neko/internal/types/config"
"demodesk/neko/internal/api/utils"
) )
type API struct { type API struct {
@ -17,8 +19,8 @@ type API struct {
websocket types.WebSocketHandler websocket types.WebSocketHandler
} }
var AdminToken *jwtauth.JWTAuth var AdminToken []byte
var UserToken *jwtauth.JWTAuth var UserToken []byte
func New( func New(
sessions types.SessionManager, sessions types.SessionManager,
@ -27,8 +29,8 @@ func New(
websocket types.WebSocketHandler, websocket types.WebSocketHandler,
conf *config.Server, conf *config.Server,
) *API { ) *API {
AdminToken = jwtauth.New("HS256", []byte(conf.AdminToken), nil) AdminToken = []byte(conf.AdminToken)
UserToken = jwtauth.New("HS256", []byte(conf.UserToken), nil) UserToken = []byte(conf.UserToken)
return &API{ return &API{
sessions: sessions, sessions: sessions,
@ -46,27 +48,10 @@ func (a *API) Mount(r *chi.Mux) {
r.Mount("/room", roomHandler.Router(UsersOnly, AdminsOnly)) r.Mount("/room", roomHandler.Router(UsersOnly, AdminsOnly))
} }
func UsersOnly(r chi.Router, protectedRoutes func(r chi.Router)) { func UsersOnly(next http.Handler) http.Handler {
r.Group(func(r chi.Router) { return utils.AuthMiddleware(next, UserToken, AdminToken)
// Verify JWT tokens
r.Use(jwtauth.Verifier(UserToken))
r.Use(jwtauth.Verifier(AdminToken))
// Handle valid / invalid tokens.
r.Use(jwtauth.Authenticator)
protectedRoutes(r)
})
} }
func AdminsOnly(r chi.Router, protectedRoutes func(r chi.Router)) { func AdminsOnly(next http.Handler) http.Handler {
r.Group(func(r chi.Router) { return utils.AuthMiddleware(next, AdminToken)
// Verify JWT token
r.Use(jwtauth.Verifier(AdminToken))
// Handle valid / invalid tokens.
r.Use(jwtauth.Authenticator)
protectedRoutes(r)
})
} }

View File

@ -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."))
}
})
}