Skip to content

Commit 4feec85

Browse files
feat: add AddOrganizationMembers RPC handler (#1538)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1224d00 commit 4feec85

9 files changed

Lines changed: 1249 additions & 823 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
44
VERSION := $(shell git describe --tags ${TAG})
55
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto admin-app compose-up-dev
66
.DEFAULT_GOAL := build
7-
PROTON_COMMIT := "ac2df1932fcddcd7f7ff1af0ded07b14d73b781b"
7+
PROTON_COMMIT := "e7f1e5d55904e32d889d6e98ae1534ff41ae064a"
88

99
admin-app:
1010
@echo " > generating admin build"

cmd/serve.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ import (
9292
"github.com/go-webauthn/webauthn/webauthn"
9393
"github.com/raystack/frontier/config"
9494
"github.com/raystack/frontier/core/group"
95+
"github.com/raystack/frontier/core/membership"
9596
"github.com/raystack/frontier/core/namespace"
9697
"github.com/raystack/frontier/core/organization"
9798
"github.com/raystack/frontier/core/policy"
@@ -420,6 +421,8 @@ func buildAPIDependencies(
420421
organizationService := organization.NewService(organizationRepository, relationService, userService,
421422
authnService, policyService, preferenceService, auditRecordRepository, roleService)
422423

424+
membershipService := membership.NewService(logger, policyService, relationService, roleService, organizationService, userService, auditRecordRepository)
425+
423426
orgKycRepository := postgres.NewOrgKycRepository(dbc)
424427
orgKycService := kyc.NewService(orgKycRepository)
425428

@@ -620,6 +623,7 @@ func buildAPIDependencies(
620623
UserProjectsService: userProjectsService,
621624
AuditRecordService: auditRecordService,
622625
UserPATService: userPATService,
626+
MembershipService: membershipService,
623627
}
624628
return dependencies, nil
625629
}

internal/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/raystack/frontier/core/group"
3232
"github.com/raystack/frontier/core/invitation"
3333
"github.com/raystack/frontier/core/kyc"
34+
"github.com/raystack/frontier/core/membership"
3435
"github.com/raystack/frontier/core/metaschema"
3536
"github.com/raystack/frontier/core/namespace"
3637
"github.com/raystack/frontier/core/organization"
@@ -101,4 +102,5 @@ type Deps struct {
101102

102103
AuditRecordService *auditrecord.Service
103104
UserPATService *userpat.Service
105+
MembershipService *membership.Service
104106
}

internal/api/v1beta1connect/interfaces.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,10 @@ type AuditRecordService interface {
407407
Export(ctx context.Context, query *rql.Query) (io.Reader, string, error)
408408
}
409409

410+
type MembershipService interface {
411+
AddOrganizationMember(ctx context.Context, orgID, principalID, principalType, roleID string) error
412+
}
413+
410414
type UserPATService interface {
411415
ValidateExpiry(expiresAt time.Time) error
412416
Create(ctx context.Context, req userpat.CreateRequest) (models.PAT, string, error)

internal/api/v1beta1connect/organization.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"connectrpc.com/connect"
77
grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
88
"github.com/raystack/frontier/core/audit"
9+
"github.com/raystack/frontier/core/membership"
910
"github.com/raystack/frontier/core/organization"
1011
"github.com/raystack/frontier/core/policy"
1112
"github.com/raystack/frontier/core/project"
@@ -569,6 +570,66 @@ func (h *ConnectHandler) SetOrganizationMemberRole(ctx context.Context, request
569570
return connect.NewResponse(&frontierv1beta1.SetOrganizationMemberRoleResponse{}), nil
570571
}
571572

573+
func (h *ConnectHandler) AddOrganizationMembers(ctx context.Context, request *connect.Request[frontierv1beta1.AddOrganizationMembersRequest]) (*connect.Response[frontierv1beta1.AddOrganizationMembersResponse], error) {
574+
errorLogger := NewErrorLogger()
575+
orgID := request.Msg.GetOrgId()
576+
577+
var results []*frontierv1beta1.OrgMemberResult
578+
for _, member := range request.Msg.GetMembers() {
579+
result := &frontierv1beta1.OrgMemberResult{
580+
UserId: member.GetUserId(),
581+
RoleId: member.GetRoleId(),
582+
}
583+
584+
if err := h.membershipService.AddOrganizationMember(ctx, orgID, member.GetUserId(), schema.UserPrincipal, member.GetRoleId()); err != nil {
585+
result.Success = false
586+
result.Error = toClientError(err)
587+
if !isDomainError(err) {
588+
errorLogger.LogServiceError(ctx, request, "AddOrganizationMembers", err,
589+
zap.String("org_id", orgID),
590+
zap.String("user_id", member.GetUserId()),
591+
zap.String("role_id", member.GetRoleId()))
592+
}
593+
} else {
594+
result.Success = true
595+
}
596+
597+
results = append(results, result)
598+
}
599+
600+
return connect.NewResponse(&frontierv1beta1.AddOrganizationMembersResponse{
601+
Results: results,
602+
}), nil
603+
}
604+
605+
// isDomainError returns true if the error is a known domain error safe to expose to clients.
606+
func isDomainError(err error) bool {
607+
knownErrors := []error{
608+
membership.ErrAlreadyMember,
609+
membership.ErrInvalidOrgRole,
610+
organization.ErrNotExist,
611+
organization.ErrDisabled,
612+
user.ErrNotExist,
613+
user.ErrDisabled,
614+
role.ErrNotExist,
615+
role.ErrInvalidID,
616+
}
617+
for _, known := range knownErrors {
618+
if errors.Is(err, known) {
619+
return true
620+
}
621+
}
622+
return false
623+
}
624+
625+
// toClientError returns a client-safe error message.
626+
func toClientError(err error) string {
627+
if isDomainError(err) {
628+
return err.Error()
629+
}
630+
return ErrInternalServerError.Error()
631+
}
632+
572633
func (h *ConnectHandler) EnableOrganization(ctx context.Context, request *connect.Request[frontierv1beta1.EnableOrganizationRequest]) (*connect.Response[frontierv1beta1.EnableOrganizationResponse], error) {
573634
errorLogger := NewErrorLogger()
574635

internal/api/v1beta1connect/v1beta1connect.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type ConnectHandler struct {
6161
userProjectsService UserProjectsService
6262
auditRecordService AuditRecordService
6363
userPATService UserPATService
64+
membershipService MembershipService
6465
}
6566

6667
func NewConnectHandler(deps api.Deps, authConf authenticate.Config) *ConnectHandler {
@@ -111,6 +112,7 @@ func NewConnectHandler(deps api.Deps, authConf authenticate.Config) *ConnectHand
111112
userProjectsService: deps.UserProjectsService,
112113
auditRecordService: deps.AuditRecordService,
113114
userPATService: deps.UserPATService,
115+
membershipService: deps.MembershipService,
114116
}
115117
}
116118

pkg/server/connect_interceptors/authorization.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,9 @@ var authorizationValidationMap = map[string]func(ctx context.Context, handler *v
10161016
"/raystack.frontier.v1beta1.AdminService/AdminCreateOrganization": func(ctx context.Context, handler *v1beta1connect.ConnectHandler, req connect.AnyRequest) error {
10171017
return handler.IsSuperUser(ctx, req)
10181018
},
1019+
"/raystack.frontier.v1beta1.AdminService/AddOrganizationMembers": func(ctx context.Context, handler *v1beta1connect.ConnectHandler, req connect.AnyRequest) error {
1020+
return handler.IsSuperUser(ctx, req)
1021+
},
10191022
"/raystack.frontier.v1beta1.AdminService/SearchOrganizationUsers": func(ctx context.Context, handler *v1beta1connect.ConnectHandler, req connect.AnyRequest) error {
10201023
return handler.IsSuperUser(ctx, req)
10211024
},

0 commit comments

Comments
 (0)