Skip to content

feat: add access service with Goa API for principal grants RBAC#1882

Open
tgmendes wants to merge 6 commits intomainfrom
tiago/age-1566-access-service
Open

feat: add access service with Goa API for principal grants RBAC#1882
tgmendes wants to merge 6 commits intomainfrom
tiago/age-1566-access-service

Conversation

@tgmendes
Copy link
Contributor

@tgmendes tgmendes commented Mar 13, 2026

Summary

  • Adds the access Goa service with three RPC endpoints for managing principal grants (RBAC):
    • listGrants — list grants for the active org, optionally filtered by principal URN
    • upsertGrant — create-or-noop a grant for a (principal, scope, resource) tuple
    • removeGrants — remove all grants for a given principal in the org
  • Includes Goa design, generated HTTP server/client code, service implementation, server wiring, and regenerated TypeScript SDK

Context

Builds on PR #1878 (merged) which added the urn.Principal type, principal_grants SQLc queries, and generated repo code. This PR adds the HTTP API layer on top.

Part of AGE-1566.

Notes

  • All grants are additive (union-based, no deny rules)
  • removeGrants deletes all grants for a principal (not individual grant rows)
  • Pagination deferred — grants per org are bounded in practice

Adds the access service with three endpoints:
- listGrants: list grants for an org, optionally filtered by principal URN
- upsertGrant: create-or-noop a grant for a principal/scope/resource tuple
- removeGrants: remove all grants for a principal in an org

Includes Goa design, generated code, service implementation, and server wiring.
@tgmendes tgmendes requested a review from a team as a code owner March 13, 2026 17:34
@linear
Copy link

linear bot commented Mar 13, 2026

@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gram-docs-redirect Ready Ready Preview, Comment Mar 13, 2026 6:11pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 13, 2026

⚠️ No Changeset found

Latest commit: bd84721

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +64 to +84
func (s *Service) ListGrants(ctx context.Context, payload *gen.ListGrantsPayload) (*gen.ListGrantsResult, error) {
authCtx, ok := contextvalues.GetAuthContext(ctx)
if !ok || authCtx == nil {
return nil, oops.C(oops.CodeUnauthorized)
}

rows, err := s.repo.ListPrincipalGrantsByOrg(ctx, repo.ListPrincipalGrantsByOrgParams{
OrganizationID: authCtx.ActiveOrganizationID,
PrincipalUrn: conv.PtrValOr(payload.PrincipalUrn, ""),
})
if err != nil {
return nil, oops.E(oops.CodeUnexpected, err, "list principal grants").Log(ctx, s.logger)
}

grants := make([]*gen.Grant, len(rows))
for i, row := range rows {
grants[i] = grantFromRow(row)
}

return &gen.ListGrantsResult{Grants: grants}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 No authorization beyond authentication for RBAC management endpoints

All three access endpoints (ListGrants, UpsertGrant, RemoveGrants) only check that the user is authenticated (has a valid session with an active organization). There is no additional role-based check to verify the caller has permission to manage grants. This means any authenticated member of an organization can list, create, or delete RBAC grants for that organization. For an RBAC management service, this is a potentially sensitive design choice — typically only admins should be able to modify access control rules. Compare with other services in the codebase that may enforce additional authorization. This may be intentional for an initial implementation, but should be verified.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


Meta("openapi:operationId", "listGrants")
Meta("openapi:extension:x-speakeasy-name-override", "list")
Meta("openapi:extension:x-speakeasy-react-hook", `{"name": "ListGrants"}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Meta("openapi:extension:x-speakeasy-react-hook", `{"name": "ListGrants"}`)
Meta("openapi:extension:x-speakeasy-react-hook", `{"name": "Grants"}`)

Meta("openapi:extension:x-speakeasy-react-hook", `{"name": "ListGrants"}`)
})

Method("upsertGrant", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would benefit from being an upsertGrants endpoint (plural) allowing batch upsert instead requiring one API call per grant

security.SessionPayload()
})

Result(Grant)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you followed the advice above

Suggested change
Result(Grant)
Result(UpsertGrantsResult)

and

var UpsertGrantsResult = Type("UpsertGrantsResult", func() {
	Required("grants")
	Attribute("grants", ArrayOf(Grant), "The list of grants that were added/changed.")
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly I think this service could be the first to have permission enforcement applied to it. Good testing ground before expanding this feature to other parts of Gram. Would suggest you try it out here.

return nil, oops.C(oops.CodeUnauthorized)
}

principal, err := urn.ParsePrincipal(payload.PrincipalUrn)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably set the type of payload.PrincipalUrn to urn.Principal in Goa designs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants