Skip to content

feat: add membership package and AddOrganizationMembers RPC#1537

Merged
whoAbhishekSah merged 7 commits intomainfrom
feat/membership-service
Apr 15, 2026
Merged

feat: add membership package and AddOrganizationMembers RPC#1537
whoAbhishekSah merged 7 commits intomainfrom
feat/membership-service

Conversation

@whoAbhishekSah
Copy link
Copy Markdown
Member

@whoAbhishekSah whoAbhishekSah commented Apr 14, 2026

Summary

Introduces core/membership package as the foundation for centralizing all member access management, and wires it into the new AddOrganizationMembers AdminService RPC. Service and handler PRs were initially split but merged into this single PR.

What's in this PR

core/membership/ package

  • AddOrganizationMember(ctx, orgID, principalID, principalType, roleID) — adds a user to an org with an explicit role
  • Creates policy + matching explicit relation atomically (owner role → owner relation, else → member relation)
  • Compensating cleanup: if relation creation fails after policy creation, deletes the orphaned policy (logs a warning if cleanup also fails)
  • Audit logging via structured records and event auditor

AddOrganizationMembers AdminService RPC

  • Batch endpoint accepting list of {user_id, role_id} pairs
  • Returns per-member {user_id, role_id, success, error} for partial failure handling
  • Authorization: IsSuperUser (AdminService)
  • Domain errors pass through as-is; internal errors masked and logged server-side

Edge cases handled

  • Principal type must be app/user (rejected upfront — serviceusers are bound to orgs at creation, not added later)
  • Org does not exist or is disabled → error (via orgService.Get)
  • User does not exist or is disabled → error
  • Role does not exist → error
  • Role is not org-scoped (even if org-specific) → error
  • User is already a member → error
  • Policy creation fails → error (no orphan)
  • Relation creation fails → policy cleaned up; if cleanup fails, logged with IDs

Design decisions

  • orgRoleToRelation is a pure function — checks role name, no DB call
  • validateOrgRole returns the fetched role to avoid redundant lookups
  • validateOrgRole always checks OrganizationNamespace scope first, then decides platform-wide vs org-specific
  • createPolicy / createRelation as separate private helpers (no deletes needed for Add)
  • Handler iterates batch entries and maps errors through toClientError to mask internals

Related

Test plan

  • 14 unit tests covering all edge cases above
  • End-to-end tested on local server (see earlier comment)

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 14, 2026

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

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Apr 15, 2026 5:58am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Warning

Rate limit exceeded

@whoAbhishekSah has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 41 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 41 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4bcfa9fb-83d6-4098-b239-3bf6328c680c

📥 Commits

Reviewing files that changed from the base of the PR and between 98d72e3 and a9c4f78.

