Rename user archive action to deactivate
"Archive" was misleading for users: the action sets a profile to DEACTIVATED while keeping the person in the organization. Rename it to "deactivate" across the API, CLI, MCP, n8n, and console UI. Consolidate the two overlapping operations into a single deactivateUser backed by the fuller, guarded logic (SCIM guard, last-active-owner guard, invitation expiry, signature cancellation, membership update, webhook) and authorized via iam:membership-profile:deactivate. Remove the archiveUser surface and the thin state-only deactivate path. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -104,9 +104,8 @@ extend type Mutation {
|
||||
createUser(input: CreateUserInput!): CreateUserPayload
|
||||
@authentication(required: PRESENT)
|
||||
deactivateUser(input: DeactivateUserInput!): DeactivateUserPayload
|
||||
updateUser(input: UpdateUserInput!): UpdateUserPayload!
|
||||
archiveUser(input: ArchiveUserInput!): ArchiveUserPayload
|
||||
@authentication(required: PRESENT)
|
||||
updateUser(input: UpdateUserInput!): UpdateUserPayload!
|
||||
removeUser(input: RemoveUserInput!): RemoveUserPayload
|
||||
@authentication(required: PRESENT)
|
||||
}
|
||||
@@ -148,11 +147,6 @@ input RemoveUserInput {
|
||||
profileId: ID!
|
||||
}
|
||||
|
||||
input ArchiveUserInput {
|
||||
organizationId: ID!
|
||||
profileId: ID!
|
||||
}
|
||||
|
||||
type CreateUserPayload {
|
||||
profileEdge: ProfileEdge!
|
||||
}
|
||||
@@ -168,7 +162,3 @@ type UpdateUserPayload {
|
||||
type RemoveUserPayload {
|
||||
deletedProfileId: ID!
|
||||
}
|
||||
|
||||
type ArchiveUserPayload {
|
||||
archivedProfileId: ID!
|
||||
}
|
||||
|
||||
@@ -64,17 +64,23 @@ func (r *mutationResolver) CreateUser(ctx context.Context, input types.CreateUse
|
||||
|
||||
// DeactivateUser is the resolver for the deactivateUser field.
|
||||
func (r *mutationResolver) DeactivateUser(ctx context.Context, input types.DeactivateUserInput) (*types.DeactivateUserPayload, error) {
|
||||
if _, err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDeactivate); err != nil {
|
||||
scope, err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDeactivate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err := r.iam.OrganizationService.UpdateUserState(
|
||||
ctx,
|
||||
input.ProfileID,
|
||||
coredata.ProfileStateDeactivated,
|
||||
)
|
||||
err = r.iam.OrganizationService.DeactivateUser(ctx, scope, input.OrganizationID, input.ProfileID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot deactivate profile", log.Error(err))
|
||||
if _, ok := errors.AsType[*iam.ErrUserManagedBySCIM](err); ok {
|
||||
return nil, gqlutils.Conflictf(ctx, "user is managed by SCIM and cannot be deactivated")
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*iam.ErrLastActiveOwner](err); ok {
|
||||
return nil, gqlutils.Conflictf(ctx, "cannot deactivate last active owner")
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot deactivate user", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
@@ -111,31 +117,6 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, input types.UpdateUse
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ArchiveUser is the resolver for the archiveUser field.
|
||||
func (r *mutationResolver) ArchiveUser(ctx context.Context, input types.ArchiveUserInput) (*types.ArchiveUserPayload, error) {
|
||||
scope, err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDelete)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.iam.OrganizationService.ArchiveUser(ctx, scope, input.OrganizationID, input.ProfileID)
|
||||
if err != nil {
|
||||
if _, ok := errors.AsType[*iam.ErrUserManagedBySCIM](err); ok {
|
||||
return nil, gqlutils.Conflictf(ctx, "user is managed by SCIM and cannot be archived")
|
||||
}
|
||||
|
||||
if _, ok := errors.AsType[*iam.ErrLastActiveOwner](err); ok {
|
||||
return nil, gqlutils.Conflictf(ctx, "cannot archive last active owner")
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot archive user from organization", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.ArchiveUserPayload{ArchivedProfileID: input.ProfileID}, nil
|
||||
}
|
||||
|
||||
// RemoveUser is the resolver for the removeUser field.
|
||||
func (r *mutationResolver) RemoveUser(ctx context.Context, input types.RemoveUserInput) (*types.RemoveUserPayload, error) {
|
||||
scope, err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipDelete)
|
||||
|
||||
Reference in New Issue
Block a user