Change JWT Auth to Middleware Handler

Caveat: Cache is per endpoint
This commit is contained in:
2024-06-26 05:31:28 +02:00
parent 826658f762
commit 6d5594a376
7 changed files with 120 additions and 147 deletions

View File

@@ -137,10 +137,6 @@ func (data *ApiMetricData) PadDataWithNull(ms *memorystore.MemoryStore, from, to
// @security ApiKeyAuth
// @router /free/ [get]
func handleFree(rw http.ResponseWriter, r *http.Request) {
if err := isAuthenticated(r); err != nil {
handleError(err, http.StatusUnauthorized, rw)
return
}
rawTo := r.URL.Query().Get("to")
if rawTo == "" {
handleError(errors.New("'to' is a required query parameter"), http.StatusBadRequest, rw)
@@ -200,10 +196,6 @@ func handleFree(rw http.ResponseWriter, r *http.Request) {
// @security ApiKeyAuth
// @router /write/ [post]
func handleWrite(rw http.ResponseWriter, r *http.Request) {
if err := isAuthenticated(r); err != nil {
handleError(err, http.StatusUnauthorized, rw)
return
}
bytes, err := io.ReadAll(r.Body)
rw.Header().Add("Content-Type", "application/json")
if err != nil {
@@ -263,10 +255,6 @@ type ApiQuery struct {
// @security ApiKeyAuth
// @router /query/ [get]
func handleQuery(rw http.ResponseWriter, r *http.Request) {
if err := isAuthenticated(r); err != nil {
handleError(err, http.StatusUnauthorized, rw)
return
}
var err error
req := ApiQueryRequest{WithStats: true, WithData: true, WithPadding: true}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -385,10 +373,10 @@ func handleQuery(rw http.ResponseWriter, r *http.Request) {
// handleDebug godoc
// @summary Debug endpoint
// @tags write
// @tags debug
// @description Write metrics to store
// @produce json
// @param selector query string false "Job Cluster"
// @param selector query string false "Selector"
// @success 200 {string} string "Debug dump"
// @failure 400 {object} api.ErrorResponse "Bad Request"
// @failure 401 {object} api.ErrorResponse "Unauthorized"
@@ -397,10 +385,6 @@ func handleQuery(rw http.ResponseWriter, r *http.Request) {
// @security ApiKeyAuth
// @router /debug/ [post]
func handleDebug(rw http.ResponseWriter, r *http.Request) {
if err := isAuthenticated(r); err != nil {
handleError(err, http.StatusUnauthorized, rw)
return
}
raw := r.URL.Query().Get("selector")
rw.Header().Add("Content-Type", "application/json")
selector := []string{}

View File

@@ -10,43 +10,47 @@ import (
"github.com/golang-jwt/jwt/v4"
)
var publicKey ed25519.PublicKey
func isAuthenticated(r *http.Request) error {
func authHandler(next http.Handler, publicKey ed25519.PublicKey) http.Handler {
cacheLock := sync.RWMutex{}
cache := map[string]*jwt.Token{}
authheader := r.Header.Get("Authorization")
if authheader == "" || !strings.HasPrefix(authheader, "Bearer ") {
return errors.New("Use JWT Authentication")
}
rawtoken := authheader[len("Bearer "):]
cacheLock.RLock()
token, ok := cache[rawtoken]
cacheLock.RUnlock()
if ok && token.Claims.Valid() == nil {
return nil
}
// The actual token is ignored for now.
// In case expiration and so on are specified, the Parse function
// already returns an error for expired tokens.
var err error
token, err = jwt.Parse(rawtoken, func(t *jwt.Token) (interface{}, error) {
if t.Method != jwt.SigningMethodEdDSA {
return nil, errors.New("only Ed25519/EdDSA supported")
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
authheader := r.Header.Get("Authorization")
if authheader == "" || !strings.HasPrefix(authheader, "Bearer ") {
http.Error(rw, "Use JWT Authentication", http.StatusUnauthorized)
return
}
return publicKey, nil
rawtoken := authheader[len("Bearer "):]
cacheLock.RLock()
token, ok := cache[rawtoken]
cacheLock.RUnlock()
if ok && token.Claims.Valid() == nil {
next.ServeHTTP(rw, r)
return
}
// The actual token is ignored for now.
// In case expiration and so on are specified, the Parse function
// already returns an error for expired tokens.
var err error
token, err = jwt.Parse(rawtoken, func(t *jwt.Token) (interface{}, error) {
if t.Method != jwt.SigningMethodEdDSA {
return nil, errors.New("only Ed25519/EdDSA supported")
}
return publicKey, nil
})
if err != nil {
http.Error(rw, err.Error(), http.StatusUnauthorized)
return
}
cacheLock.Lock()
cache[rawtoken] = token
cacheLock.Unlock()
// Let request through...
next.ServeHTTP(rw, r)
})
if err != nil {
return err
}
cacheLock.Lock()
cache[rawtoken] = token
cacheLock.Unlock()
return nil
}

View File

@@ -23,30 +23,32 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/clusters/": {
"get": {
"/debug/": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Write metrics to store",
"produces": [
"application/json"
],
"tags": [
"free"
"debug"
],
"summary": "Debug endpoint",
"parameters": [
{
"type": "string",
"description": "up to timestamp",
"name": "to",
"description": "Selector",
"name": "selector",
"in": "query"
}
],
"responses": {
"200": {
"description": "ok",
"description": "Debug dump",
"schema": {
"type": "string"
}
@@ -78,32 +80,30 @@ const docTemplate = `{
}
}
},
"/debug/": {
"post": {
"/free/": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Write metrics to store",
"produces": [
"application/json"
],
"tags": [
"write"
"free"
],
"summary": "Debug endpoint",
"parameters": [
{
"type": "string",
"description": "Job Cluster",
"name": "selector",
"description": "up to timestamp",
"name": "to",
"in": "query"
}
],
"responses": {
"200": {
"description": "Debug dump",
"description": "ok",
"schema": {
"type": "string"
}

View File

@@ -19,11 +19,15 @@ func MountRoutes(r *http.ServeMux) {
if err != nil {
log.Fatalf("starting server failed: %v", err)
}
publicKey = ed25519.PublicKey(buf)
publicKey := ed25519.PublicKey(buf)
r.Handle("POST /api/free/", authHandler(http.HandlerFunc(handleFree), publicKey))
r.Handle("POST /api/write/", authHandler(http.HandlerFunc(handleWrite), publicKey))
r.Handle("GET /api/query/", authHandler(http.HandlerFunc(handleQuery), publicKey))
r.Handle("GET /api/debug/", authHandler(http.HandlerFunc(handleDebug), publicKey))
} else {
r.HandleFunc("POST /api/free/", handleFree)
r.HandleFunc("POST /api/write/", handleWrite)
r.HandleFunc("GET /api/query/", handleQuery)
r.HandleFunc("GET /api/debug/", handleDebug)
}
r.HandleFunc("POST /api/free/", handleFree)
r.HandleFunc("POST /api/write/", handleWrite)
r.HandleFunc("GET /api/query/", handleQuery)
r.HandleFunc("GET /api/debug/", handleDebug)
}