Split inactive profile state
Replace the binary profile ACTIVE/INACTIVE model with PENDING, ACTIVE, and DEACTIVATED so invited-but-not-yet-activated members remain assignable to assets, data, and risks instead of being treated like deactivated users. Add activated_at/deactivated_at timestamps and Mark* lifecycle helpers, and update every transition (create, invite/re-invite, activation, archive, SCIM, SAML, sessions, compliance-portal grant) to the new states. Expose a multi-state states[] filter across coredata, GraphQL, MCP, and the console owner pickers, which now request ACTIVE and PENDING members. A migration renames the membership_state enum, classifies existing inactive profiles as PENDING from recent invitation activity, and backfills the new timestamp columns. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -438,9 +438,8 @@ func (s *OrganizationService) ArchiveUser(
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if profile.State != coredata.ProfileStateInactive {
|
||||
profile.State = coredata.ProfileStateInactive
|
||||
profile.UpdatedAt = now
|
||||
if profile.State != coredata.ProfileStateDeactivated {
|
||||
profile.MarkDeactivated(now)
|
||||
|
||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update profile state: %w", err)
|
||||
@@ -505,6 +504,14 @@ func (s *OrganizationService) InviteUser(
|
||||
return NewUserManagedBySCIMError(profile.ID)
|
||||
}
|
||||
|
||||
if profile.State == coredata.ProfileStateDeactivated {
|
||||
profile.MarkPending(now)
|
||||
|
||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update profile state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = invitation.Insert(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert invitation: %w", err)
|
||||
@@ -583,6 +590,7 @@ func (s *OrganizationService) CreateOrganization(
|
||||
OrganizationID: organization.ID,
|
||||
Source: coredata.ProfileSourceManual,
|
||||
State: coredata.ProfileStateActive,
|
||||
ActivatedAt: &now,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -1038,8 +1046,8 @@ func (s *OrganizationService) CreateUser(ctx context.Context, scope coredata.Sco
|
||||
Kind: req.Kind,
|
||||
AdditionalEmailAddresses: req.AdditionalEmailAddresses,
|
||||
Position: req.Position,
|
||||
// User is created inactive
|
||||
State: coredata.ProfileStateInactive,
|
||||
// User is pending until they accept an invitation.
|
||||
State: coredata.ProfileStatePending,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -1192,14 +1200,22 @@ func (s *OrganizationService) UpdateUserState(
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
profile.State = state
|
||||
profile.UpdatedAt = time.Now()
|
||||
now := time.Now()
|
||||
|
||||
switch state {
|
||||
case coredata.ProfileStatePending:
|
||||
profile.MarkPending(now)
|
||||
case coredata.ProfileStateActive:
|
||||
profile.MarkActive(now)
|
||||
case coredata.ProfileStateDeactivated:
|
||||
profile.MarkDeactivated(now)
|
||||
}
|
||||
|
||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update profile: %w", err)
|
||||
}
|
||||
|
||||
if state == coredata.ProfileStateInactive {
|
||||
if state == coredata.ProfileStateDeactivated {
|
||||
signatures := &coredata.DocumentVersionSignatures{}
|
||||
if err := signatures.DeleteRequestedBySignatory(ctx, tx, scope, profile.ID); err != nil {
|
||||
return fmt.Errorf("cannot delete requested signatures: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user