summaryrefslogtreecommitdiff
path: root/internal/processing
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2024-06-06 15:43:25 +0200
committerLibravatar GitHub <noreply@github.com>2024-06-06 14:43:25 +0100
commitbcda048eab799284fc46d74706334bf9ef76dc83 (patch)
treec4595fe5e6e6fd570d59cee7095a336f2e884344 /internal/processing
parentdrop date (#2969) (diff)
downloadgotosocial-bcda048eab799284fc46d74706334bf9ef76dc83.tar.xz
[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
Diffstat (limited to 'internal/processing')
-rw-r--r--internal/processing/account/account.go4
-rw-r--r--internal/processing/account/account_test.go5
-rw-r--r--internal/processing/account/delete.go17
-rw-r--r--internal/processing/account/update.go2
-rw-r--r--internal/processing/account/update_test.go10
-rw-r--r--internal/processing/account_test.go106
-rw-r--r--internal/processing/admin/signupapprove.go (renamed from internal/processing/admin/accountapprove.go)7
-rw-r--r--internal/processing/admin/signupapprove_test.go (renamed from internal/processing/admin/accountapprove_test.go)2
-rw-r--r--internal/processing/admin/signupreject.go (renamed from internal/processing/admin/accountreject.go)7
-rw-r--r--internal/processing/admin/signupreject_test.go (renamed from internal/processing/admin/accountreject_test.go)6
-rw-r--r--internal/processing/processor.go6
-rw-r--r--internal/processing/report/create.go2
-rw-r--r--internal/processing/user/create.go (renamed from internal/processing/account/create.go)12
-rw-r--r--internal/processing/user/delete.go48
-rw-r--r--internal/processing/user/email.go81
-rw-r--r--internal/processing/user/get.go32
-rw-r--r--internal/processing/user/user.go14
-rw-r--r--internal/processing/user/user_test.go3
-rw-r--r--internal/processing/workers/fromclientapi.go66
-rw-r--r--internal/processing/workers/fromfediapi.go12
-rw-r--r--internal/processing/workers/fromfediapi_test.go4
-rw-r--r--internal/processing/workers/surfaceemail.go6
22 files changed, 263 insertions, 189 deletions
diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go
index dbcecdb0a..65bb40292 100644
--- a/internal/processing/account/account.go
+++ b/internal/processing/account/account.go
@@ -22,7 +22,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/media"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/processing/common"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/text"
@@ -39,7 +38,6 @@ type Processor struct {
state *state.State
converter *typeutils.Converter
mediaManager *media.Manager
- oauthServer oauth.Server
filter *visibility.Filter
formatter *text.Formatter
federator *federation.Federator
@@ -53,7 +51,6 @@ func New(
state *state.State,
converter *typeutils.Converter,
mediaManager *media.Manager,
- oauthServer oauth.Server,
federator *federation.Federator,
filter *visibility.Filter,
parseMention gtsmodel.ParseMentionFunc,
@@ -63,7 +60,6 @@ func New(
state: state,
converter: converter,
mediaManager: mediaManager,
- oauthServer: oauthServer,
filter: filter,
formatter: text.NewFormatter(state.DB),
federator: federator,
diff --git a/internal/processing/account/account_test.go b/internal/processing/account/account_test.go
index 10d5f91e1..556f4d91f 100644
--- a/internal/processing/account/account_test.go
+++ b/internal/processing/account/account_test.go
@@ -29,7 +29,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/messages"
- "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/processing/account"
"github.com/superseriousbusiness/gotosocial/internal/processing/common"
@@ -48,7 +47,6 @@ type AccountStandardTestSuite struct {
storage *storage.Driver
state state.State
mediaManager *media.Manager
- oauthServer oauth.Server
transportController transport.Controller
federator *federation.Federator
emailSender email.Sender
@@ -106,7 +104,6 @@ func (suite *AccountStandardTestSuite) SetupTest() {
suite.storage = testrig.NewInMemoryStorage()
suite.state.Storage = suite.storage
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
- suite.oauthServer = testrig.NewTestOauthServer(suite.db)
suite.transportController = testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../testrig/media"))
suite.federator = testrig.NewTestFederator(&suite.state, suite.transportController, suite.mediaManager)
@@ -115,7 +112,7 @@ func (suite *AccountStandardTestSuite) SetupTest() {
filter := visibility.NewFilter(&suite.state)
common := common.New(&suite.state, suite.tc, suite.federator, filter)
- suite.accountProcessor = account.New(&common, &suite.state, suite.tc, suite.mediaManager, suite.oauthServer, suite.federator, filter, processing.GetParseMentionFunc(&suite.state, suite.federator))
+ suite.accountProcessor = account.New(&common, &suite.state, suite.tc, suite.mediaManager, suite.federator, filter, processing.GetParseMentionFunc(&suite.state, suite.federator))
testrig.StandardDBSetup(suite.db, nil)
testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")
}
diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go
index 3f051edf0..075e94544 100644
--- a/internal/processing/account/delete.go
+++ b/internal/processing/account/delete.go
@@ -95,23 +95,6 @@ func (p *Processor) Delete(
return nil
}
-// DeleteSelf is like Delete, but specifically for local accounts deleting themselves.
-//
-// Calling DeleteSelf results in a delete message being enqueued in the processor,
-// which causes side effects to occur: delete will be federated out to other instances,
-// and the above Delete function will be called afterwards from the processor, to clear
-// out the account's bits and bobs, and stubbify it.
-func (p *Processor) DeleteSelf(ctx context.Context, account *gtsmodel.Account) gtserror.WithCode {
- // Process the delete side effects asynchronously.
- p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
- APObjectType: ap.ActorPerson,
- APActivityType: ap.ActivityDelete,
- Origin: account,
- Target: account,
- })
- return nil
-}
-
// deleteUserAndTokensForAccount deletes the gtsmodel.User and
// any OAuth tokens and applications for the given account.
//
diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go
index 7f2749503..ea6abed6e 100644
--- a/internal/processing/account/update.go
+++ b/internal/processing/account/update.go
@@ -297,7 +297,7 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
}
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
- APObjectType: ap.ObjectProfile,
+ APObjectType: ap.ActorPerson,
APActivityType: ap.ActivityUpdate,
GTSModel: account,
Origin: account,
diff --git a/internal/processing/account/update_test.go b/internal/processing/account/update_test.go
index ad09ff25c..a07562544 100644
--- a/internal/processing/account/update_test.go
+++ b/internal/processing/account/update_test.go
@@ -64,7 +64,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateSimple() {
// Profile update.
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
- suite.Equal(ap.ObjectProfile, msg.APObjectType)
+ suite.Equal(ap.ActorPerson, msg.APObjectType)
// Correct account updated.
if msg.Origin == nil {
@@ -114,7 +114,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMention() {
// Profile update.
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
- suite.Equal(ap.ObjectProfile, msg.APObjectType)
+ suite.Equal(ap.ActorPerson, msg.APObjectType)
// Correct account updated.
if msg.Origin == nil {
@@ -170,7 +170,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMarkdownNote() {
// Profile update.
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
- suite.Equal(ap.ObjectProfile, msg.APObjectType)
+ suite.Equal(ap.ActorPerson, msg.APObjectType)
// Correct account updated.
if msg.Origin == nil {
@@ -255,7 +255,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithFields() {
// Profile update.
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
- suite.Equal(ap.ObjectProfile, msg.APObjectType)
+ suite.Equal(ap.ActorPerson, msg.APObjectType)
// Correct account updated.
if msg.Origin == nil {
@@ -312,7 +312,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateNoteNotFields() {
// Profile update.
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
- suite.Equal(ap.ObjectProfile, msg.APObjectType)
+ suite.Equal(ap.ActorPerson, msg.APObjectType)
// Correct account updated.
if msg.Origin == nil {
diff --git a/internal/processing/account_test.go b/internal/processing/account_test.go
deleted file mode 100644
index 82c28115e..000000000
--- a/internal/processing/account_test.go
+++ /dev/null
@@ -1,106 +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 <http://www.gnu.org/licenses/>.
-
-package processing_test
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "testing"
- "time"
-
- "github.com/stretchr/testify/suite"
- "github.com/superseriousbusiness/activity/pub"
- "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
- "github.com/superseriousbusiness/gotosocial/testrig"
-)
-
-type AccountTestSuite struct {
- ProcessingStandardTestSuite
-}
-
-func (suite *AccountTestSuite) TestAccountDeleteLocal() {
- ctx := context.Background()
- deletingAccount := suite.testAccounts["local_account_1"]
- followingAccount := suite.testAccounts["remote_account_1"]
-
- // make the following account follow the deleting account so that a delete message will be sent to it via the federating API
- follow := &gtsmodel.Follow{
- ID: "01FJ1S8DX3STJJ6CEYPMZ1M0R3",
- CreatedAt: time.Now(),
- UpdatedAt: time.Now(),
- URI: fmt.Sprintf("%s/follow/01FJ1S8DX3STJJ6CEYPMZ1M0R3", followingAccount.URI),
- AccountID: followingAccount.ID,
- TargetAccountID: deletingAccount.ID,
- }
- err := suite.db.Put(ctx, follow)
- suite.NoError(err)
-
- errWithCode := suite.processor.Account().DeleteSelf(ctx, suite.testAccounts["local_account_1"])
- suite.NoError(errWithCode)
-
- // the delete should be federated outwards to the following account's inbox
- var sent []byte
- delete := new(struct {
- Actor string `json:"actor"`
- ID string `json:"id"`
- Object string `json:"object"`
- To string `json:"to"`
- CC string `json:"cc"`
- Type string `json:"type"`
- })
-
- if !testrig.WaitFor(func() bool {
- delivery, ok := suite.state.Workers.Delivery.Queue.Pop()
- if !ok {
- return false
- }
- if !testrig.EqualRequestURIs(delivery.Request.URL, *followingAccount.SharedInboxURI) {
- panic("differing request uris")
- }
- sent, err = io.ReadAll(delivery.Request.Body)
- if err != nil {
- panic("error reading body: " + err.Error())
- }
- err = json.Unmarshal(sent, delete)
- if err != nil {
- panic("error unmarshaling json: " + err.Error())
- }
- return true
- }) {
- suite.FailNow("timed out waiting for message")
- }
-
- suite.Equal(deletingAccount.URI, delete.Actor)
- suite.Equal(deletingAccount.URI, delete.Object)
- suite.Equal(deletingAccount.FollowersURI, delete.To)
- suite.Equal(pub.PublicActivityPubIRI, delete.CC)
- suite.Equal("Delete", delete.Type)
-
- if !testrig.WaitFor(func() bool {
- dbAccount, _ := suite.db.GetAccountByID(ctx, deletingAccount.ID)
- return !dbAccount.SuspendedAt.IsZero()
- }) {
- suite.FailNow("timed out waiting for account to be deleted")
- }
-}
-
-func TestAccountTestSuite(t *testing.T) {
- suite.Run(t, &AccountTestSuite{})
-}
diff --git a/internal/processing/admin/accountapprove.go b/internal/processing/admin/signupapprove.go
index c3f6409c3..84e04fa8d 100644
--- a/internal/processing/admin/accountapprove.go
+++ b/internal/processing/admin/signupapprove.go
@@ -30,7 +30,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/messages"
)
-func (p *Processor) AccountApprove(
+func (p *Processor) SignupApprove(
ctx context.Context,
adminAcct *gtsmodel.Account,
accountID string,
@@ -55,7 +55,10 @@ func (p *Processor) AccountApprove(
if !*user.Approved {
// Process approval side effects asynschronously.
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
- APObjectType: ap.ActorPerson,
+ // 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,
diff --git a/internal/processing/admin/accountapprove_test.go b/internal/processing/admin/signupapprove_test.go
index b6ca1ed32..58b8fdade 100644
--- a/internal/processing/admin/accountapprove_test.go
+++ b/internal/processing/admin/signupapprove_test.go
@@ -42,7 +42,7 @@ func (suite *AdminApproveTestSuite) TestApprove() {
*targetUser = *suite.testUsers["unconfirmed_account"]
// Approve the sign-up.
- acct, errWithCode := suite.adminProcessor.AccountApprove(
+ acct, errWithCode := suite.adminProcessor.SignupApprove(
ctx,
adminAcct,
targetAcct.ID,
diff --git a/internal/processing/admin/accountreject.go b/internal/processing/admin/signupreject.go
index 8cb54cad6..39eff0b87 100644
--- a/internal/processing/admin/accountreject.go
+++ b/internal/processing/admin/signupreject.go
@@ -30,7 +30,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/messages"
)
-func (p *Processor) AccountReject(
+func (p *Processor) SignupReject(
ctx context.Context,
adminAcct *gtsmodel.Account,
accountID string,
@@ -102,7 +102,10 @@ func (p *Processor) AccountReject(
// Process rejection side effects asynschronously.
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
- APObjectType: ap.ActorPerson,
+ // 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,
diff --git a/internal/processing/admin/accountreject_test.go b/internal/processing/admin/signupreject_test.go
index 071401afc..cb6a25eb3 100644
--- a/internal/processing/admin/accountreject_test.go
+++ b/internal/processing/admin/signupreject_test.go
@@ -42,7 +42,7 @@ func (suite *AdminRejectTestSuite) TestReject() {
message = "Too stinky."
)
- acct, errWithCode := suite.adminProcessor.AccountReject(
+ acct, errWithCode := suite.adminProcessor.SignupReject(
ctx,
adminAcct,
targetAcct.ID,
@@ -104,7 +104,7 @@ func (suite *AdminRejectTestSuite) TestRejectRemote() {
)
// Try to reject a remote account.
- _, err := suite.adminProcessor.AccountReject(
+ _, err := suite.adminProcessor.SignupReject(
ctx,
adminAcct,
targetAcct.ID,
@@ -126,7 +126,7 @@ func (suite *AdminRejectTestSuite) TestRejectApproved() {
)
// Try to reject an already-approved account.
- _, err := suite.adminProcessor.AccountReject(
+ _, err := suite.adminProcessor.SignupReject(
ctx,
adminAcct,
targetAcct.ID,
diff --git a/internal/processing/processor.go b/internal/processing/processor.go
index 8a18bc45e..1e7997b8f 100644
--- a/internal/processing/processor.go
+++ b/internal/processing/processor.go
@@ -180,13 +180,13 @@ func NewProcessor(
// Start with sub processors that will
// be required by the workers processor.
common := common.New(state, converter, federator, filter)
- processor.account = account.New(&common, state, converter, mediaManager, oauthServer, federator, filter, parseMentionFunc)
+ processor.account = account.New(&common, state, converter, mediaManager, federator, filter, parseMentionFunc)
processor.media = media.New(state, converter, mediaManager, federator.TransportController())
processor.stream = stream.New(state, oauthServer)
// Instantiate the rest of the sub
// processors + pin them to this struct.
- processor.account = account.New(&common, state, converter, mediaManager, oauthServer, federator, filter, parseMentionFunc)
+ processor.account = account.New(&common, state, converter, mediaManager, federator, filter, parseMentionFunc)
processor.admin = admin.New(state, cleaner, converter, mediaManager, federator.TransportController(), emailSender)
processor.fedi = fedi.New(state, &common, converter, federator, filter)
processor.filtersv1 = filtersv1.New(state, converter)
@@ -198,7 +198,7 @@ func NewProcessor(
processor.timeline = timeline.New(state, converter, filter)
processor.search = search.New(state, federator, converter, filter)
processor.status = status.New(state, &common, &processor.polls, federator, converter, filter, parseMentionFunc)
- processor.user = user.New(state, emailSender)
+ processor.user = user.New(state, converter, oauthServer, emailSender)
// Workers processor handles asynchronous
// worker jobs; instantiate it separately
diff --git a/internal/processing/report/create.go b/internal/processing/report/create.go
index cac600006..dd31a8798 100644
--- a/internal/processing/report/create.go
+++ b/internal/processing/report/create.go
@@ -92,7 +92,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
}
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
- APObjectType: ap.ObjectProfile,
+ APObjectType: ap.ActorPerson,
APActivityType: ap.ActivityFlag,
GTSModel: report,
Origin: account,
diff --git a/internal/processing/account/create.go b/internal/processing/user/create.go
index 761165356..0d848583e 100644
--- a/internal/processing/account/create.go
+++ b/internal/processing/user/create.go
@@ -15,7 +15,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-package account
+package user
import (
"context"
@@ -32,10 +32,9 @@ import (
"github.com/superseriousbusiness/oauth2/v4"
)
-// Create processes the given form for creating a new account,
-// returning a new user (with attached account) if successful.
+// Create processes the given form for creating a new user+account.
//
-// App should be the app used to create the account.
+// App should be the app used to create the user+account.
// If nil, the instance app will be used.
//
// Precondition: the form's fields should have already been
@@ -124,9 +123,12 @@ func (p *Processor) Create(
return nil, gtserror.NewErrorInternalError(err)
}
- // There are side effects for creating a new account
+ // There are side effects for creating a new user+account
// (confirmation emails etc), perform these async.
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.ActivityCreate,
GTSModel: user,
diff --git a/internal/processing/user/delete.go b/internal/processing/user/delete.go
new file mode 100644
index 000000000..9783010ef
--- /dev/null
+++ b/internal/processing/user/delete.go
@@ -0,0 +1,48 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+package user
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/ap"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/messages"
+)
+
+// DeleteSelf is like Account.Delete, but specifically
+// for local user+accounts deleting themselves.
+//
+// Calling DeleteSelf results in a delete message being enqueued in the processor,
+// which causes side effects to occur: delete will be federated out to other instances,
+// and the above Delete function will be called afterwards from the processor, to clear
+// out the account's bits and bobs, and stubbify it.
+func (p *Processor) DeleteSelf(ctx context.Context, account *gtsmodel.Account) gtserror.WithCode {
+ // Process the delete side effects asynchronously.
+ 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.ActivityDelete,
+ Origin: account,
+ Target: account,
+ })
+ return nil
+}
diff --git a/internal/processing/user/email.go b/internal/processing/user/email.go
index 2b27c6c92..ea9dbb64c 100644
--- a/internal/processing/user/email.go
+++ b/internal/processing/user/email.go
@@ -23,11 +23,92 @@ import (
"fmt"
"time"
+ "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"
+ "github.com/superseriousbusiness/gotosocial/internal/validate"
+ "golang.org/x/crypto/bcrypt"
)
+// EmailChange processes an email address change request for the given user.
+func (p *Processor) EmailChange(
+ ctx context.Context,
+ user *gtsmodel.User,
+ password string,
+ newEmail string,
+) (*apimodel.User, gtserror.WithCode) {
+ // Ensure provided password is correct.
+ if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(password)); err != nil {
+ err := gtserror.Newf("%w", err)
+ return nil, gtserror.NewErrorUnauthorized(err, "password was incorrect")
+ }
+
+ // Ensure new email address is valid.
+ if err := validate.Email(newEmail); err != nil {
+ return nil, gtserror.NewErrorBadRequest(err, err.Error())
+ }
+
+ // Ensure new email address is different
+ // from current email address.
+ if newEmail == user.Email {
+ const help = "new email address cannot be the same as current email address"
+ err := gtserror.New(help)
+ return nil, gtserror.NewErrorBadRequest(err, help)
+ }
+
+ if newEmail == user.UnconfirmedEmail {
+ const help = "you already have an email change request pending for given email address"
+ err := gtserror.New(help)
+ return nil, gtserror.NewErrorBadRequest(err, help)
+ }
+
+ // Ensure this address isn't already used by another account.
+ emailAvailable, err := p.state.DB.IsEmailAvailable(ctx, newEmail)
+ if err != nil {
+ err := gtserror.Newf("db error checking email availability: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ if !emailAvailable {
+ const help = "new email address is already in use on this instance"
+ err := gtserror.New(help)
+ return nil, gtserror.NewErrorConflict(err, help)
+ }
+
+ // Set new email address on user.
+ user.UnconfirmedEmail = newEmail
+ if err := p.state.DB.UpdateUser(
+ ctx, user,
+ "unconfirmed_email",
+ ); err != nil {
+ err := gtserror.Newf("db error updating user: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // Ensure user populated (we need account).
+ if err := p.state.DB.PopulateUser(ctx, user); err != nil {
+ err := gtserror.Newf("db error populating user: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // Add email sending job to the queue.
+ 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.ActivityUpdate,
+ GTSModel: user,
+ Origin: user.Account,
+ Target: user.Account,
+ })
+
+ return p.converter.UserToAPIUser(ctx, user), nil
+}
+
// EmailGetUserForConfirmToken retrieves the user (with account) from
// the database for the given "confirm your email" token string.
func (p *Processor) EmailGetUserForConfirmToken(ctx context.Context, token string) (*gtsmodel.User, gtserror.WithCode) {
diff --git a/internal/processing/user/get.go b/internal/processing/user/get.go
new file mode 100644
index 000000000..9b19189a8
--- /dev/null
+++ b/internal/processing/user/get.go
@@ -0,0 +1,32 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+package user
+
+import (
+ "context"
+
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+// Get returns the API model of the given user.
+// Should only be served if user == the user doing the request.
+func (p *Processor) Get(ctx context.Context, user *gtsmodel.User) (*apimodel.User, gtserror.WithCode) {
+ return p.converter.UserToAPIUser(ctx, user), nil
+}
diff --git a/internal/processing/user/user.go b/internal/processing/user/user.go
index 2fbb9c888..cd8ab9900 100644
--- a/internal/processing/user/user.go
+++ b/internal/processing/user/user.go
@@ -19,18 +19,28 @@ package user
import (
"github.com/superseriousbusiness/gotosocial/internal/email"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/state"
+ "github.com/superseriousbusiness/gotosocial/internal/typeutils"
)
type Processor struct {
state *state.State
+ converter *typeutils.Converter
+ oauthServer oauth.Server
emailSender email.Sender
}
-// New returns a new user processor
-func New(state *state.State, emailSender email.Sender) Processor {
+// New returns a new user processor.
+func New(
+ state *state.State,
+ converter *typeutils.Converter,
+ oauthServer oauth.Server,
+ emailSender email.Sender,
+) Processor {
return Processor{
state: state,
+ converter: converter,
emailSender: emailSender,
}
}
diff --git a/internal/processing/user/user_test.go b/internal/processing/user/user_test.go
index 61e8f8b05..e473c5bb0 100644
--- a/internal/processing/user/user_test.go
+++ b/internal/processing/user/user_test.go
@@ -24,6 +24,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/processing/user"
"github.com/superseriousbusiness/gotosocial/internal/state"
+ "github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/testrig"
)
@@ -53,7 +54,7 @@ func (suite *UserStandardTestSuite) SetupTest() {
suite.emailSender = testrig.NewEmailSender("../../../web/template/", suite.sentEmails)
suite.testUsers = testrig.NewTestUsers()
- suite.user = user.New(&suite.state, suite.emailSender)
+ suite.user = user.New(&suite.state, typeutils.NewConverter(&suite.state), testrig.NewTestOauthServer(suite.db), suite.emailSender)
testrig.StandardDBSetup(suite.db, nil)
}
diff --git a/internal/processing/workers/fromclientapi.go b/internal/processing/workers/fromclientapi.go
index a9e33892f..89b8f546f 100644
--- a/internal/processing/workers/fromclientapi.go
+++ b/internal/processing/workers/fromclientapi.go
@@ -71,9 +71,9 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro
case ap.ActivityCreate:
switch cMsg.APObjectType {
- // CREATE PROFILE/ACCOUNT
- case ap.ObjectProfile, ap.ActorPerson:
- return p.clientAPI.CreateAccount(ctx, cMsg)
+ // CREATE USER (ie., new user+account sign-up)
+ case ap.ObjectProfile:
+ return p.clientAPI.CreateUser(ctx, cMsg)
// CREATE NOTE/STATUS
case ap.ObjectNote:
@@ -111,13 +111,17 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro
case ap.ObjectNote:
return p.clientAPI.UpdateStatus(ctx, cMsg)
- // UPDATE PROFILE/ACCOUNT
- case ap.ObjectProfile, ap.ActorPerson:
+ // UPDATE ACCOUNT (ie., bio, settings, etc)
+ case ap.ActorPerson:
return p.clientAPI.UpdateAccount(ctx, cMsg)
// UPDATE A FLAG/REPORT (mark as resolved/closed)
case ap.ActivityFlag:
return p.clientAPI.UpdateReport(ctx, cMsg)
+
+ // UPDATE USER (ie., email address)
+ case ap.ObjectProfile:
+ return p.clientAPI.UpdateUser(ctx, cMsg)
}
// ACCEPT SOMETHING
@@ -128,9 +132,9 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro
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)
+ // ACCEPT USER (ie., new user+account sign-up)
+ case ap.ObjectProfile:
+ return p.clientAPI.AcceptUser(ctx, cMsg)
}
// REJECT SOMETHING
@@ -141,9 +145,9 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro
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)
+ // REJECT USER (ie., new user+account sign-up)
+ case ap.ObjectProfile:
+ return p.clientAPI.RejectUser(ctx, cMsg)
}
// UNDO SOMETHING
@@ -175,17 +179,17 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro
case ap.ObjectNote:
return p.clientAPI.DeleteStatus(ctx, cMsg)
- // DELETE PROFILE/ACCOUNT
- case ap.ObjectProfile, ap.ActorPerson:
- return p.clientAPI.DeleteAccount(ctx, cMsg)
+ // DELETE REMOTE ACCOUNT or LOCAL USER+ACCOUNT
+ case ap.ActorPerson, ap.ObjectProfile:
+ return p.clientAPI.DeleteAccountOrUser(ctx, cMsg)
}
// FLAG/REPORT SOMETHING
case ap.ActivityFlag:
switch cMsg.APObjectType { //nolint:gocritic
- // FLAG/REPORT A PROFILE
- case ap.ObjectProfile:
+ // FLAG/REPORT ACCOUNT
+ case ap.ActorPerson:
return p.clientAPI.ReportAccount(ctx, cMsg)
}
@@ -193,8 +197,8 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro
case ap.ActivityMove:
switch cMsg.APObjectType { //nolint:gocritic
- // MOVE PROFILE/ACCOUNT
- case ap.ObjectProfile, ap.ActorPerson:
+ // MOVE ACCOUNT
+ case ap.ActorPerson:
return p.clientAPI.MoveAccount(ctx, cMsg)
}
}
@@ -202,7 +206,7 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro
return gtserror.Newf("unhandled: %s %s", cMsg.APActivityType, cMsg.APObjectType)
}
-func (p *clientAPI) CreateAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
+func (p *clientAPI) CreateUser(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)
@@ -219,7 +223,7 @@ func (p *clientAPI) CreateAccount(ctx context.Context, cMsg *messages.FromClient
}
// Send "please confirm your address" email to the new user.
- if err := p.surface.emailUserPleaseConfirm(ctx, newUser); err != nil {
+ if err := p.surface.emailUserPleaseConfirm(ctx, newUser, true); err != nil {
log.Errorf(ctx, "error emailing confirm: %v", err)
}
@@ -479,6 +483,22 @@ func (p *clientAPI) UpdateReport(ctx context.Context, cMsg *messages.FromClientA
return nil
}
+func (p *clientAPI) UpdateUser(ctx context.Context, cMsg *messages.FromClientAPI) error {
+ user, ok := cMsg.GTSModel.(*gtsmodel.User)
+ if !ok {
+ return gtserror.Newf("cannot cast %T -> *gtsmodel.User", cMsg.GTSModel)
+ }
+
+ // The only possible "UpdateUser" action is to update the
+ // user's email address, so we can safely assume by this
+ // point that a new unconfirmed email address has been set.
+ if err := p.surface.emailUserPleaseConfirm(ctx, user, false); err != nil {
+ log.Errorf(ctx, "error emailing report closed: %v", err)
+ }
+
+ return nil
+}
+
func (p *clientAPI) AcceptFollow(ctx context.Context, cMsg *messages.FromClientAPI) error {
follow, ok := cMsg.GTSModel.(*gtsmodel.Follow)
if !ok {
@@ -669,7 +689,7 @@ func (p *clientAPI) DeleteStatus(ctx context.Context, cMsg *messages.FromClientA
return nil
}
-func (p *clientAPI) DeleteAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
+func (p *clientAPI) DeleteAccountOrUser(ctx context.Context, cMsg *messages.FromClientAPI) error {
// The originID of the delete, one of:
// - ID of a domain block, for which
// this account delete is a side effect.
@@ -768,7 +788,7 @@ func (p *clientAPI) MoveAccount(ctx context.Context, cMsg *messages.FromClientAP
return nil
}
-func (p *clientAPI) AcceptAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
+func (p *clientAPI) AcceptUser(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)
@@ -791,7 +811,7 @@ func (p *clientAPI) AcceptAccount(ctx context.Context, cMsg *messages.FromClient
return nil
}
-func (p *clientAPI) RejectAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
+func (p *clientAPI) RejectUser(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)
diff --git a/internal/processing/workers/fromfediapi.go b/internal/processing/workers/fromfediapi.go
index 49756a47a..ac4003f6a 100644
--- a/internal/processing/workers/fromfediapi.go
+++ b/internal/processing/workers/fromfediapi.go
@@ -115,8 +115,8 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ObjectNote:
return p.fediAPI.UpdateStatus(ctx, fMsg)
- // UPDATE PROFILE/ACCOUNT
- case ap.ObjectProfile:
+ // UPDATE ACCOUNT
+ case ap.ActorPerson:
return p.fediAPI.UpdateAccount(ctx, fMsg)
}
@@ -137,17 +137,17 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
case ap.ObjectNote:
return p.fediAPI.DeleteStatus(ctx, fMsg)
- // DELETE PROFILE/ACCOUNT
- case ap.ObjectProfile:
+ // DELETE ACCOUNT
+ case ap.ActorPerson:
return p.fediAPI.DeleteAccount(ctx, fMsg)
}
// MOVE SOMETHING
case ap.ActivityMove:
- // MOVE PROFILE/ACCOUNT
+ // MOVE ACCOUNT
// fromfediapi_move.go.
- if fMsg.APObjectType == ap.ObjectProfile {
+ if fMsg.APObjectType == ap.ActorPerson {
return p.fediAPI.MoveAccount(ctx, fMsg)
}
}
diff --git a/internal/processing/workers/fromfediapi_test.go b/internal/processing/workers/fromfediapi_test.go
index 8429fe17c..e69e2c7a8 100644
--- a/internal/processing/workers/fromfediapi_test.go
+++ b/internal/processing/workers/fromfediapi_test.go
@@ -337,7 +337,7 @@ func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
// now they are mufos!
err = testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
- APObjectType: ap.ObjectProfile,
+ APObjectType: ap.ActorPerson,
APActivityType: ap.ActivityDelete,
GTSModel: deletedAccount,
Receiving: receivingAccount,
@@ -613,7 +613,7 @@ func (suite *FromFediAPITestSuite) TestMoveAccount() {
// Process the Move.
err := testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
- APObjectType: ap.ObjectProfile,
+ APObjectType: ap.ActorPerson,
APActivityType: ap.ActivityMove,
GTSModel: &gtsmodel.Move{
OriginURI: requestingAcct.URI,
diff --git a/internal/processing/workers/surfaceemail.go b/internal/processing/workers/surfaceemail.go
index 5f8ae1823..d0a40e6ba 100644
--- a/internal/processing/workers/surfaceemail.go
+++ b/internal/processing/workers/surfaceemail.go
@@ -74,7 +74,10 @@ func (s *Surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Re
// emailUserPleaseConfirm emails the given user
// to ask them to confirm their email address.
-func (s *Surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.User) error {
+//
+// If newSignup is true, template will be geared
+// towards someone who just created an account.
+func (s *Surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.User, newSignup bool) error {
if user.UnconfirmedEmail == "" ||
user.UnconfirmedEmail == user.Email {
// User has already confirmed this
@@ -104,6 +107,7 @@ func (s *Surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
InstanceURL: instance.URI,
InstanceName: instance.Title,
ConfirmLink: confirmLink,
+ NewSignup: newSignup,
},
); err != nil {
return err