2
0
Эх сурвалжийг харах

add logging scopes automatically

Alexander Belanger 4 жил өмнө
parent
commit
788916fbb3
53 өөрчлөгдсөн 218 нэмэгдсэн , 194 устгасан
  1. 6 6
      api/server/authn/handler.go
  2. 4 5
      api/server/authz/cluster.go
  3. 3 4
      api/server/authz/git_installation.go
  4. 3 4
      api/server/authz/helm_repo.go
  5. 3 4
      api/server/authz/infra.go
  6. 3 4
      api/server/authz/invite.go
  7. 1 2
      api/server/authz/namespace.go
  8. 8 10
      api/server/authz/policy.go
  9. 2 7
      api/server/authz/policy/policy.go
  10. 12 12
      api/server/authz/policy/policy_test.go
  11. 5 5
      api/server/authz/policy_test.go
  12. 2 3
      api/server/authz/project.go
  13. 2 3
      api/server/authz/project_test.go
  14. 3 4
      api/server/authz/registry.go
  15. 3 4
      api/server/authz/release.go
  16. 3 3
      api/server/handlers/cluster/create_namespace.go
  17. 2 2
      api/server/handlers/cluster/delete_namespace.go
  18. 2 2
      api/server/handlers/cluster/detect_prometheus_installed.go
  19. 2 2
      api/server/handlers/cluster/get.go
  20. 3 3
      api/server/handlers/cluster/get_kubeconfig.go
  21. 4 4
      api/server/handlers/cluster/get_pod_metrics.go
  22. 2 2
      api/server/handlers/cluster/list.go
  23. 3 3
      api/server/handlers/cluster/list_namespaces.go
  24. 3 3
      api/server/handlers/cluster/list_nginx_ingresses.go
  25. 1 1
      api/server/handlers/gitinstallation/get.go
  26. 6 7
      api/server/handlers/handler.go
  27. 1 1
      api/server/handlers/helmrepo/get.go
  28. 1 1
      api/server/handlers/infra/get.go
  29. 1 1
      api/server/handlers/invite/get.go
  30. 1 1
      api/server/handlers/metadata/get.go
  31. 3 3
      api/server/handlers/namespace/list_releases.go
  32. 2 2
      api/server/handlers/project/create.go
  33. 1 1
      api/server/handlers/project/get.go
  34. 2 2
      api/server/handlers/project/get_policy.go
  35. 2 2
      api/server/handlers/project/list.go
  36. 2 2
      api/server/handlers/project/list_infra.go
  37. 1 1
      api/server/handlers/registry/get.go
  38. 2 2
      api/server/handlers/release/get.go
  39. 5 5
      api/server/handlers/user/cli_login.go
  40. 5 5
      api/server/handlers/user/create.go
  41. 1 1
      api/server/handlers/user/current.go
  42. 3 3
      api/server/handlers/user/delete.go
  43. 3 3
      api/server/handlers/user/email_verify.go
  44. 1 2
      api/server/handlers/user/email_verify_test.go
  45. 5 5
      api/server/handlers/user/login.go
  46. 1 1
      api/server/handlers/user/logout.go
  47. 23 24
      api/server/handlers/user/pw_reset.go
  48. 47 8
      api/server/shared/apierrors/errors.go
  49. 1 2
      api/server/shared/apitest/authz.go
  50. 1 1
      api/server/shared/apitest/request.go
  51. 2 2
      api/server/shared/reader.go
  52. 3 4
      api/server/shared/writer.go
  53. 7 0
      api/types/request.go

+ 6 - 6
api/server/authn/handler.go

@@ -59,7 +59,7 @@ func (authn *AuthN) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// if the error is not an invalid auth error, the token was invalid, and we throw error
 	// forbidden. If the error was an invalid auth error, we look for a cookie.
 	if err != nil && err != errInvalidAuthHeader {
-		authn.sendForbiddenError(err, w)
+		authn.sendForbiddenError(err, w, r)
 		return
 	} else if err == nil && tok != nil {
 		authn.nextWithToken(w, r, tok)
@@ -76,7 +76,7 @@ func (authn *AuthN) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		// forbidden error regardless
 		session.Save(r, w)
 
-		authn.sendForbiddenError(err, w)
+		authn.sendForbiddenError(err, w, r)
 		return
 	}
 
@@ -114,7 +114,7 @@ func (authn *AuthN) handleForbiddenForSession(
 
 		http.Redirect(w, r, "/dashboard", 302)
 	} else {
-		authn.sendForbiddenError(err, w)
+		authn.sendForbiddenError(err, w, r)
 	}
 
 	return
