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 }