mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Admin accounts endpoints; approve/reject sign-ups (#2826)
* update settings panels, add pending overview + approve/deny functions * add admin accounts get, approve, reject * send approved/rejected emails * use signup URL * docs! * email * swagger * web linting * fix email tests * wee lil fixerinos * use new paging logic for GetAccounts() series of admin endpoints, small changes to query building * shuffle useAccountIDIn check *before* adding to query * fix parse from toot react error * use `netip.Addr` * put valid slices in globals * optimistic updates for account state --------- Co-authored-by: kim <grufwub@gmail.com>
This commit is contained in:
@ -33,6 +33,7 @@ import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/account"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// clientAPI wraps processing functions
|
||||
@ -141,6 +142,10 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg messages.From
|
||||
// ACCEPT FOLLOW (request)
|
||||
case ap.ActivityFollow:
|
||||
return p.clientAPI.AcceptFollow(ctx, cMsg)
|
||||
|
||||
// ACCEPT PROFILE/ACCOUNT (sign-up)
|
||||
case ap.ObjectProfile, ap.ActorPerson:
|
||||
return p.clientAPI.AcceptAccount(ctx, cMsg)
|
||||
}
|
||||
|
||||
// REJECT SOMETHING
|
||||
@ -150,6 +155,10 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg messages.From
|
||||
// REJECT FOLLOW (request)
|
||||
case ap.ActivityFollow:
|
||||
return p.clientAPI.RejectFollowRequest(ctx, cMsg)
|
||||
|
||||
// REJECT PROFILE/ACCOUNT (sign-up)
|
||||
case ap.ObjectProfile, ap.ActorPerson:
|
||||
return p.clientAPI.RejectAccount(ctx, cMsg)
|
||||
}
|
||||
|
||||
// UNDO SOMETHING
|
||||
@ -685,3 +694,66 @@ func (p *clientAPI) MoveAccount(ctx context.Context, cMsg messages.FromClientAPI
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) AcceptAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
newUser, ok := cMsg.GTSModel.(*gtsmodel.User)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.User", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Mark user as approved + clear sign-up IP.
|
||||
newUser.Approved = util.Ptr(true)
|
||||
newUser.SignUpIP = nil
|
||||
if err := p.state.DB.UpdateUser(ctx, newUser, "approved", "sign_up_ip"); err != nil {
|
||||
// Error now means we should return without
|
||||
// sending email + let admin try to approve again.
|
||||
return gtserror.Newf("db error updating user %s: %w", newUser.ID, err)
|
||||
}
|
||||
|
||||
// Send "your sign-up has been approved" email to the new user.
|
||||
if err := p.surface.emailUserSignupApproved(ctx, newUser); err != nil {
|
||||
log.Errorf(ctx, "error emailing: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) RejectAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
deniedUser, ok := cMsg.GTSModel.(*gtsmodel.DeniedUser)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.DeniedUser", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Remove the account.
|
||||
if err := p.state.DB.DeleteAccount(ctx, cMsg.TargetAccount.ID); err != nil {
|
||||
log.Errorf(ctx,
|
||||
"db error deleting account %s: %v",
|
||||
cMsg.TargetAccount.ID, err,
|
||||
)
|
||||
}
|
||||
|
||||
// Remove the user.
|
||||
if err := p.state.DB.DeleteUserByID(ctx, deniedUser.ID); err != nil {
|
||||
log.Errorf(ctx,
|
||||
"db error deleting user %s: %v",
|
||||
deniedUser.ID, err,
|
||||
)
|
||||
}
|
||||
|
||||
// Store the deniedUser entry.
|
||||
if err := p.state.DB.PutDeniedUser(ctx, deniedUser); err != nil {
|
||||
log.Errorf(ctx,
|
||||
"db error putting denied user %s: %v",
|
||||
deniedUser.ID, err,
|
||||
)
|
||||
}
|
||||
|
||||
if *deniedUser.SendEmail {
|
||||
// Send "your sign-up has been rejected" email to the denied user.
|
||||
if err := p.surface.emailUserSignupRejected(ctx, deniedUser); err != nil {
|
||||
log.Errorf(ctx, "error emailing: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -129,6 +129,69 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
||||
return nil
|
||||
}
|
||||
|
||||
// emailUserSignupApproved emails the given user
|
||||
// to inform them their sign-up has been approved.
|
||||
func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.User) error {
|
||||
// User may have been approved without
|
||||
// their email address being confirmed
|
||||
// yet. Just send to whatever we have.
|
||||
emailAddr := user.Email
|
||||
if emailAddr == "" {
|
||||
emailAddr = user.UnconfirmedEmail
|
||||
}
|
||||
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("db error getting instance: %w", err)
|
||||
}
|
||||
|
||||
// Assemble email contents and send the email.
|
||||
if err := s.emailSender.SendSignupApprovedEmail(
|
||||
emailAddr,
|
||||
email.SignupApprovedData{
|
||||
Username: user.Account.Username,
|
||||
InstanceURL: instance.URI,
|
||||
InstanceName: instance.Title,
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Email sent, update the user
|
||||
// entry with the emailed time.
|
||||
now := time.Now()
|
||||
user.LastEmailedAt = now
|
||||
|
||||
if err := s.state.DB.UpdateUser(
|
||||
ctx,
|
||||
user,
|
||||
"last_emailed_at",
|
||||
); err != nil {
|
||||
return gtserror.Newf("error updating user entry after email sent: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// emailUserSignupApproved emails the given user
|
||||
// to inform them their sign-up has been approved.
|
||||
func (s *surface) emailUserSignupRejected(ctx context.Context, deniedUser *gtsmodel.DeniedUser) error {
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("db error getting instance: %w", err)
|
||||
}
|
||||
|
||||
// Assemble email contents and send the email.
|
||||
return s.emailSender.SendSignupRejectedEmail(
|
||||
deniedUser.Email,
|
||||
email.SignupRejectedData{
|
||||
Message: deniedUser.Message,
|
||||
InstanceURL: instance.URI,
|
||||
InstanceName: instance.Title,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// emailAdminReportOpened emails all active moderators/admins
|
||||
// of this instance that a new report has been created.
|
||||
func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.Report) error {
|
||||
@ -193,7 +256,7 @@ func (s *surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.Use
|
||||
SignupEmail: newUser.UnconfirmedEmail,
|
||||
SignupUsername: newUser.Account.Username,
|
||||
SignupReason: newUser.Reason,
|
||||
SignupURL: "TODO",
|
||||
SignupURL: instance.URI + "/settings/admin/accounts/" + newUser.AccountID,
|
||||
}
|
||||
|
||||
if err := s.emailSender.SendNewSignupEmail(toAddresses, newSignupData); err != nil {
|
||||
|
Reference in New Issue
Block a user