Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Self-serve email change for users #2957

Merged
merged 8 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions docs/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2709,6 +2709,77 @@ definitions:
type: object
x-go-name: Theme
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
user:
properties:
admin:
description: User is an admin.
example: false
type: boolean
x-go-name: Admin
approved:
description: User was approved by an admin.
example: true
type: boolean
x-go-name: Approved
confirmation_sent_at:
description: Time when the last "please confirm your email address" email was sent, if at all. (ISO 8601 Datetime)
example: "2021-07-30T09:20:25+00:00"
type: string
x-go-name: ConfirmationSentAt
confirmed_at:
description: Time at which the email given in the `email` field was confirmed, if at all. (ISO 8601 Datetime)
example: "2021-07-30T09:20:25+00:00"
type: string
x-go-name: ConfirmedAt
created_at:
description: Time this user was created. (ISO 8601 Datetime)
example: "2021-07-30T09:20:25+00:00"
type: string
x-go-name: CreatedAt
disabled:
description: User's account is disabled.
example: false
type: boolean
x-go-name: Disabled
email:
description: Confirmed email address of this user, if set.
example: someone@example.org
type: string
x-go-name: Email
id:
description: Database ID of this user.
example: 01FBVD42CQ3ZEEVMW180SBX03B
type: string
x-go-name: ID
last_emailed_at:
description: Time at which this user was last emailed, if at all. (ISO 8601 Datetime)
example: "2021-07-30T09:20:25+00:00"
type: string
x-go-name: LastEmailedAt
moderator:
description: User is a moderator.
example: false
type: boolean
x-go-name: Moderator
reason:
description: Reason for sign-up, if provided.
example: Please! Pretty please!
type: string
x-go-name: Reason
reset_password_sent_at:
description: Time when the last "please reset your password" email was sent, if at all. (ISO 8601 Datetime)
example: "2021-07-30T09:20:25+00:00"
type: string
x-go-name: ResetPasswordSentAt
unconfirmed_email:
description: Unconfirmed email address of this user, if set.
example: someone.else@somewhere.else.example.org
type: string
x-go-name: UnconfirmedEmail
title: User models fields relevant to one user.
type: object
x-go-name: User
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
wellKnownResponse:
description: See https://webfinger.net/
properties:
Expand Down Expand Up @@ -8632,6 +8703,77 @@ paths:
summary: See public statuses that use the given hashtag (case insensitive).
tags:
- timelines
/api/v1/user:
get:
operationId: getUser
produces:
- application/json
responses:
"200":
description: The requested user.
schema:
$ref: '#/definitions/user'
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"406":
description: not acceptable
"500":
description: internal error
security:
- OAuth2 Bearer:
- read:user
summary: Get your own user model.
tags:
- user
/api/v1/user/email_change:
post:
consumes:
- application/json
- application/xml
- application/x-www-form-urlencoded
operationId: userEmailChange
parameters:
- description: User's current password, for verification.
in: formData
name: password
required: true
type: string
x-go-name: Password
- description: Desired new email address.
in: formData
name: new_email
required: true
type: string
x-go-name: NewEmail
produces:
- application/json
responses:
"202":
description: 'Accepted: email change is processing; check your inbox to confirm new address.'
schema:
$ref: '#/definitions/user'
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"406":
description: not acceptable
"409":
description: 'Conflict: desired email address already in use'
"500":
description: internal error
security:
- OAuth2 Bearer:
- write:user
summary: Request changing the email address of authenticated user.
tags:
- user
/api/v1/user/password_change:
post:
consumes:
Expand Down
Binary file removed docs/assets/user-settings-post-settings.png
Binary file not shown.
Binary file added docs/assets/user-settings-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 13 additions & 5 deletions docs/user_guide/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,13 @@ See the [Custom CSS](./custom_css.md) page for some tips on writing custom CSS f
!!! tip
Any custom CSS you add in this box will be applied *after* your selected theme, so you can pick a preset theme that you like and then make your own tweaks!

## Post Settings
## Settings

