[management] Add user approval (#4411)

This PR adds user approval functionality to the management system, allowing administrators to manually approve new users joining via domain matching. When enabled, users are blocked with pending approval status until explicitly approved by an admin.

Adds UserApprovalRequired setting to control manual user approval requirement
Introduces user approval and rejection endpoints with corresponding business logic
Prevents pending approval users from adding peers or logging in
This commit is contained in:
Maycon Santos
2025-09-01 13:00:45 -03:00
committed by GitHub
parent 21368b38d9
commit d39fcfd62a
19 changed files with 842 additions and 48 deletions

View File

@@ -83,6 +83,9 @@ type ExtraSettings struct {
// PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator
PeerApprovalEnabled bool
// UserApprovalRequired enables or disables the need for users joining via domain matching to be approved by an administrator
UserApprovalRequired bool
// IntegratedValidator is the string enum for the integrated validator type
IntegratedValidator string
// IntegratedValidatorGroups list of group IDs to be used with integrated approval configurations
@@ -99,6 +102,7 @@ type ExtraSettings struct {
func (e *ExtraSettings) Copy() *ExtraSettings {
return &ExtraSettings{
PeerApprovalEnabled: e.PeerApprovalEnabled,
UserApprovalRequired: e.UserApprovalRequired,
IntegratedValidatorGroups: slices.Clone(e.IntegratedValidatorGroups),
IntegratedValidator: e.IntegratedValidator,
FlowEnabled: e.FlowEnabled,

View File

@@ -64,6 +64,7 @@ type UserInfo struct {
NonDeletable bool `json:"non_deletable"`
LastLogin time.Time `json:"last_login"`
Issued string `json:"issued"`
PendingApproval bool `json:"pending_approval"`
IntegrationReference integration_reference.IntegrationReference `json:"-"`
}
@@ -84,6 +85,8 @@ type User struct {
PATsG []PersonalAccessToken `json:"-" gorm:"foreignKey:UserID;references:id;constraint:OnDelete:CASCADE;"`
// Blocked indicates whether the user is blocked. Blocked users can't use the system.
Blocked bool
// PendingApproval indicates whether the user requires approval before being activated
PendingApproval bool
// LastLogin is the last time the user logged in to IdP
LastLogin *time.Time
// CreatedAt records the time the user was created
@@ -141,16 +144,17 @@ func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
if userData == nil {
return &UserInfo{
ID: u.Id,
Email: "",
Name: u.ServiceUserName,
Role: string(u.Role),
AutoGroups: u.AutoGroups,
Status: string(UserStatusActive),
IsServiceUser: u.IsServiceUser,
IsBlocked: u.Blocked,
LastLogin: u.GetLastLogin(),
Issued: u.Issued,
ID: u.Id,
Email: "",
Name: u.ServiceUserName,
Role: string(u.Role),
AutoGroups: u.AutoGroups,
Status: string(UserStatusActive),
IsServiceUser: u.IsServiceUser,
IsBlocked: u.Blocked,
LastLogin: u.GetLastLogin(),
Issued: u.Issued,
PendingApproval: u.PendingApproval,
}, nil
}
if userData.ID != u.Id {
@@ -163,16 +167,17 @@ func (u *User) ToUserInfo(userData *idp.UserData) (*UserInfo, error) {
}
return &UserInfo{
ID: u.Id,
Email: userData.Email,
Name: userData.Name,
Role: string(u.Role),
AutoGroups: autoGroups,
Status: string(userStatus),
IsServiceUser: u.IsServiceUser,
IsBlocked: u.Blocked,
LastLogin: u.GetLastLogin(),
Issued: u.Issued,
ID: u.Id,
Email: userData.Email,
Name: userData.Name,
Role: string(u.Role),
AutoGroups: autoGroups,
Status: string(userStatus),
IsServiceUser: u.IsServiceUser,
IsBlocked: u.Blocked,
LastLogin: u.GetLastLogin(),
Issued: u.Issued,
PendingApproval: u.PendingApproval,
}, nil
}
@@ -194,6 +199,7 @@ func (u *User) Copy() *User {
ServiceUserName: u.ServiceUserName,
PATs: pats,
Blocked: u.Blocked,
PendingApproval: u.PendingApproval,
LastLogin: u.LastLogin,
CreatedAt: u.CreatedAt,
Issued: u.Issued,