Przeglądaj źródła

feature: Cleaned up jwt auth, and added header auth

jamesread 3 lat temu
rodzic
commit
0335e58b12

+ 2 - 0
internal/config/config.go

@@ -76,6 +76,8 @@ type Config struct {
 	AuthJwtSecret                   string
 	AuthJwtClaimUsername            string
 	AuthJwtClaimUserGroup           string
+	AuthHttpHeaderUsername 			string
+	AuthHttpHeaderUserGroup 		string
 	DefaultPermissions              PermissionsList
 	AccessControlLists              []AccessControlList
 }

+ 30 - 55
internal/httpservers/restapi.go

@@ -2,8 +2,6 @@ package httpservers
 
 import (
 	"context"
-	"errors"
-	"fmt"
 	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
 	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc"
@@ -11,8 +9,6 @@ import (
 	"google.golang.org/protobuf/encoding/protojson"
 	"net/http"
 
-	"github.com/golang-jwt/jwt/v4"
-
 	gw "github.com/OliveTin/OliveTin/gen/grpc"
 
 	config "github.com/OliveTin/OliveTin/internal/config"
@@ -23,39 +19,44 @@ var (
 	cfg *config.Config
 )
 
-func parseToken(cookieValue string) (*jwt.Token, error) {
-	return jwt.Parse(cookieValue, func(token *jwt.Token) (interface{}, error) {
-		// Don't forget to validate the alg is what you expect:
-		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
-			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
+func parseHttpHeaderForAuth(req *http.Request) (string, string) {
+	username, ok := req.Header[cfg.AuthHttpHeaderUsername]
+
+	if !ok {
+		return "", ""
+	}
+
+	if cfg.AuthHttpHeaderUserGroup != "" {
+		usergroup, ok := req.Header[cfg.AuthHttpHeaderUserGroup]
+
+		if ok {
+			return username[0], usergroup[0]
 		}
+	}
 
-		// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
-		return []byte(cfg.AuthJwtSecret), nil
-	})
+	return username[0], ""
 }
 
-func getClaimsFromJwtToken(cookieValue string) (jwt.MapClaims, error) {
-	token, err := parseToken(cookieValue)
+func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD {
+	username := ""
+	usergroup := ""
 
-	if err != nil {
-		log.Errorf("jwt parse failure: %v", err)
-		return nil, errors.New("jwt parse failure")
+	if cfg.AuthJwtCookieName != "" {
+		username, usergroup = parseJwtCookie(req)
 	}
 
-	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
-		return claims, nil
-	} else {
-		return nil, errors.New("jwt token isn't valid")
+	if cfg.AuthHttpHeaderUsername != "" {
+		username, usergroup = parseHttpHeaderForAuth(req)
 	}
-}
 
-func lookupClaimValueOrDefault(claims jwt.MapClaims, key string, def string) string {
-	if val, ok := claims[key]; ok {
-		return fmt.Sprintf("%s", val)
-	} else {
-		return def
-	}
+	md := metadata.Pairs(
+		"username", username,
+		"usergroup", usergroup,
+	)
+
+	log.Debugf("jwt usable claims: %+v", md)
+
+	return md
 }
 
 func startRestAPIServer(globalConfig *config.Config) error {
@@ -72,33 +73,7 @@ func startRestAPIServer(globalConfig *config.Config) error {
 	// The JSONPb.EmitDefaults is necssary, so "empty" fields are returned in JSON.
 	mux := runtime.NewServeMux(
 		runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
-			cookie, err := request.Cookie(cfg.AuthJwtCookieName)
-
-			if err != nil {
-				log.Debugf("jwt cookie check %v name: %v", err, cfg.AuthJwtCookieName)
-				return nil
-			}
-
-			claims, err := getClaimsFromJwtToken(cookie.Value)
-
-			log.Debugf("jwt claims data: %+v", claims)
-
-			if err != nil {
-				log.Warnf("jwt claim error: %+v", err)
-				return nil
-			}
-
-			username := lookupClaimValueOrDefault(claims, cfg.AuthJwtClaimUsername, "none")
-			usergroup := lookupClaimValueOrDefault(claims, cfg.AuthJwtClaimUserGroup, "none")
-
-			md := metadata.Pairs(
-				"username", username,
-				"usergroup", usergroup,
-			)
-
-			log.Debugf("jwt usable claims: %+v", md)
-
-			return md
+			return parseRequestMetadata(ctx, request)
 		}),
 		runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
 			Marshaler: &runtime.JSONPb{

+ 68 - 0
internal/httpservers/restapi_auth_jwt.go

@@ -0,0 +1,68 @@
+package httpservers;
+
+import (
+	"github.com/golang-jwt/jwt/v4"
+	"fmt"
+	log "github.com/sirupsen/logrus"
+	"errors"
+	"net/http"
+)
+
+func parseJwtToken(cookieValue string) (*jwt.Token, error) {
+	return jwt.Parse(cookieValue, func(token *jwt.Token) (interface{}, error) {
+		// Don't forget to validate the alg is what you expect:
+		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
+		}
+
+		// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
+		return []byte(cfg.AuthJwtSecret), nil
+	})
+}
+
+func getClaimsFromJwtToken(cookieValue string) (jwt.MapClaims, error) {
+	token, err := parseJwtToken(cookieValue)
+
+	if err != nil {
+		log.Errorf("jwt parse failure: %v", err)
+		return nil, errors.New("jwt parse failure")
+	}
+
+	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
+		return claims, nil
+	} else {
+		return nil, errors.New("jwt token isn't valid")
+	}
+}
+
+func lookupClaimValueOrDefault(claims jwt.MapClaims, key string, def string) string {
+	if val, ok := claims[key]; ok {
+		return fmt.Sprintf("%s", val)
+	} else {
+		return def
+	}
+}
+
+func parseJwtCookie(request *http.Request) (string, string) {
+	cookie, err := request.Cookie(cfg.AuthJwtCookieName)
+
+	if err != nil {
+		log.Debugf("jwt cookie check %v name: %v", err, cfg.AuthJwtCookieName)
+		return "", ""
+	}
+
+	claims, err := getClaimsFromJwtToken(cookie.Value)
+
+	log.Debugf("jwt claims data: %+v", claims)
+
+	if err != nil {
+		log.Warnf("jwt claim error: %+v", err)
+		return "", ""
+	}
+
+	username := lookupClaimValueOrDefault(claims, cfg.AuthJwtClaimUsername, "")
+	usergroup := lookupClaimValueOrDefault(claims, cfg.AuthJwtClaimUserGroup, "")
+
+	return username, usergroup
+}
+