![Screenshot of the user settings section, providing drop-down menu's to select default post settings, and form fields to change your password](../assets/user-settings-post-settings.png)
![Screenshot of the settings section](../assets/user-settings-settings.png)

In the 'Settings' section, you can set various defaults for new posts.
In the 'Settings' section, you can set various defaults for new posts, and change your password / email address.

### Post Settings

The default post language setting allows you to indicate to other fediverse users which language your posts are usually written in. This is helpful for fediverse users who speak (for example) Korean, and would prefer to filter out posts written in other languages.

Expand All @@ -151,12 +153,18 @@ The markdown setting indicates that your posts should be parsed as Markdown, whi

When you are finished updating your post settings, remember to click the `Save post settings` button at the bottom of the section to save your changes.

## Password Change
### Password Change

You can use the Password Change section of the User Settings Panel to set a new password for your account.
You can use the Password Change section of the panel to set a new password for your account. For security reasons, you must provide your current password to validate the change.

For more information on the way GoToSocial manages passwords, please see the [Password management document](./password_management.md).

### Email Change

You can use the Email Change section of the panel to change the email address for your account. For security reasons, you must provide your current password to validate the change.

Once a new email address has been entered, and you have clicked "Change email address", you must open the inbox of the new email address and confirm your address via the link provided. Once you've done that, your email address change will be confirmed, and you should use the new email address to log in.

## Migration

In the migration section you can manage settings related to aliasing and/or migrating your account to another account.
Expand Down
2 changes: 1 addition & 1 deletion internal/api/activitypub/users/userget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() {
userModule := users.New(suite.processor)
targetAccount := suite.testAccounts["local_account_1"]

suite.processor.Account().DeleteSelf(context.Background(), suite.testAccounts["local_account_1"])
suite.processor.User().DeleteSelf(context.Background(), suite.testAccounts["local_account_1"])

// wait for the account delete to be processed
if !testrig.WaitFor(func() bool {
Expand Down
6 changes: 3 additions & 3 deletions internal/api/client/accounts/accountcreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
}
form.IP = signUpIP

// Create the new account + user.
// Create the new user+account.
ctx := c.Request.Context()
user, errWithCode := m.processor.Account().Create(
user, errWithCode := m.processor.User().Create(
ctx,
authed.Application,
form,
Expand All @@ -118,7 +118,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
}

// Get a token for the new user.
ti, errWithCode := m.processor.Account().TokenForNewUser(
ti, errWithCode := m.processor.User().TokenForNewUser(
ctx,
authed.Token,
authed.Application,
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/accounts/accountdelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (m *Module) AccountDeletePOSTHandler(c *gin.Context) {
return
}

if errWithCode := m.processor.Account().DeleteSelf(c.Request.Context(), authed.Account); errWithCode != nil {
if errWithCode := m.processor.User().DeleteSelf(c.Request.Context(), authed.Account); errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/admin/accountapprove.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (m *Module) AccountApprovePOSTHandler(c *gin.Context) {
return
}

account, errWithCode := m.processor.Admin().AccountApprove(
account, errWithCode := m.processor.Admin().SignupApprove(
c.Request.Context(),
authed.Account,
targetAcctID,
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/admin/accountreject.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (m *Module) AccountRejectPOSTHandler(c *gin.Context) {
return
}

account, errWithCode := m.processor.Admin().AccountReject(
account, errWithCode := m.processor.Admin().SignupReject(
c.Request.Context(),
authed.Account,
targetAcctID,
Expand Down
104 changes: 104 additions & 0 deletions internal/api/client/user/emailchange.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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 (
"errors"
"net/http"

"github.com/gin-gonic/gin"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)

// EmailChangePOSTHandler swagger:operation POST /api/v1/user/email_change userEmailChange
//
// Request changing the email address of authenticated user.
//
// ---
// tags:
// - user
//
// consumes:
// - application/json
// - application/xml
// - application/x-www-form-urlencoded
//
// produces:
// - application/json
//
// security:
// - OAuth2 Bearer:
// - write:user
//
// responses:
// '202':
// description: "Accepted: email change is processing; check your inbox to confirm new address."
// schema:
// "$ref": "#/definitions/user"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '403':
// description: forbidden
// '406':
// description: not acceptable
// '409':
// description: "Conflict: desired email address already in use"
// '500':
// description: internal error
func (m *Module) EmailChangePOSTHandler(c *gin.Context) {
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
return
}

if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return
}

form := &apimodel.EmailChangeRequest{}
if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
}

if form.Password == "" {
err := errors.New("email change request missing field password")
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
}

user, errWithCode := m.processor.User().EmailChange(
c.Request.Context(),
authed.User,
form.Password,
form.NewEmail,
)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}

apiutil.JSON(c, http.StatusAccepted, user)
}
Loading