You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/dgrijalva/jwt-go"
|
|
"github.com/gorilla/mux"
|
|
"github.com/rs/zerolog/log"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type key int
|
|
|
|
const (
|
|
usernameKey key = iota
|
|
)
|
|
|
|
// Token is the authentication token used by processes when dialing with the API
|
|
type Token struct {
|
|
// Username used for logging purposes
|
|
Username string `json:"username"`
|
|
|
|
// Rights that the token provides
|
|
// Format is: METHOD - list of paths
|
|
Rights map[string][]string `json:"rights"`
|
|
}
|
|
|
|
// Middleware is the authentication middleware
|
|
type Middleware struct {
|
|
signingKey []byte
|
|
}
|
|
|
|
// NewMiddleware create a new Middleware instance with given secret token signing key
|
|
func NewMiddleware(signingKey []byte) *Middleware {
|
|
return &Middleware{signingKey: signingKey}
|
|
}
|
|
|
|
// Middleware return an net/http compatible middleware func to use
|
|
func (m *Middleware) Middleware() mux.MiddlewareFunc {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Extract authorization header
|
|
tokenStr := r.Header.Get("Authorization")
|
|
if tokenStr == "" {
|
|
log.Warn().Msg("missing token")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
tokenStr = strings.TrimPrefix(tokenStr, "Bearer ")
|
|
|
|
// Decode the JWT token
|
|
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
|
// Validate expected alg
|
|
if v, ok := t.Method.(*jwt.SigningMethodHMAC); !ok || v.Name != "HS256" {
|
|
return nil, fmt.Errorf("unexpected signing method: %s", t.Header["alg"])
|
|
}
|
|
|
|
// Return signing secret
|
|
return m.signingKey, nil
|
|
})
|
|
if err != nil {
|
|
log.Err(err).Msg("error while decoding JWT token")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// From here we have a valid JWT token, extract claims
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
if !ok {
|
|
log.Err(err).Msg("error while decoding token claims")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
rights := map[string][]string{}
|
|
for method, paths := range claims["rights"].(map[string]interface{}) {
|
|
for _, path := range paths.([]interface{}) {
|
|
rights[method] = append(rights[method], path.(string))
|
|
}
|
|
}
|
|
|
|
t := Token{
|
|
Username: claims["username"].(string),
|
|
Rights: rights,
|
|
}
|
|
|
|
// Validate rights
|
|
paths, contains := t.Rights[r.Method]
|
|
if !contains {
|
|
log.Warn().
|
|
Str("username", t.Username).
|
|
Str("method", r.Method).
|
|
Str("resource", r.URL.Path).
|
|
Msg("Access to resources is unauthorized")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
authorized := false
|
|
for _, path := range paths {
|
|
if path == r.URL.Path {
|
|
authorized = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !authorized {
|
|
log.Warn().
|
|
Str("username", t.Username).
|
|
Str("method", r.Method).
|
|
Str("resource", r.URL.Path).
|
|
Msg("Access to resources is unauthorized")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Everything's fine, call next handler ;D
|
|
ctx := context.WithValue(r.Context(), usernameKey, t.Username)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|