[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:
tobi
2024-04-13 13:25:10 +02:00
committed by GitHub
parent 1439042104
commit 89e0cfd874
74 changed files with 4102 additions and 545 deletions

View File

@ -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
}

View File

@ -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 {