Files
cloud-services/pkg/httphandlers/auth_jwttoken.go

167 lines
3.8 KiB
Go

package httphandlers
import (
"context"
"errors"
"net/http"
"fiskerinc.com/modules/adminroles"
"fiskerinc.com/modules/common/authproviders"
c "fiskerinc.com/modules/common/context"
"fiskerinc.com/modules/db/queries"
"fiskerinc.com/modules/jwt"
"fiskerinc.com/modules/logger"
"fiskerinc.com/modules/utils"
)
var errNoUsername = errors.New("no username")
type AuthCheckerInterface interface {
GetHandler(requiredRoles map[string][]adminroles.RoleID, next http.HandlerFunc) http.HandlerFunc
Check(requiredRoles map[string][]adminroles.RoleID, r *http.Request) (context.Context, error)
GetValidator() jwt.JWTValidatorInterface
SetGroupKey(string)
Close()
}
type AuthJWTToken struct {
groupkey string
apiCalls queries.APICallsInterface
validator jwt.JWTValidatorInterface
AuthBase
}
func (a *AuthJWTToken) GetValidator() jwt.JWTValidatorInterface {
if a.validator == nil {
a.validator = jwt.NewJWTValidator("")
}
return a.validator
}
func (a *AuthJWTToken) GroupKey() string {
if len(a.groupkey) == 0 {
return "custom:groups"
}
return a.groupkey
}
func (a *AuthJWTToken) SetGroupKey(gp string) {
a.groupkey = gp
}
func (a *AuthJWTToken) GetHandler(requiredRoles map[string][]adminroles.RoleID, next http.HandlerFunc) http.HandlerFunc {
wrapper := func(w http.ResponseWriter, r *http.Request) {
ctx, err := a.Check(requiredRoles, r)
if err != nil {
logger.Warn().Msgf("AuthJWTToken %s %s '%v'", r.Method, r.RequestURI, err)
utils.RespError(w, http.StatusUnauthorized, err.Error())
return
}
if ctx != nil {
r = r.WithContext(ctx)
}
next.ServeHTTP(w, r)
}
return wrapper
}
func (a *AuthJWTToken) Check(requiredRoles map[string][]adminroles.RoleID, r *http.Request) (context.Context, error) {
// if there are no required roles, anyone can access
if !a.hasRoles(requiredRoles) {
return r.Context(), nil
}
token, err := jwt.GetAuthorizationHeader(r)
if err != nil {
return nil, err
}
payload, err := a.GetValidator().ValidateToken(token.Token)
if err != nil {
return nil, err
}
if username, ok := a.getUsername(payload); ok && username != "" {
ctx := context.WithValue(r.Context(), ClientIDContextKey, username)
*r = *r.WithContext(ctx)
} else {
return nil, errNoUsername
}
provider := a.getProvider(payload)
roles, ok := a.getRolesForProvider(provider, requiredRoles)
if !ok {
return nil, errors.New(adminroles.MissingPermissionError)
}
checker := adminroles.RolesChecker{}
checker.SetRequiredRoles(roles)
err = checker.CheckGroups(payload[a.GroupKey()])
if err != nil {
return nil, err
}
ctx := a.addContext(r.Context(), c.ProviderKey, provider)
return ctx, nil
}
func (a *AuthJWTToken) getProvider(payload map[string]interface{}) string {
var ok bool
var data interface{}
var identities []interface{}
var identity map[string]interface{}
var provider string
if data, ok = payload["identities"]; !ok {
return authproviders.Default
}
if identities, ok = data.([]interface{}); !ok || len(identities) != 1 {
return authproviders.Default
}
if identity, ok = identities[0].(map[string]interface{}); !ok {
return authproviders.Default
}
if provider, ok = identity["providerName"].(string); !ok {
return authproviders.Default
}
return provider
}
func (a *AuthJWTToken) Close() {
// nothing to dispose here
}
func (a *AuthJWTToken) getUsername(payload map[string]interface{}) (string, bool) {
username, ok := payload["cognito:username"].(string)
if ok && username != "" {
return username, true
}
username, ok = payload["username"].(string)
if ok && username != "" {
return username, true
}
username, ok = payload["preferred_username"].(string)
if ok && username != "" {
return username, true
}
username, ok = payload["email"].(string)
if ok && username != "" {
return username, true
}
return "", false
}