@@ -136,7 +136,7 @@ func (authn *AuthN) nextWithUserID(w http.ResponseWriter, r *http.Request, userI
 	user, err := authn.config.Repo.User().ReadUser(userID)
 
 	if err != nil {
-		authn.sendForbiddenError(fmt.Errorf("user with id %d not found in database", userID), w)
+		authn.sendForbiddenError(fmt.Errorf("user with id %d not found in database", userID), w, r)
 		return
 	}
 
@@ -150,10 +150,10 @@ func (authn *AuthN) nextWithUserID(w http.ResponseWriter, r *http.Request, userI
 
 // sendForbiddenError sends a 403 Forbidden error to the end user while logging a
 // specific error
-func (authn *AuthN) sendForbiddenError(err error, w http.ResponseWriter) {
+func (authn *AuthN) sendForbiddenError(err error, w http.ResponseWriter, r *http.Request) {
 	reqErr := apierrors.NewErrForbidden(err)
 
-	apierrors.HandleAPIError(context.Background(), authn.config, w, reqErr)
+	apierrors.HandleAPIError(authn.config, w, r, reqErr)
 }
 
 var errInvalidToken = fmt.Errorf("authorization header exists, but token is not valid")

+ 4 - 5
api/server/authz/cluster.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -44,17 +43,17 @@ func (p *ClusterScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	// get the cluster id from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	clusterID := reqScopes[types.ClusterScope].Resource.UInt
 	cluster, err := p.config.Repo.Cluster().ReadCluster(proj.ID, clusterID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrForbidden(
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrForbidden(
 				fmt.Errorf("cluster with id %d not found in project %d", clusterID, proj.ID),
 			))
 		} else {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		}
 
 		return
@@ -136,7 +135,7 @@ func (d *OutOfClusterAgentGetter) GetHelmAgent(r *http.Request, cluster *models.
 	}
 
 	// look for namespace in context, otherwise go with default
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	namespace := "default"
 
 	if nsPolicy, ok := reqScopes[types.NamespaceScope]; ok && nsPolicy.Resource.Name != "" {

+ 3 - 4
api/server/authz/git_installation.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -38,18 +37,18 @@ func (p *GitInstallationScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *ht
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	// get the registry id from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	gitInstallationID := reqScopes[types.GitInstallationScope].Resource.UInt
 
 	gitInstallation, err := p.config.Repo.GithubAppInstallation().ReadGithubAppInstallation(proj.ID, gitInstallationID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrForbidden(
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrForbidden(
 				fmt.Errorf("github app installation with id %d not found in project %d", gitInstallationID, proj.ID),
 			))
 		} else {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		}
 
 		return

+ 3 - 4
api/server/authz/helm_repo.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -37,18 +36,18 @@ func (p *HelmRepoScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	// get the registry id from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	helmRepoID := reqScopes[types.HelmRepoScope].Resource.UInt
 
 	helmRepo, err := p.config.Repo.HelmRepo().ReadHelmRepo(proj.ID, helmRepoID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrForbidden(
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrForbidden(
 				fmt.Errorf("helm repo with id %d not found in project %d", helmRepoID, proj.ID),
 			))
 		} else {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		}
 
 		return

+ 3 - 4
api/server/authz/infra.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -37,18 +36,18 @@ func (p *InfraScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	// get the registry id from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	infraID := reqScopes[types.InfraScope].Resource.UInt
 
 	infra, err := p.config.Repo.Infra().ReadInfra(proj.ID, infraID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrForbidden(
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrForbidden(
 				fmt.Errorf("infra with id %d not found in project %d", infraID, proj.ID),
 			))
 		} else {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		}
 
 		return

+ 3 - 4
api/server/authz/invite.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -37,18 +36,18 @@ func (p *InviteScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	// get the registry id from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	inviteID := reqScopes[types.InviteScope].Resource.UInt
 
 	invite, err := p.config.Repo.Invite().ReadInvite(proj.ID, inviteID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrForbidden(
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrForbidden(
 				fmt.Errorf("invite with id %d not found in project %d", inviteID, proj.ID),
 			))
 		} else {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		}
 
 		return

+ 1 - 2
api/server/authz/namespace.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 )
@@ -30,7 +29,7 @@ type NamespaceScopedMiddleware struct {
 
 func (n *NamespaceScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// get the namespace from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 
 	namespace := reqScopes[types.NamespaceScope].Resource.Name
 

+ 8 - 10
api/server/authz/policy.go

@@ -42,7 +42,7 @@ func (h *PolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	reqScopes, reqErr := getRequestActionForEndpoint(r, h.endpointMeta)
 
 	if reqErr != nil {
-		apierrors.HandleAPIError(r.Context(), h.config, w, reqErr)
+		apierrors.HandleAPIError(h.config, w, r, reqErr)
 		return
 	}
 
@@ -53,7 +53,7 @@ func (h *PolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	policyDocs, reqErr := h.loader.LoadPolicyDocuments(user.ID, projID)
 
 	if reqErr != nil {
-		apierrors.HandleAPIError(r.Context(), h.config, w, reqErr)
+		apierrors.HandleAPIError(h.config, w, r, reqErr)
 		return
 	}
 
@@ -62,9 +62,9 @@ func (h *PolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	if !hasAccess {
 		apierrors.HandleAPIError(
-			r.Context(),
 			h.config,
 			w,
+			r,
 			apierrors.NewErrForbidden(fmt.Errorf("policy forbids action for user %d in project %d", user.ID, projID)),
 		)
 
@@ -77,17 +77,15 @@ func (h *PolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	h.next.ServeHTTP(w, r)
 }
 
-const RequestScopeCtxKey = "requestscopes"
-
-func NewRequestScopeCtx(ctx context.Context, reqScopes map[types.PermissionScope]*policy.RequestAction) context.Context {
-	return context.WithValue(ctx, RequestScopeCtxKey, reqScopes)
+func NewRequestScopeCtx(ctx context.Context, reqScopes map[types.PermissionScope]*types.RequestAction) context.Context {
+	return context.WithValue(ctx, types.RequestScopeCtxKey, reqScopes)
 }
 
 func getRequestActionForEndpoint(
 	r *http.Request,
 	endpointMeta types.APIRequestMetadata,
-) (res map[types.PermissionScope]*policy.RequestAction, reqErr apierrors.RequestError) {
-	res = make(map[types.PermissionScope]*policy.RequestAction)
+) (res map[types.PermissionScope]*types.RequestAction, reqErr apierrors.RequestError) {
+	res = make(map[types.PermissionScope]*types.RequestAction)
 
 	// iterate through scopes, attach policies as needed
 	for _, scope := range endpointMeta.Scopes {
@@ -117,7 +115,7 @@ func getRequestActionForEndpoint(
 			return nil, reqErr
 		}
 
-		res[scope] = &policy.RequestAction{
+		res[scope] = &types.RequestAction{
 			Verb:     endpointMeta.Verb,
 			Resource: resource,
 		}

+ 2 - 7
api/server/authz/policy/policy.go

@@ -4,16 +4,11 @@ import (
 	"github.com/porter-dev/porter/api/types"
 )
 
-type RequestAction struct {
-	Verb     types.APIVerb
-	Resource types.NameOrUInt
-}
-
 // HasScopeAccess checks that a user can perform an action (`verb`) against a specific
 // resource (`resource+scope`) according to a `policy`.
 func HasScopeAccess(
 	policy []*types.PolicyDocument,
-	reqScopes map[types.PermissionScope]*RequestAction,
+	reqScopes map[types.PermissionScope]*types.RequestAction,
 ) bool {
 	// iterate through policy documents until a match is found
 	for _, policyDoc := range policy {
@@ -94,7 +89,7 @@ func populateAndVerifyPolicyDocument(
 	tree types.ScopeTree,
 	currScope types.PermissionScope,
 	parentVerbs []types.APIVerb,
-	reqScopes map[types.PermissionScope]*RequestAction,
+	reqScopes map[types.PermissionScope]*types.RequestAction,
 	currMatchDocs map[types.PermissionScope]*types.PolicyDocument,
 ) (ok bool, matchDocs map[types.PermissionScope]*types.PolicyDocument) {
 	if currMatchDocs == nil {

+ 12 - 12
api/server/authz/policy/policy_test.go

@@ -11,7 +11,7 @@ import (
 type testHasScopeAccess struct {
 	description string
 	policy      []*types.PolicyDocument
-	reqScopes   map[types.PermissionScope]*policy.RequestAction
+	reqScopes   map[types.PermissionScope]*types.RequestAction
 	expRes      bool
 }
 
@@ -19,7 +19,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "admin access to project",
 		policy:      policy.AdminPolicy,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ProjectScope: {
 				Verb: types.APIVerbGet,
 				Resource: types.NameOrUInt{
@@ -32,7 +32,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "viewer access cannot perform write operation",
 		policy:      policy.ViewerPolicy,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ClusterScope: {
 				Verb: types.APIVerbCreate,
 				Resource: types.NameOrUInt{
@@ -45,7 +45,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "developer access cannot write settings",
 		policy:      policy.DeveloperPolicy,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.SettingsScope: {
 				Verb: types.APIVerbUpdate,
 				Resource: types.NameOrUInt{
@@ -58,7 +58,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "custom policy for cluster 1 can write cluster 1",
 		policy:      testPolicySpecificClusters,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ClusterScope: {
 				Verb: types.APIVerbUpdate,
 				Resource: types.NameOrUInt{
@@ -71,7 +71,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "custom policy for cluster 1 cannot write cluster 2",
 		policy:      testPolicySpecificClusters,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ClusterScope: {
 				Verb: types.APIVerbUpdate,
 				Resource: types.NameOrUInt{
@@ -84,7 +84,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "cannot access wrong namespace + cluster combination",
 		policy:      testPolicyNamespaceSpecific,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ClusterScope: {
 				Verb: types.APIVerbGet,
 				Resource: types.NameOrUInt{
@@ -103,7 +103,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "can access set namespace + cluster combination",
 		policy:      testPolicyNamespaceSpecific,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ClusterScope: {
 				Verb: types.APIVerbGet,
 				Resource: types.NameOrUInt{
@@ -122,7 +122,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "cannot write the set namespace + cluster combination",
 		policy:      testPolicyNamespaceSpecific,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ClusterScope: {
 				Verb: types.APIVerbGet,
 				Resource: types.NameOrUInt{
@@ -141,7 +141,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "test invalid policy document",
 		policy:      testInvalidPolicyDocument,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ProjectScope: {
 				Verb: types.APIVerbGet,
 				Resource: types.NameOrUInt{
@@ -154,7 +154,7 @@ var hasScopeAccessTests = []testHasScopeAccess{
 	{
 		description: "test invalid policy document nested",
 		policy:      testInvalidPolicyDocumentNested,
-		reqScopes: map[types.PermissionScope]*policy.RequestAction{
+		reqScopes: map[types.PermissionScope]*types.RequestAction{
 			types.ProjectScope: {
 				Verb: types.APIVerbGet,
 				Resource: types.NameOrUInt{
@@ -183,7 +183,7 @@ func BenchmarkSimpleHasScopeAccess(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		res := policy.HasScopeAccess(
 			testPolicySpecificClusters,
-			map[types.PermissionScope]*policy.RequestAction{
+			map[types.PermissionScope]*types.RequestAction{
 				types.ClusterScope: {
 					Verb: types.APIVerbCreate,
 					Resource: types.NameOrUInt{

+ 5 - 5
api/server/authz/policy_test.go

@@ -47,7 +47,7 @@ func TestPolicyMiddlewareSuccessfulProjectCluster(t *testing.T) {
 
 	handler.ServeHTTP(rr, req)
 
-	assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*policy.RequestAction{
+	assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*types.RequestAction{
 		types.ProjectScope: {
 			Verb: types.APIVerbCreate,
 			Resource: types.NameOrUInt{
@@ -102,7 +102,7 @@ func TestPolicyMiddlewareSuccessfulApplication(t *testing.T) {
 
 	handler.ServeHTTP(rr, req)
 
-	assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*policy.RequestAction{
+	assertNextHandlerCalled(t, next, rr, map[types.PermissionScope]*types.RequestAction{
 		types.ProjectScope: {
 			Verb: types.APIVerbCreate,
 			Resource: types.NameOrUInt{
@@ -272,20 +272,20 @@ func (f *viewerDocLoader) LoadPolicyDocuments(userID, projectID uint) ([]*types.
 
 type testHandler struct {
 	WasCalled bool
-	ReqScopes map[types.PermissionScope]*policy.RequestAction
+	ReqScopes map[types.PermissionScope]*types.RequestAction
 }
 
 func (t *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	t.WasCalled = true
 
-	t.ReqScopes, _ = r.Context().Value(authz.RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	t.ReqScopes, _ = r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 }
 
 func assertNextHandlerCalled(
 	t *testing.T,
 	next *testHandler,
 	rr *httptest.ResponseRecorder,
-	expScopes map[types.PermissionScope]*policy.RequestAction,
+	expScopes map[types.PermissionScope]*types.RequestAction,
 ) {
 	// make sure the handler was called with the expected user, and resulted in 200 OK
 	assert := assert.New(t)

+ 2 - 3
api/server/authz/project.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -32,14 +31,14 @@ type ProjectScopedMiddleware struct {
 
 func (p *ProjectScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// get the project id from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 
 	projID := reqScopes[types.ProjectScope].Resource.UInt
 
 	project, err := p.config.Repo.Project().ReadProject(projID)
 
 	if err != nil {
-		apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+		apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		return
 	}
 

+ 2 - 3
api/server/authz/project_test.go

@@ -5,7 +5,6 @@ import (
 	"testing"
 
 	"github.com/porter-dev/porter/api/server/authz"
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/handlers/project"
 	"github.com/porter-dev/porter/api/server/shared/apitest"
 	"github.com/porter-dev/porter/api/server/shared/config"
@@ -29,7 +28,7 @@ func TestProjectMiddlewareSuccessful(t *testing.T) {
 
 	req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1", nil)
 	req = apitest.WithAuthenticatedUser(t, req, user)
-	req = apitest.WithRequestScopes(t, req, map[types.PermissionScope]*policy.RequestAction{
+	req = apitest.WithRequestScopes(t, req, map[types.PermissionScope]*types.RequestAction{
 		types.ProjectScope: {
 			Verb: types.APIVerbCreate,
 			Resource: types.NameOrUInt{
@@ -59,7 +58,7 @@ func TestProjectMiddlewareFailedRead(t *testing.T) {
 
 	req, rr := apitest.GetRequestAndRecorder(t, string(types.HTTPVerbPost), "/api/projects/1", nil)
 	req = apitest.WithAuthenticatedUser(t, req, user)
-	req = apitest.WithRequestScopes(t, req, map[types.PermissionScope]*policy.RequestAction{
+	req = apitest.WithRequestScopes(t, req, map[types.PermissionScope]*types.RequestAction{
 		types.ProjectScope: {
 			Verb: types.APIVerbCreate,
 			Resource: types.NameOrUInt{

+ 3 - 4
api/server/authz/registry.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -37,18 +36,18 @@ func (p *RegistryScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	// get the registry id from the URL param context
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	registryID := reqScopes[types.RegistryScope].Resource.UInt
 
 	registry, err := p.config.Repo.Registry().ReadRegistry(proj.ID, registryID)
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrForbidden(
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrForbidden(
 				fmt.Errorf("registry with id %d not found in project %d", registryID, proj.ID),
 			))
 		} else {
-			apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+			apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		}
 
 		return

+ 3 - 4
api/server/authz/release.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"net/http"
 
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
@@ -38,12 +37,12 @@ func (p *ReleaseScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	helmAgent, err := p.agentGetter.GetHelmAgent(r, cluster)
 
 	if err != nil {
-		apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+		apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	// get the name of the application
-	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
 	name := reqScopes[types.ReleaseScope].Resource.Name
 
 	// get the version for the application
@@ -52,7 +51,7 @@ func (p *ReleaseScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	release, err := helmAgent.GetRelease(name, int(version))
 
 	if err != nil {
-		apierrors.HandleAPIError(r.Context(), p.config, w, apierrors.NewErrInternal(err))
+		apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
 		return
 	}
 

+ 3 - 3
api/server/handlers/cluster/create_namespace.go

@@ -40,14 +40,14 @@ func (c *CreateNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	agent, err := c.GetAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	namespace, err := agent.CreateNamespace(request.Name)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -55,5 +55,5 @@ func (c *CreateNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		Namespace: namespace,
 	}
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 2 - 2
api/server/handlers/cluster/delete_namespace.go

@@ -39,12 +39,12 @@ func (c *DeleteNamespaceHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	agent, err := c.GetAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	if err := agent.DeleteNamespace(request.Name); err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 

+ 2 - 2
api/server/handlers/cluster/detect_prometheus_installed.go

@@ -32,12 +32,12 @@ func (c *DetectPrometheusInstalledHandler) ServeHTTP(w http.ResponseWriter, r *h
 	agent, err := c.GetAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	if _, found, err := prometheus.GetPrometheusService(agent.Clientset); err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	} else if !found {
 		http.NotFound(w, r)

+ 2 - 2
api/server/handlers/cluster/get.go

@@ -39,7 +39,7 @@ func (c *ClusterGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	agent, err := c.GetAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -53,5 +53,5 @@ func (c *ClusterGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		res.IngressError = kubernetes.CatchK8sConnectionError(ingressErr).Externalize()
 	}
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 3 - 3
api/server/handlers/cluster/get_kubeconfig.go

@@ -36,14 +36,14 @@ func (c *GetTemporaryKubeconfigHandler) ServeHTTP(w http.ResponseWriter, r *http
 	kubeconfig, err := outOfClusterConfig.CreateRawConfigFromCluster()
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	kubeconfigBytes, err := clientcmd.Write(*kubeconfig)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -51,5 +51,5 @@ func (c *GetTemporaryKubeconfigHandler) ServeHTTP(w http.ResponseWriter, r *http
 		Kubeconfig: kubeconfigBytes,
 	}
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 4 - 4
api/server/handlers/cluster/get_pod_metrics.go

@@ -42,7 +42,7 @@ func (c *GetPodMetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	agent, err := c.GetAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -50,14 +50,14 @@ func (c *GetPodMetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	promSvc, found, err := prometheus.GetPrometheusService(agent.Clientset)
 
 	if err != nil || !found {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	rawQuery, err := prometheus.QueryPrometheus(agent.Clientset, promSvc, &request.QueryOpts)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -65,5 +65,5 @@ func (c *GetPodMetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 
 	var res types.GetPodMetricsResponse = &s
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 2 - 2
api/server/handlers/cluster/list.go

@@ -32,7 +32,7 @@ func (p *ClusterListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	clusters, err := p.Repo().Cluster().ListClustersByProjectID(proj.ID)
 
 	if err != nil {
-		p.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -42,5 +42,5 @@ func (p *ClusterListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		res[i] = cluster.ToClusterType()
 	}
 
-	p.WriteResult(r.Context(), w, res)
+	p.WriteResult(w, r, res)
 }

+ 3 - 3
api/server/handlers/cluster/list_namespaces.go

@@ -33,14 +33,14 @@ func (c *ListNamespacesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 	agent, err := c.GetAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	namespaceList, err := agent.ListNamespaces()
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -48,5 +48,5 @@ func (c *ListNamespacesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 		NamespaceList: namespaceList,
 	}
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 3 - 3
api/server/handlers/cluster/list_nginx_ingresses.go

@@ -34,18 +34,18 @@ func (c *ListNGINXIngressesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	agent, err := c.GetAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	ingresses, err := prometheus.GetIngressesWithNGINXAnnotation(agent.Clientset)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	var res types.ListNGINXIngressesResponse = ingresses
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 1 - 1
api/server/handlers/gitinstallation/get.go

@@ -29,5 +29,5 @@ func NewGitInstallationGetHandler(
 func (c *GitInstallationGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	ga, _ := r.Context().Value(types.GitInstallationScope).(*integrations.GithubAppInstallation)
 
-	c.WriteResult(r.Context(), w, ga.ToGitInstallationType())
+	c.WriteResult(w, r, ga.ToGitInstallationType())
 }

+ 6 - 7
api/server/handlers/handler.go

@@ -1,7 +1,6 @@
 package handlers
 
 import (
-	"context"
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/shared"
@@ -13,7 +12,7 @@ import (
 type PorterHandler interface {
 	Config() *config.Config
 	Repo() repository.Repository
-	HandleAPIError(ctx context.Context, w http.ResponseWriter, err apierrors.RequestError)
+	HandleAPIError(w http.ResponseWriter, r *http.Request, err apierrors.RequestError)
 }
 
 type PorterHandlerWriter interface {
@@ -53,12 +52,12 @@ func (d *DefaultPorterHandler) Repo() repository.Repository {
 	return d.config.Repo
 }
 
-func (d *DefaultPorterHandler) HandleAPIError(ctx context.Context, w http.ResponseWriter, err apierrors.RequestError) {
-	apierrors.HandleAPIError(ctx, d.Config(), w, err)
+func (d *DefaultPorterHandler) HandleAPIError(w http.ResponseWriter, r *http.Request, err apierrors.RequestError) {
+	apierrors.HandleAPIError(d.Config(), w, r, err)
 }
 
-func (d *DefaultPorterHandler) WriteResult(ctx context.Context, w http.ResponseWriter, v interface{}) {
-	d.writer.WriteResult(ctx, w, v)
+func (d *DefaultPorterHandler) WriteResult(w http.ResponseWriter, r *http.Request, v interface{}) {
+	d.writer.WriteResult(w, r, v)
 }
 
 func (d *DefaultPorterHandler) DecodeAndValidate(w http.ResponseWriter, r *http.Request, v interface{}) bool {
@@ -69,6 +68,6 @@ func (d *DefaultPorterHandler) DecodeAndValidateNoWrite(r *http.Request, v inter
 	return d.decoderValidator.DecodeAndValidateNoWrite(r, v)
 }
 
-func IgnoreAPIError(ctx context.Context, w http.ResponseWriter, err apierrors.RequestError) {
+func IgnoreAPIError(w http.ResponseWriter, r *http.Request, err apierrors.RequestError) {
 	return
 }

+ 1 - 1
api/server/handlers/helmrepo/get.go

@@ -29,5 +29,5 @@ func NewHelmRepoGetHandler(
 func (c *HelmRepoGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	helmRepo, _ := r.Context().Value(types.HelmRepoScope).(*models.HelmRepo)
 
-	c.WriteResult(r.Context(), w, helmRepo.ToHelmRepoType())
+	c.WriteResult(w, r, helmRepo.ToHelmRepoType())
 }

+ 1 - 1
api/server/handlers/infra/get.go

@@ -29,5 +29,5 @@ func NewInfraGetHandler(
 func (c *InfraGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	infra, _ := r.Context().Value(types.InfraScope).(*models.Infra)
 
-	c.WriteResult(r.Context(), w, infra.ToInfraType())
+	c.WriteResult(w, r, infra.ToInfraType())
 }

+ 1 - 1
api/server/handlers/invite/get.go

@@ -29,5 +29,5 @@ func NewInviteGetHandler(
 func (c *InviteGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	invite, _ := r.Context().Value(types.InviteScope).(*models.Invite)
 
-	c.WriteResult(r.Context(), w, invite.ToInviteType())
+	c.WriteResult(w, r, invite.ToInviteType())
 }

+ 1 - 1
api/server/handlers/metadata/get.go

@@ -22,5 +22,5 @@ func NewMetadataGetHandler(
 }
 
 func (v *MetadataGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	v.WriteResult(r.Context(), w, v.Config().Metadata)
+	v.WriteResult(w, r, v.Config().Metadata)
 }

+ 3 - 3
api/server/handlers/namespace/list_releases.go

@@ -41,18 +41,18 @@ func (c *ListReleasesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	helmAgent, err := c.GetHelmAgent(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	releases, err := helmAgent.ListReleases(namespace, request.ReleaseListFilter)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	var res types.ListReleasesResponse = releases
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 2 - 2
api/server/handlers/project/create.go

@@ -46,11 +46,11 @@ func (p *ProjectCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	proj, err = CreateProjectWithUser(p.Repo().Project(), proj, user)
 
 	if err != nil {
-		p.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	p.WriteResult(r.Context(), w, proj.ToProjectType())
+	p.WriteResult(w, r, proj.ToProjectType())
 }
 
 func CreateProjectWithUser(

+ 1 - 1
api/server/handlers/project/get.go

@@ -26,5 +26,5 @@ func NewProjectGetHandler(
 func (p *ProjectGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
-	p.WriteResult(r.Context(), w, proj.ToProjectType())
+	p.WriteResult(w, r, proj.ToProjectType())
 }

+ 2 - 2
api/server/handlers/project/get_policy.go

@@ -35,10 +35,10 @@ func (p *ProjectGetPolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	policyDocs, err := policyDocLoader.LoadPolicyDocuments(user.ID, proj.ID)
 
 	if err != nil {
-		p.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 	}
 
 	var res types.GetProjectPolicyResponse = policyDocs
 
-	p.WriteResult(r.Context(), w, res)
+	p.WriteResult(w, r, res)
 }

+ 2 - 2
api/server/handlers/project/list.go

@@ -32,7 +32,7 @@ func (p *ProjectListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	projects, err := p.Config().Repo.Project().ListProjectsByUserID(user.ID)
 
 	if err != nil {
-		p.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -42,5 +42,5 @@ func (p *ProjectListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		res[i] = proj.ToProjectType()
 	}
 
-	p.WriteResult(r.Context(), w, res)
+	p.WriteResult(w, r, res)
 }

+ 2 - 2
api/server/handlers/project/list_infra.go

@@ -30,7 +30,7 @@ func (p *ProjectListInfraHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 	infras, err := p.Repo().Infra().ListInfrasByProjectID(proj.ID)
 
 	if err != nil {
-		p.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 	}
 
 	infraList := make([]*types.Infra, 0)
@@ -41,5 +41,5 @@ func (p *ProjectListInfraHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	var res types.ListProjectInfraResponse = infraList
 
-	p.WriteResult(r.Context(), w, res)
+	p.WriteResult(w, r, res)
 }

+ 1 - 1
api/server/handlers/registry/get.go

@@ -29,5 +29,5 @@ func NewRegistryGetHandler(
 func (c *RegistryGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	registry, _ := r.Context().Value(types.RegistryScope).(*models.Registry)
 
-	c.WriteResult(r.Context(), w, registry.ToRegistryType())
+	c.WriteResult(w, r, registry.ToRegistryType())
 }

+ 2 - 2
api/server/handlers/release/get.go

@@ -53,7 +53,7 @@ func (c *ReleaseGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	dynClient, err := c.GetDynamicClient(r, cluster)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 	}
 
 	parserDef := &parser.ClientConfigDefault{
@@ -70,5 +70,5 @@ func (c *ReleaseGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		res.Form = form
 	}
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 5 - 5
api/server/handlers/user/cli_login.go

@@ -46,7 +46,7 @@ func (c *CLILoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	if err != nil {
 		err = fmt.Errorf("CLI token creation failed: %s", err.Error())
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -54,7 +54,7 @@ func (c *CLILoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	if err != nil {
 		err = fmt.Errorf("CLI token encoding failed: %s", err.Error())
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -63,7 +63,7 @@ func (c *CLILoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	if err != nil {
 		err = fmt.Errorf("CLI random code generation failed: %s", err.Error())
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -79,7 +79,7 @@ func (c *CLILoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	authCode, err = c.Repo().AuthCode().CreateAuthCode(authCode)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -121,5 +121,5 @@ func (c *CLILoginExchangeHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		Token: authCode.Token,
 	}
 
-	c.WriteResult(r.Context(), w, res)
+	c.WriteResult(w, r, res)
 }

+ 5 - 5
api/server/handlers/user/create.go

@@ -48,7 +48,7 @@ func (u *UserCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	if doesExist {
 		err := fmt.Errorf("email already taken")
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
+		u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
 		return
 	}
 
@@ -56,7 +56,7 @@ func (u *UserCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	hashedPw, err := bcrypt.GenerateFromPassword([]byte(user.Password), 8)
 
 	if err != nil {
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -66,17 +66,17 @@ func (u *UserCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	user, err = u.Repo().User().CreateUser(user)
 
 	if err != nil {
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	// save the user as authenticated in the session
 	if err := authn.SaveUserAuthenticated(w, r, u.Config(), user); err != nil {
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	u.WriteResult(r.Context(), w, user.ToUserType())
+	u.WriteResult(w, r, user.ToUserType())
 }
 
 func doesUserExist(userRepo repository.UserRepository, user *models.User) bool {

+ 1 - 1
api/server/handlers/user/current.go

@@ -26,5 +26,5 @@ func NewUserGetCurrentHandler(
 func (a *UserGetCurrentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
 
-	a.WriteResult(r.Context(), w, user.ToUserType())
+	a.WriteResult(w, r, user.ToUserType())
 }

+ 3 - 3
api/server/handlers/user/delete.go

@@ -31,15 +31,15 @@ func (u *UserDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	user, err := u.Repo().User().DeleteUser(user)
 
 	if err != nil {
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	// set the user as unauthenticated in the session
 	if err := authn.SaveUserUnauthenticated(w, r, u.Config()); err != nil {
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	u.WriteResult(r.Context(), w, user.ToUserType())
+	u.WriteResult(w, r, user.ToUserType())
 }

+ 3 - 3
api/server/handlers/user/email_verify.go

@@ -30,10 +30,10 @@ func (v *VerifyEmailInitiateHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
 
 	pwReset, rawToken, err := CreatePWResetTokenForEmail(
-		r.Context(),
 		v.Repo().PWResetToken(),
 		v.HandleAPIError,
 		w,
+		r,
 		&types.InitiateResetUserPasswordRequest{
 			Email: user.Email,
 		},
@@ -56,7 +56,7 @@ func (v *VerifyEmailInitiateHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 	)
 
 	if err != nil {
-		v.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		v.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 }
@@ -85,10 +85,10 @@ func (v *VerifyEmailFinalizeHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
 	}
 
 	token, err := VerifyToken(
-		r.Context(),
 		v.Repo().PWResetToken(),
 		handlers.IgnoreAPIError,
 		w,
+		r,
 		&request.VerifyTokenFinalizeRequest,
 		user.Email,
 	)

+ 1 - 2
api/server/handlers/user/email_verify_test.go

@@ -1,7 +1,6 @@
 package user_test
 
 import (
-	"context"
 	"fmt"
 	"net/url"
 	"testing"
@@ -63,10 +62,10 @@ func TestEmailVerifyFinalizeSuccessful(t *testing.T) {
 
 	// create a token in the DB to use for testing
 	pwReset, rawToken, err := user.CreatePWResetTokenForEmail(
-		context.Background(),
 		config.Repo.PWResetToken(),
 		handlers.IgnoreAPIError,
 		nil,
+		nil,
 		&types.InitiateResetUserPasswordRequest{
 			Email: authUser.Email,
 		},

+ 5 - 5
api/server/handlers/user/login.go

@@ -44,25 +44,25 @@ func (u *UserLoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// case on user not existing, send forbidden error if not exist
 	if err != nil {
 		if targetErr := gorm.ErrRecordNotFound; errors.Is(err, targetErr) {
-			u.HandleAPIError(r.Context(), w, apierrors.NewErrForbidden(err))
+			u.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
 			return
 		} else {
-			u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+			u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}
 	}
 
 	if err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(request.Password)); err != nil {
 		reqErr := apierrors.NewErrPassThroughToClient(fmt.Errorf("incorrect password"), http.StatusUnauthorized)
-		u.HandleAPIError(r.Context(), w, reqErr)
+		u.HandleAPIError(w, r, reqErr)
 		return
 	}
 
 	// save the user as authenticated in the session
 	if err := authn.SaveUserAuthenticated(w, r, u.Config(), storedUser); err != nil {
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	u.WriteResult(r.Context(), w, storedUser.ToUserType())
+	u.WriteResult(w, r, storedUser.ToUserType())
 }

+ 1 - 1
api/server/handlers/user/logout.go

@@ -23,7 +23,7 @@ func NewUserLogoutHandler(
 
 func (u *UserLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if err := authn.SaveUserUnauthenticated(w, r, u.Config()); err != nil {
-		u.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 	}
 
 	return

+ 23 - 24
api/server/handlers/user/pw_reset.go

@@ -1,7 +1,6 @@
 package user
 
 import (
-	"context"
 	"fmt"
 	"net/http"
 	"net/url"
@@ -50,7 +49,7 @@ func (c *UserPasswordInitiateResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 		w.WriteHeader(http.StatusOK)
 		return
 	} else if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -64,7 +63,7 @@ func (c *UserPasswordInitiateResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 		)
 
 		if err != nil {
-			c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}
 
@@ -73,10 +72,10 @@ func (c *UserPasswordInitiateResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	}
 
 	pwReset, rawToken, err := CreatePWResetTokenForEmail(
-		r.Context(),
 		c.Repo().PWResetToken(),
 		c.HandleAPIError,
 		w,
+		r,
 		request,
 	)
 
@@ -98,7 +97,7 @@ func (c *UserPasswordInitiateResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -130,10 +129,10 @@ func (c *UserPasswordVerifyResetHandler) ServeHTTP(w http.ResponseWriter, r *htt
 	}
 
 	VerifyToken(
-		r.Context(),
 		c.Repo().PWResetToken(),
 		c.HandleAPIError,
 		w,
+		r,
 		&request.VerifyTokenFinalizeRequest,
 		request.Email,
 	)
@@ -163,10 +162,10 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	}
 
 	token, err := VerifyToken(
-		r.Context(),
 		c.Repo().PWResetToken(),
 		c.HandleAPIError,
 		w,
+		r,
 		&request.VerifyTokenFinalizeRequest,
 		request.Email,
 	)
@@ -181,9 +180,9 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			err = fmt.Errorf("finalize password reset failed: email does not exist")
-			c.HandleAPIError(r.Context(), w, apierrors.NewErrForbidden(err))
+			c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
 		} else {
-			c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		}
 
 		return
@@ -192,7 +191,7 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	hashedPW, err := bcrypt.GenerateFromPassword([]byte(request.NewPassword), 8)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -201,7 +200,7 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	user, err = c.Repo().User().UpdateUser(user)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -211,7 +210,7 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 	_, err = c.Repo().PWResetToken().UpdatePWResetToken(token)
 
 	if err != nil {
-		c.HandleAPIError(r.Context(), w, apierrors.NewErrInternal(err))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
@@ -220,10 +219,10 @@ func (c *UserPasswordFinalizeResetHandler) ServeHTTP(w http.ResponseWriter, r *h
 }
 
 func VerifyToken(
-	ctx context.Context,
 	pwResetRepo repository.PWResetTokenRepository,
-	handleErr func(ctx context.Context, w http.ResponseWriter, apiErr apierrors.RequestError),
+	handleErr func(w http.ResponseWriter, r *http.Request, err apierrors.RequestError),
 	w http.ResponseWriter,
+	r *http.Request,
 	request *types.VerifyTokenFinalizeRequest,
 	email string,
 ) (*models.PWResetToken, error) {
@@ -232,10 +231,10 @@ func VerifyToken(
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			err = fmt.Errorf("verify token failed: token does not exist")
-			handleErr(ctx, w, apierrors.NewErrForbidden(err))
+			handleErr(w, r, apierrors.NewErrForbidden(err))
 			return nil, err
 		} else {
-			handleErr(ctx, w, apierrors.NewErrInternal(err))
+			handleErr(w, r, apierrors.NewErrInternal(err))
 		}
 
 		return nil, err
@@ -244,7 +243,7 @@ func VerifyToken(
 	// make sure the token is still valid and has not expired
 	if !token.IsValid || token.IsExpired() {
 		err = fmt.Errorf("verify token failed: expired %t, valid %t", token.IsExpired(), token.IsValid)
-		handleErr(ctx, w, apierrors.NewErrForbidden(err))
+		handleErr(w, r, apierrors.NewErrForbidden(err))
 
 		return nil, err
 	}
@@ -252,7 +251,7 @@ func VerifyToken(
 	// check that the email matches
 	if token.Email != email {
 		err = fmt.Errorf("verify token failed: token email does not match request email")
-		handleErr(ctx, w, apierrors.NewErrForbidden(err))
+		handleErr(w, r, apierrors.NewErrForbidden(err))
 
 		return nil, err
 	}
@@ -260,7 +259,7 @@ func VerifyToken(
 	// make sure the token is correct
 	if err := bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(request.Token)); err != nil {
 		err = fmt.Errorf("verify token failed: %s", err)
-		handleErr(ctx, w, apierrors.NewErrForbidden(err))
+		handleErr(w, r, apierrors.NewErrForbidden(err))
 
 		return nil, err
 	}
@@ -269,10 +268,10 @@ func VerifyToken(
 }
 
 func CreatePWResetTokenForEmail(
-	ctx context.Context,
 	pwResetRepo repository.PWResetTokenRepository,
-	handleErr func(ctx context.Context, w http.ResponseWriter, apiErr apierrors.RequestError),
+	handleErr func(w http.ResponseWriter, r *http.Request, err apierrors.RequestError),
 	w http.ResponseWriter,
+	r *http.Request,
 	request *types.InitiateResetUserPasswordRequest,
 ) (*models.PWResetToken, string, error) {
 	// convert the form to a project model
@@ -281,14 +280,14 @@ func CreatePWResetTokenForEmail(
 	rawToken, err := random.StringWithCharset(32, "")
 
 	if err != nil {
-		handleErr(ctx, w, apierrors.NewErrInternal(err))
+		handleErr(w, r, apierrors.NewErrInternal(err))
 		return nil, "", err
 	}
 
 	hashedToken, err := bcrypt.GenerateFromPassword([]byte(rawToken), 8)
 
 	if err != nil {
-		handleErr(ctx, w, apierrors.NewErrInternal(err))
+		handleErr(w, r, apierrors.NewErrInternal(err))
 		return nil, "", err
 	}
 
@@ -303,7 +302,7 @@ func CreatePWResetTokenForEmail(
 	pwReset, err = pwResetRepo.CreatePWResetToken(pwReset)
 
 	if err != nil {
-		handleErr(ctx, w, apierrors.NewErrInternal(err))
+		handleErr(w, r, apierrors.NewErrInternal(err))
 		return nil, "", err
 	}
 

+ 47 - 8
api/server/shared/apierrors/errors.go

@@ -7,6 +7,8 @@ import (
 
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/rs/zerolog"
 )
 
 type RequestError interface {
@@ -91,23 +93,27 @@ func (e *ErrPassThroughToClient) GetStatusCode() int {
 }
 
 func HandleAPIError(
-	ctx context.Context,
 	config *config.Config,
 	w http.ResponseWriter,
+	r *http.Request,
 	err RequestError,
 ) {
 	// if the status code is internal server error, use alerter
 	if err.GetStatusCode() == http.StatusInternalServerError && config.Alerter != nil {
-		config.Alerter.SendAlert(ctx, err)
+		config.Alerter.SendAlert(r.Context(), err)
 	}
 
 	extErrorStr := err.ExternalError()
 
 	// log the internal error
-	config.Logger.Warn().
+	event := config.Logger.Warn().
 		Str("internal_error", err.InternalError()).
-		Str("external_error", extErrorStr).
-		Msg("")
+		Str("external_error", extErrorStr)
+
+	addLoggingScopes(r.Context(), event)
+	addLoggingRequestMeta(r, event)
+
+	event.Msg("")
 
 	// send the external error
 	resp := &types.ExternalError{
@@ -120,10 +126,43 @@ func HandleAPIError(
 	writerErr := json.NewEncoder(w).Encode(resp)
 
 	if writerErr != nil {
-		config.Logger.Error().
-			Err(writerErr).
-			Msg("")
+		event := config.Logger.Error().
+			Err(writerErr)
+
+		addLoggingScopes(r.Context(), event)
+		addLoggingRequestMeta(r, event)
+
+		event.Msg("")
 	}
 
 	return
 }
+
+func addLoggingScopes(ctx context.Context, event *zerolog.Event) {
+	// case on the context values that exist, add them to event
+	if userVal := ctx.Value(types.UserScope); userVal != nil {
+		if userModel, ok := userVal.(*models.User); ok {
+			event.Uint("user_id", userModel.ID)
+		}
+	}
+
+	// if this is a project-scoped route, add various scopes
+	if reqScopesVal := ctx.Value(types.RequestScopeCtxKey); reqScopesVal != nil {
+		if reqScopes, ok := reqScopesVal.(map[types.PermissionScope]*types.RequestAction); ok {
+			for key, scope := range reqScopes {
+				if scope.Resource.Name != "" {
+					event.Str(string(key), scope.Resource.Name)
+				}
+
+				if scope.Resource.UInt != 0 {
+					event.Uint(string(key), scope.Resource.UInt)
+				}
+			}
+		}
+	}
+}
+
+func addLoggingRequestMeta(r *http.Request, event *zerolog.Event) {
+	event.Str("method", r.Method)
+	event.Str("url", r.URL.String())
+}

+ 1 - 2
api/server/shared/apitest/authz.go

@@ -6,7 +6,6 @@ import (
 	"testing"
 
 	"github.com/porter-dev/porter/api/server/authz"
-	"github.com/porter-dev/porter/api/server/authz/policy"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 )
@@ -19,7 +18,7 @@ func WithProject(t *testing.T, req *http.Request, proj *models.Project) *http.Re
 	return req
 }
 
-func WithRequestScopes(t *testing.T, req *http.Request, reqScopes map[types.PermissionScope]*policy.RequestAction) *http.Request {
+func WithRequestScopes(t *testing.T, req *http.Request, reqScopes map[types.PermissionScope]*types.RequestAction) *http.Request {
 	ctx := req.Context()
 	ctx = authz.NewRequestScopeCtx(ctx, reqScopes)
 	req = req.WithContext(ctx)

+ 1 - 1
api/server/shared/apitest/request.go

@@ -65,7 +65,7 @@ func (f *failingDecoderValidator) DecodeAndValidate(
 	r *http.Request,
 	v interface{},
 ) (ok bool) {
-	apierrors.HandleAPIError(r.Context(), f.config, w, apierrors.NewErrInternal(fmt.Errorf("fake error")))
+	apierrors.HandleAPIError(f.config, w, r, apierrors.NewErrInternal(fmt.Errorf("fake error")))
 	return false
 }
 

+ 2 - 2
api/server/shared/reader.go

@@ -38,13 +38,13 @@ func (j *DefaultRequestDecoderValidator) DecodeAndValidate(
 
 	// decode the request parameters (body and query)
 	if requestErr = j.decoder.Decode(v, r); requestErr != nil {
-		apierrors.HandleAPIError(r.Context(), j.config, w, requestErr)
+		apierrors.HandleAPIError(j.config, w, r, requestErr)
 		return false
 	}
 
 	// validate the request object
 	if requestErr = j.validator.Validate(v); requestErr != nil {
-		apierrors.HandleAPIError(r.Context(), j.config, w, requestErr)
+		apierrors.HandleAPIError(j.config, w, r, requestErr)
 		return false
 	}
 

+ 3 - 4
api/server/shared/writer.go

@@ -1,7 +1,6 @@
 package shared
 
 import (
-	"context"
 	"encoding/json"
 	"net/http"
 
@@ -10,7 +9,7 @@ import (
 )
 
 type ResultWriter interface {
-	WriteResult(ctx context.Context, w http.ResponseWriter, v interface{})
+	WriteResult(w http.ResponseWriter, r *http.Request, v interface{})
 }
 
 // default generalizes response codes for common operations
@@ -23,10 +22,10 @@ func NewDefaultResultWriter(conf *config.Config) ResultWriter {
 	return &DefaultResultWriter{conf}
 }
 
-func (j *DefaultResultWriter) WriteResult(ctx context.Context, w http.ResponseWriter, v interface{}) {
+func (j *DefaultResultWriter) WriteResult(w http.ResponseWriter, r *http.Request, v interface{}) {
 	err := json.NewEncoder(w).Encode(v)
 
 	if err != nil {
-		apierrors.HandleAPIError(ctx, j.config, w, apierrors.NewErrInternal(err))
+		apierrors.HandleAPIError(j.config, w, r, apierrors.NewErrInternal(err))
 	}
 }

+ 7 - 0
api/types/request.go

@@ -57,3 +57,10 @@ type APIRequestMetadata struct {
 	Scopes         []PermissionScope
 	ShouldRedirect bool
 }
+
+const RequestScopeCtxKey = "requestscopes"
+
+type RequestAction struct {
+	Verb     APIVerb
+	Resource NameOrUInt
+}