From bcda048eab799284fc46d74706334bf9ef76dc83 Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:43:25 +0200 Subject: [feature] Self-serve email change for users (#2957) * [feature] Email change * frontend stuff for changing email * docs * tests etc * differentiate more clearly between local user+account and account * populate user --- internal/processing/admin/accountapprove.go | 79 ------------- internal/processing/admin/accountapprove_test.go | 75 ------------ internal/processing/admin/accountreject.go | 113 ------------------ internal/processing/admin/accountreject_test.go | 142 ----------------------- internal/processing/admin/signupapprove.go | 82 +++++++++++++ internal/processing/admin/signupapprove_test.go | 75 ++++++++++++ internal/processing/admin/signupreject.go | 116 ++++++++++++++++++ internal/processing/admin/signupreject_test.go | 142 +++++++++++++++++++++++ 8 files changed, 415 insertions(+), 409 deletions(-) delete mode 100644 internal/processing/admin/accountapprove.go delete mode 100644 internal/processing/admin/accountapprove_test.go delete mode 100644 internal/processing/admin/accountreject.go delete mode 100644 internal/processing/admin/accountreject_test.go create mode 100644 internal/processing/admin/signupapprove.go create mode 100644 internal/processing/admin/signupapprove_test.go create mode 100644 internal/processing/admin/signupreject.go create mode 100644 internal/processing/admin/signupreject_test.go (limited to 'internal/processing/admin') diff --git a/internal/processing/admin/accountapprove.go b/internal/processing/admin/accountapprove.go deleted file mode 100644 index c3f6409c3..000000000 --- a/internal/processing/admin/accountapprove.go +++ /dev/null @@ -1,79 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package admin - -import ( - "context" - "errors" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/ap" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/messages" -) - -func (p *Processor) AccountApprove( - ctx context.Context, - adminAcct *gtsmodel.Account, - accountID string, -) (*apimodel.AdminAccountInfo, gtserror.WithCode) { - user, err := p.state.DB.GetUserByAccountID(ctx, accountID) - if err != nil && !errors.Is(err, db.ErrNoEntries) { - err := gtserror.Newf("db error getting user for account id %s: %w", accountID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - if user == nil { - err := fmt.Errorf("user for account %s not found", accountID) - return nil, gtserror.NewErrorNotFound(err, err.Error()) - } - - // Get a lock on the account URI, - // to ensure it's not also being - // rejected at the same time! - unlock := p.state.ProcessingLocks.Lock(user.Account.URI) - defer unlock() - - if !*user.Approved { - // Process approval side effects asynschronously. - p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ - APObjectType: ap.ActorPerson, - APActivityType: ap.ActivityAccept, - GTSModel: user, - Origin: adminAcct, - Target: user.Account, - }) - } - - apiAccount, err := p.converter.AccountToAdminAPIAccount(ctx, user.Account) - if err != nil { - err := gtserror.Newf("error converting account %s to admin api model: %w", accountID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - // Optimistically set approved to true and - // clear sign-up IP to reflect state that - // will be produced by side effects. - apiAccount.Approved = true - apiAccount.IP = nil - - return apiAccount, nil -} diff --git a/internal/processing/admin/accountapprove_test.go b/internal/processing/admin/accountapprove_test.go deleted file mode 100644 index b6ca1ed32..000000000 --- a/internal/processing/admin/accountapprove_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package admin_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type AdminApproveTestSuite struct { - AdminStandardTestSuite -} - -func (suite *AdminApproveTestSuite) TestApprove() { - var ( - ctx = context.Background() - adminAcct = suite.testAccounts["admin_account"] - targetAcct = suite.testAccounts["unconfirmed_account"] - targetUser = new(gtsmodel.User) - ) - - // Copy user since we're modifying it. - *targetUser = *suite.testUsers["unconfirmed_account"] - - // Approve the sign-up. - acct, errWithCode := suite.adminProcessor.AccountApprove( - ctx, - adminAcct, - targetAcct.ID, - ) - if errWithCode != nil { - suite.FailNow(errWithCode.Error()) - } - - // Account should be approved. - suite.NotNil(acct) - suite.True(acct.Approved) - suite.Nil(acct.IP) - - // Wait for processor to - // handle side effects. - var ( - dbUser *gtsmodel.User - err error - ) - if !testrig.WaitFor(func() bool { - dbUser, err = suite.state.DB.GetUserByID(ctx, targetUser.ID) - return err == nil && dbUser != nil && *dbUser.Approved - }) { - suite.FailNow("waiting for approved user") - } -} - -func TestAdminApproveTestSuite(t *testing.T) { - suite.Run(t, new(AdminApproveTestSuite)) -} diff --git a/internal/processing/admin/accountreject.go b/internal/processing/admin/accountreject.go deleted file mode 100644 index 8cb54cad6..000000000 --- a/internal/processing/admin/accountreject.go +++ /dev/null @@ -1,113 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package admin - -import ( - "context" - "errors" - "fmt" - - "github.com/superseriousbusiness/gotosocial/internal/ap" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtserror" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/messages" -) - -func (p *Processor) AccountReject( - ctx context.Context, - adminAcct *gtsmodel.Account, - accountID string, - privateComment string, - sendEmail bool, - message string, -) (*apimodel.AdminAccountInfo, gtserror.WithCode) { - user, err := p.state.DB.GetUserByAccountID(ctx, accountID) - if err != nil && !errors.Is(err, db.ErrNoEntries) { - err := gtserror.Newf("db error getting user for account id %s: %w", accountID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - if user == nil { - err := fmt.Errorf("user for account %s not found", accountID) - return nil, gtserror.NewErrorNotFound(err, err.Error()) - } - - // Get a lock on the account URI, - // since we're going to be deleting - // it and its associated user. - unlock := p.state.ProcessingLocks.Lock(user.Account.URI) - defer unlock() - - // Can't reject an account with a - // user that's already been approved. - if *user.Approved { - err := fmt.Errorf("account %s has already been approved", accountID) - return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) - } - - // Convert to API account *before* doing the - // rejection, since the rejection will cause - // the user and account to be removed. - apiAccount, err := p.converter.AccountToAdminAPIAccount(ctx, user.Account) - if err != nil { - err := gtserror.Newf("error converting account %s to admin api model: %w", accountID, err) - return nil, gtserror.NewErrorInternalError(err) - } - - // Set approved to false on the API model, to - // reflect the changes that will occur - // asynchronously in the processor. - apiAccount.Approved = false - - // Ensure we an email address. - var email string - if user.Email != "" { - email = user.Email - } else { - email = user.UnconfirmedEmail - } - - // Create a denied user entry for - // the worker to process + store. - deniedUser := >smodel.DeniedUser{ - ID: user.ID, - Email: email, - Username: user.Account.Username, - SignUpIP: user.SignUpIP, - InviteID: user.InviteID, - Locale: user.Locale, - CreatedByApplicationID: user.CreatedByApplicationID, - SignUpReason: user.Reason, - PrivateComment: privateComment, - SendEmail: &sendEmail, - Message: message, - } - - // Process rejection side effects asynschronously. - p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ - APObjectType: ap.ActorPerson, - APActivityType: ap.ActivityReject, - GTSModel: deniedUser, - Origin: adminAcct, - Target: user.Account, - }) - - return apiAccount, nil -} diff --git a/internal/processing/admin/accountreject_test.go b/internal/processing/admin/accountreject_test.go deleted file mode 100644 index 071401afc..000000000 --- a/internal/processing/admin/accountreject_test.go +++ /dev/null @@ -1,142 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package admin_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/testrig" -) - -type AdminRejectTestSuite struct { - AdminStandardTestSuite -} - -func (suite *AdminRejectTestSuite) TestReject() { - var ( - ctx = context.Background() - adminAcct = suite.testAccounts["admin_account"] - targetAcct = suite.testAccounts["unconfirmed_account"] - targetUser = suite.testUsers["unconfirmed_account"] - privateComment = "It's a no from me chief." - sendEmail = true - message = "Too stinky." - ) - - acct, errWithCode := suite.adminProcessor.AccountReject( - ctx, - adminAcct, - targetAcct.ID, - privateComment, - sendEmail, - message, - ) - if errWithCode != nil { - suite.FailNow(errWithCode.Error()) - } - suite.NotNil(acct) - suite.False(acct.Approved) - - // Wait for processor to - // handle side effects. - var ( - deniedUser *gtsmodel.DeniedUser - err error - ) - if !testrig.WaitFor(func() bool { - deniedUser, err = suite.state.DB.GetDeniedUserByID(ctx, targetUser.ID) - return deniedUser != nil && err == nil - }) { - suite.FailNow("waiting for denied user") - } - - // Ensure fields as expected. - suite.Equal(targetUser.ID, deniedUser.ID) - suite.Equal(targetUser.UnconfirmedEmail, deniedUser.Email) - suite.Equal(targetAcct.Username, deniedUser.Username) - suite.Equal(targetUser.SignUpIP, deniedUser.SignUpIP) - suite.Equal(targetUser.InviteID, deniedUser.InviteID) - suite.Equal(targetUser.Locale, deniedUser.Locale) - suite.Equal(targetUser.CreatedByApplicationID, deniedUser.CreatedByApplicationID) - suite.Equal(targetUser.Reason, deniedUser.SignUpReason) - suite.Equal(privateComment, deniedUser.PrivateComment) - suite.Equal(sendEmail, *deniedUser.SendEmail) - suite.Equal(message, deniedUser.Message) - - // Should be no user entry for - // this denied request now. - _, err = suite.state.DB.GetUserByID(ctx, targetUser.ID) - suite.ErrorIs(db.ErrNoEntries, err) - - // Should be no account entry for - // this denied request now. - _, err = suite.state.DB.GetAccountByID(ctx, targetAcct.ID) - suite.ErrorIs(db.ErrNoEntries, err) -} - -func (suite *AdminRejectTestSuite) TestRejectRemote() { - var ( - ctx = context.Background() - adminAcct = suite.testAccounts["admin_account"] - targetAcct = suite.testAccounts["remote_account_1"] - privateComment = "It's a no from me chief." - sendEmail = true - message = "Too stinky." - ) - - // Try to reject a remote account. - _, err := suite.adminProcessor.AccountReject( - ctx, - adminAcct, - targetAcct.ID, - privateComment, - sendEmail, - message, - ) - suite.EqualError(err, "user for account 01F8MH5ZK5VRH73AKHQM6Y9VNX not found") -} - -func (suite *AdminRejectTestSuite) TestRejectApproved() { - var ( - ctx = context.Background() - adminAcct = suite.testAccounts["admin_account"] - targetAcct = suite.testAccounts["local_account_1"] - privateComment = "It's a no from me chief." - sendEmail = true - message = "Too stinky." - ) - - // Try to reject an already-approved account. - _, err := suite.adminProcessor.AccountReject( - ctx, - adminAcct, - targetAcct.ID, - privateComment, - sendEmail, - message, - ) - suite.EqualError(err, "account 01F8MH1H7YV1Z7D2C8K2730QBF has already been approved") -} - -func TestAdminRejectTestSuite(t *testing.T) { - suite.Run(t, new(AdminRejectTestSuite)) -} diff --git a/internal/processing/admin/signupapprove.go b/internal/processing/admin/signupapprove.go new file mode 100644 index 000000000..84e04fa8d --- /dev/null +++ b/internal/processing/admin/signupapprove.go @@ -0,0 +1,82 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package admin + +import ( + "context" + "errors" + "fmt" + + "github.com/superseriousbusiness/gotosocial/internal/ap" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/messages" +) + +func (p *Processor) SignupApprove( + ctx context.Context, + adminAcct *gtsmodel.Account, + accountID string, +) (*apimodel.AdminAccountInfo, gtserror.WithCode) { + user, err := p.state.DB.GetUserByAccountID(ctx, accountID) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := gtserror.Newf("db error getting user for account id %s: %w", accountID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + if user == nil { + err := fmt.Errorf("user for account %s not found", accountID) + return nil, gtserror.NewErrorNotFound(err, err.Error()) + } + + // Get a lock on the account URI, + // to ensure it's not also being + // rejected at the same time! + unlock := p.state.ProcessingLocks.Lock(user.Account.URI) + defer unlock() + + if !*user.Approved { + // Process approval side effects asynschronously. + p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ + // Use ap.ObjectProfile here to + // distinguish this message (user model) + // from ap.ActorPerson (account model). + APObjectType: ap.ObjectProfile, + APActivityType: ap.ActivityAccept, + GTSModel: user, + Origin: adminAcct, + Target: user.Account, + }) + } + + apiAccount, err := p.converter.AccountToAdminAPIAccount(ctx, user.Account) + if err != nil { + err := gtserror.Newf("error converting account %s to admin api model: %w", accountID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + // Optimistically set approved to true and + // clear sign-up IP to reflect state that + // will be produced by side effects. + apiAccount.Approved = true + apiAccount.IP = nil + + return apiAccount, nil +} diff --git a/internal/processing/admin/signupapprove_test.go b/internal/processing/admin/signupapprove_test.go new file mode 100644 index 000000000..58b8fdade --- /dev/null +++ b/internal/processing/admin/signupapprove_test.go @@ -0,0 +1,75 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package admin_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type AdminApproveTestSuite struct { + AdminStandardTestSuite +} + +func (suite *AdminApproveTestSuite) TestApprove() { + var ( + ctx = context.Background() + adminAcct = suite.testAccounts["admin_account"] + targetAcct = suite.testAccounts["unconfirmed_account"] + targetUser = new(gtsmodel.User) + ) + + // Copy user since we're modifying it. + *targetUser = *suite.testUsers["unconfirmed_account"] + + // Approve the sign-up. + acct, errWithCode := suite.adminProcessor.SignupApprove( + ctx, + adminAcct, + targetAcct.ID, + ) + if errWithCode != nil { + suite.FailNow(errWithCode.Error()) + } + + // Account should be approved. + suite.NotNil(acct) + suite.True(acct.Approved) + suite.Nil(acct.IP) + + // Wait for processor to + // handle side effects. + var ( + dbUser *gtsmodel.User + err error + ) + if !testrig.WaitFor(func() bool { + dbUser, err = suite.state.DB.GetUserByID(ctx, targetUser.ID) + return err == nil && dbUser != nil && *dbUser.Approved + }) { + suite.FailNow("waiting for approved user") + } +} + +func TestAdminApproveTestSuite(t *testing.T) { + suite.Run(t, new(AdminApproveTestSuite)) +} diff --git a/internal/processing/admin/signupreject.go b/internal/processing/admin/signupreject.go new file mode 100644 index 000000000..39eff0b87 --- /dev/null +++ b/internal/processing/admin/signupreject.go @@ -0,0 +1,116 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package admin + +import ( + "context" + "errors" + "fmt" + + "github.com/superseriousbusiness/gotosocial/internal/ap" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/messages" +) + +func (p *Processor) SignupReject( + ctx context.Context, + adminAcct *gtsmodel.Account, + accountID string, + privateComment string, + sendEmail bool, + message string, +) (*apimodel.AdminAccountInfo, gtserror.WithCode) { + user, err := p.state.DB.GetUserByAccountID(ctx, accountID) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err := gtserror.Newf("db error getting user for account id %s: %w", accountID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + if user == nil { + err := fmt.Errorf("user for account %s not found", accountID) + return nil, gtserror.NewErrorNotFound(err, err.Error()) + } + + // Get a lock on the account URI, + // since we're going to be deleting + // it and its associated user. + unlock := p.state.ProcessingLocks.Lock(user.Account.URI) + defer unlock() + + // Can't reject an account with a + // user that's already been approved. + if *user.Approved { + err := fmt.Errorf("account %s has already been approved", accountID) + return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error()) + } + + // Convert to API account *before* doing the + // rejection, since the rejection will cause + // the user and account to be removed. + apiAccount, err := p.converter.AccountToAdminAPIAccount(ctx, user.Account) + if err != nil { + err := gtserror.Newf("error converting account %s to admin api model: %w", accountID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + // Set approved to false on the API model, to + // reflect the changes that will occur + // asynchronously in the processor. + apiAccount.Approved = false + + // Ensure we an email address. + var email string + if user.Email != "" { + email = user.Email + } else { + email = user.UnconfirmedEmail + } + + // Create a denied user entry for + // the worker to process + store. + deniedUser := >smodel.DeniedUser{ + ID: user.ID, + Email: email, + Username: user.Account.Username, + SignUpIP: user.SignUpIP, + InviteID: user.InviteID, + Locale: user.Locale, + CreatedByApplicationID: user.CreatedByApplicationID, + SignUpReason: user.Reason, + PrivateComment: privateComment, + SendEmail: &sendEmail, + Message: message, + } + + // Process rejection side effects asynschronously. + p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ + // Use ap.ObjectProfile here to + // distinguish this message (user model) + // from ap.ActorPerson (account model). + APObjectType: ap.ObjectProfile, + APActivityType: ap.ActivityReject, + GTSModel: deniedUser, + Origin: adminAcct, + Target: user.Account, + }) + + return apiAccount, nil +} diff --git a/internal/processing/admin/signupreject_test.go b/internal/processing/admin/signupreject_test.go new file mode 100644 index 000000000..cb6a25eb3 --- /dev/null +++ b/internal/processing/admin/signupreject_test.go @@ -0,0 +1,142 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package admin_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type AdminRejectTestSuite struct { + AdminStandardTestSuite +} + +func (suite *AdminRejectTestSuite) TestReject() { + var ( + ctx = context.Background() + adminAcct = suite.testAccounts["admin_account"] + targetAcct = suite.testAccounts["unconfirmed_account"] + targetUser = suite.testUsers["unconfirmed_account"] + privateComment = "It's a no from me chief." + sendEmail = true + message = "Too stinky." + ) + + acct, errWithCode := suite.adminProcessor.SignupReject( + ctx, + adminAcct, + targetAcct.ID, + privateComment, + sendEmail, + message, + ) + if errWithCode != nil { + suite.FailNow(errWithCode.Error()) + } + suite.NotNil(acct) + suite.False(acct.Approved) + + // Wait for processor to + // handle side effects. + var ( + deniedUser *gtsmodel.DeniedUser + err error + ) + if !testrig.WaitFor(func() bool { + deniedUser, err = suite.state.DB.GetDeniedUserByID(ctx, targetUser.ID) + return deniedUser != nil && err == nil + }) { + suite.FailNow("waiting for denied user") + } + + // Ensure fields as expected. + suite.Equal(targetUser.ID, deniedUser.ID) + suite.Equal(targetUser.UnconfirmedEmail, deniedUser.Email) + suite.Equal(targetAcct.Username, deniedUser.Username) + suite.Equal(targetUser.SignUpIP, deniedUser.SignUpIP) + suite.Equal(targetUser.InviteID, deniedUser.InviteID) + suite.Equal(targetUser.Locale, deniedUser.Locale) + suite.Equal(targetUser.CreatedByApplicationID, deniedUser.CreatedByApplicationID) + suite.Equal(targetUser.Reason, deniedUser.SignUpReason) + suite.Equal(privateComment, deniedUser.PrivateComment) + suite.Equal(sendEmail, *deniedUser.SendEmail) + suite.Equal(message, deniedUser.Message) + + // Should be no user entry for + // this denied request now. + _, err = suite.state.DB.GetUserByID(ctx, targetUser.ID) + suite.ErrorIs(db.ErrNoEntries, err) + + // Should be no account entry for + // this denied request now. + _, err = suite.state.DB.GetAccountByID(ctx, targetAcct.ID) + suite.ErrorIs(db.ErrNoEntries, err) +} + +func (suite *AdminRejectTestSuite) TestRejectRemote() { + var ( + ctx = context.Background() + adminAcct = suite.testAccounts["admin_account"] + targetAcct = suite.testAccounts["remote_account_1"] + privateComment = "It's a no from me chief." + sendEmail = true + message = "Too stinky." + ) + + // Try to reject a remote account. + _, err := suite.adminProcessor.SignupReject( + ctx, + adminAcct, + targetAcct.ID, + privateComment, + sendEmail, + message, + ) + suite.EqualError(err, "user for account 01F8MH5ZK5VRH73AKHQM6Y9VNX not found") +} + +func (suite *AdminRejectTestSuite) TestRejectApproved() { + var ( + ctx = context.Background() + adminAcct = suite.testAccounts["admin_account"] + targetAcct = suite.testAccounts["local_account_1"] + privateComment = "It's a no from me chief." + sendEmail = true + message = "Too stinky." + ) + + // Try to reject an already-approved account. + _, err := suite.adminProcessor.SignupReject( + ctx, + adminAcct, + targetAcct.ID, + privateComment, + sendEmail, + message, + ) + suite.EqualError(err, "account 01F8MH1H7YV1Z7D2C8K2730QBF has already been approved") +} + +func TestAdminRejectTestSuite(t *testing.T) { + suite.Run(t, new(AdminRejectTestSuite)) +} -- cgit v1.2.3