⛔ Files ignored due to path filters (2)
  • proto/v1beta1/admin.pb.go is excluded by !**/*.pb.go, !proto/**
  • proto/v1beta1/frontierv1beta1connect/admin.connect.go is excluded by !proto/**
📒 Files selected for processing (17)
  • .mockery.yaml
  • Makefile
  • cmd/serve.go
  • core/membership/errors.go
  • core/membership/mocks/audit_record_repository.go
  • core/membership/mocks/org_service.go
  • core/membership/mocks/policy_service.go
  • core/membership/mocks/relation_service.go
  • core/membership/mocks/role_service.go
  • core/membership/mocks/user_service.go
  • core/membership/service.go
  • core/membership/service_test.go
  • internal/api/api.go
  • internal/api/v1beta1connect/interfaces.go
  • internal/api/v1beta1connect/organization.go
  • internal/api/v1beta1connect/v1beta1connect.go
  • pkg/server/connect_interceptors/authorization.go
📝 Walkthrough

Walkthrough

A new membership service is introduced for managing organization members. It includes error definitions, a Service implementation with dependency injection for policy, relation, role, organization, user, and audit services, and comprehensive test coverage. Auto-generated mocks are configured and created for all service dependencies.

Changes

Cohort / File(s) Summary
Configuration
.mockery.yaml
Added mockery configuration for the github.com/raystack/frontier/core/membership package to generate mocks into core/membership/mocks directory.
Core Implementation
core/membership/errors.go, core/membership/service.go, core/membership/service_test.go
Added membership service for organization member management with role validation, policy and relation creation, and audit logging. Includes four error variables and comprehensive test coverage for success and failure paths.
Auto-generated Mocks
core/membership/mocks/audit_record_repository.go, core/membership/mocks/org_service.go, core/membership/mocks/policy_service.go, core/membership/mocks/relation_service.go, core/membership/mocks/role_service.go, core/membership/mocks/user_service.go
Generated mock implementations for service dependencies supporting typed expectations, fluent assertion chains (.Run, .Return, .RunAndReturn), and automatic expectation cleanup hooks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • rohilsurana
  • rsbh

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
core/membership/service_test.go (2)

32-40: Add test coverage for remaining service guards.

Line 231 always passes schema.UserPrincipal, so the “principal must be user” branch is not validated. Also, there’s no explicit org-disabled case in the table. Please add both scenarios to lock down the contract from this PR.

Also applies to: 231-231


126-126: Audit assertions are too broad in success paths.

Using mock.Anything for auditRepo.Create can hide payload regressions. Prefer matching key fields (resource/principal/action) so these tests fail on incorrect audit records.

Also applies to: 149-149, 165-165


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 19e33b2d-b936-40f2-849b-4bc39105b736

📥 Commits

Reviewing files that changed from the base of the PR and between 13f2269 and 98d72e3.

📒 Files selected for processing (10)
  • .mockery.yaml
  • core/membership/errors.go
  • core/membership/mocks/audit_record_repository.go
  • core/membership/mocks/org_service.go
  • core/membership/mocks/policy_service.go
  • core/membership/mocks/relation_service.go
  • core/membership/mocks/role_service.go
  • core/membership/mocks/user_service.go
  • core/membership/service.go
  • core/membership/service_test.go

Comment thread core/membership/service.go
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 14, 2026

Coverage Report for CI Build 24438924492

Coverage increased (+0.1%) to 41.727%

Details

  • Coverage increased (+0.1%) from the base build.
  • Patch coverage: 63 uncovered changes across 5 files (121 of 184 lines covered, 65.76%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
internal/api/v1beta1connect/organization.go 48 0 0.0%
core/membership/service.go 129 121 93.8%
cmd/serve.go 3 0 0.0%
pkg/server/connect_interceptors/authorization.go 3 0 0.0%
internal/api/v1beta1connect/v1beta1connect.go 1 0 0.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 36626
Covered Lines: 15283
Line Coverage: 41.73%
Coverage Strength: 11.87 hits per line

💛 - Coveralls

Comment thread core/membership/service.go Outdated
Comment thread core/membership/service.go
@whoAbhishekSah whoAbhishekSah changed the title feat: add membership package with AddOrganizationMember feat: add membership package and AddOrganizationMembers RPC Apr 15, 2026
whoAbhishekSah and others added 7 commits April 15, 2026 11:26
Introduce core/membership package that manages policy + relation
together for org membership operations. AddOrganizationMember
validates org/user/role, creates policy + explicit relation, and
emits audit records.

- Validates org exists and is enabled, user exists and is enabled,
  role is org-scoped (global or org-specific)
- Rejects if user is already a member
- Creates policy and matching explicit relation (owner role -> owner
  relation, everything else -> member relation)
- Audit logging via both structured records and event auditor
- 12 unit tests covering all error and success paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If createRelation fails after createPolicy succeeds, attempt to
delete the orphaned policy. Log a warning if the cleanup also fails
so the orphan can be investigated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validate principalType is app/user before any service calls. Prevents
invalid membership data if a caller passes a serviceuser or group
principal type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rohil pointed out that the previous check let custom roles created
for an org pass even if they weren't scoped to the org namespace.
Now we check schema.OrganizationNamespace scope first, before
deciding if the role is platform-wide or org-specific.

Also remove stale 'replacePolicy deletes...' comment on createPolicy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@whoAbhishekSah
Copy link
Copy Markdown
Member Author

Test Results

Tested end-to-end on pr1537-test-org using the claude plugin. All 8 scenarios pass.

# Scenario Result
1 Add multiple members (alice as owner, bob as viewer) success: true for both
2 User already a member error: "principal is already a member of this resource"
3 Invalid user ID error: "user doesn't exist"
4 Invalid role ID error: "role doesn't exist"
5 Invalid org ID error: "org doesn't exist"
6 Partial failure (1 valid, 1 duplicate) charlie added, alice errored — per-entry results correct
7 Project-scoped role (not org-scoped) error: "role is not valid for organization scope"
8 Non-super admin authorization code: "permission_denied"

Verified

  • Batch processing with per-entry success/error
  • Validation: org existence, user existence, role existence, role scope
  • Duplicate membership rejection
  • SuperAdmin (IsSuperUser) authorization enforced
  • Members (alice, bob, charlie) appear in the org after the RPC call

@whoAbhishekSah whoAbhishekSah merged commit 08d63db into main Apr 15, 2026
8 checks passed
@whoAbhishekSah whoAbhishekSah deleted the feat/membership-service branch April 15, 2026 06:18
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.

3 participants