mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-13 21:07:28 -06:00
[feature] Allow admins to send test emails (#1620)
* [feature] Allow admins to send test emails * implement unwrap on new error type * add + use gtserror types * GoToSocial Email Test -> GoToSocial Test Email * add + use getInstance db call * removed unused "unknown" error type
This commit is contained in:
parent
d5529d6c9f
commit
196cd88b1c
17 changed files with 460 additions and 83 deletions
|
|
@ -25,60 +25,37 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// BasePath is the base API path for this module, excluding the api prefix
|
||||
BasePath = "/v1/admin"
|
||||
// EmojiPath is used for posting/deleting custom emojis.
|
||||
EmojiPath = BasePath + "/custom_emojis"
|
||||
// EmojiPathWithID is used for interacting with a single emoji.
|
||||
EmojiPathWithID = EmojiPath + "/:" + IDKey
|
||||
// EmojiCategoriesPath is used for interacting with emoji categories.
|
||||
EmojiCategoriesPath = EmojiPath + "/categories"
|
||||
// DomainBlocksPath is used for posting domain blocks.
|
||||
DomainBlocksPath = BasePath + "/domain_blocks"
|
||||
// DomainBlocksPathWithID is used for interacting with a single domain block.
|
||||
BasePath = "/v1/admin"
|
||||
EmojiPath = BasePath + "/custom_emojis"
|
||||
EmojiPathWithID = EmojiPath + "/:" + IDKey
|
||||
EmojiCategoriesPath = EmojiPath + "/categories"
|
||||
DomainBlocksPath = BasePath + "/domain_blocks"
|
||||
DomainBlocksPathWithID = DomainBlocksPath + "/:" + IDKey
|
||||
// AccountsPath is used for listing + acting on accounts.
|
||||
AccountsPath = BasePath + "/accounts"
|
||||
// AccountsPathWithID is used for interacting with a single account.
|
||||
AccountsPathWithID = AccountsPath + "/:" + IDKey
|
||||
// AccountsActionPath is used for taking action on a single account.
|
||||
AccountsActionPath = AccountsPathWithID + "/action"
|
||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||
MediaRefetchPath = BasePath + "/media_refetch"
|
||||
// ReportsPath is for serving admin view of user reports.
|
||||
ReportsPath = BasePath + "/reports"
|
||||
// ReportsPathWithID is for viewing/acting on one report.
|
||||
ReportsPathWithID = ReportsPath + "/:" + IDKey
|
||||
// ReportsResolvePath is for marking one report as resolved.
|
||||
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
||||
AccountsPath = BasePath + "/accounts"
|
||||
AccountsPathWithID = AccountsPath + "/:" + IDKey
|
||||
AccountsActionPath = AccountsPathWithID + "/action"
|
||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||
MediaRefetchPath = BasePath + "/media_refetch"
|
||||
ReportsPath = BasePath + "/reports"
|
||||
ReportsPathWithID = ReportsPath + "/:" + IDKey
|
||||
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
||||
EmailPath = BasePath + "/email"
|
||||
EmailTestPath = EmailPath + "/test"
|
||||
|
||||
// ExportQueryKey is for requesting a public export of some data.
|
||||
ExportQueryKey = "export"
|
||||
// ImportQueryKey is for submitting an import of some data.
|
||||
ImportQueryKey = "import"
|
||||
// IDKey specifies the ID of a single item being interacted with.
|
||||
IDKey = "id"
|
||||
// FilterKey is for applying filters to admin views of accounts, emojis, etc.
|
||||
FilterQueryKey = "filter"
|
||||
// MaxShortcodeDomainKey is the url query for returning emoji results lower (alphabetically)
|
||||
// than the given `[shortcode]@[domain]` parameter.
|
||||
ExportQueryKey = "export"
|
||||
ImportQueryKey = "import"
|
||||
IDKey = "id"
|
||||
FilterQueryKey = "filter"
|
||||
MaxShortcodeDomainKey = "max_shortcode_domain"
|
||||
// MaxShortcodeDomainKey is the url query for returning emoji results higher (alphabetically)
|
||||
// than the given `[shortcode]@[domain]` parameter.
|
||||
MinShortcodeDomainKey = "min_shortcode_domain"
|
||||
// LimitKey is for specifying maximum number of results to return.
|
||||
LimitKey = "limit"
|
||||
// DomainQueryKey is for specifying a domain during admin actions.
|
||||
DomainQueryKey = "domain"
|
||||
// ResolvedKey is for filtering reports by their resolved status
|
||||
ResolvedKey = "resolved"
|
||||
// AccountIDKey is for selecting account in API paths.
|
||||
AccountIDKey = "account_id"
|
||||
// TargetAccountIDKey is for selecting target account in API paths.
|
||||
TargetAccountIDKey = "target_account_id"
|
||||
MaxIDKey = "max_id"
|
||||
SinceIDKey = "since_id"
|
||||
MinIDKey = "min_id"
|
||||
LimitKey = "limit"
|
||||
DomainQueryKey = "domain"
|
||||
ResolvedKey = "resolved"
|
||||
AccountIDKey = "account_id"
|
||||
TargetAccountIDKey = "target_account_id"
|
||||
MaxIDKey = "max_id"
|
||||
SinceIDKey = "since_id"
|
||||
MinIDKey = "min_id"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
|
|
@ -117,4 +94,7 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
|||
attachHandler(http.MethodGet, ReportsPath, m.ReportsGETHandler)
|
||||
attachHandler(http.MethodGet, ReportsPathWithID, m.ReportGETHandler)
|
||||
attachHandler(http.MethodPost, ReportsResolvePath, m.ReportResolvePOSTHandler)
|
||||
|
||||
// email stuff
|
||||
attachHandler(http.MethodPost, EmailTestPath, m.EmailTestPOSTHandler)
|
||||
}
|
||||
|
|
|
|||
120
internal/api/client/admin/emailtest.go
Normal file
120
internal/api/client/admin/emailtest.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// 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 admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// EmailTestPostHandler swagger:operation POST /api/v1/admin/email/test testEmailSend
|
||||
//
|
||||
// Send a generic test email to a specified email address.
|
||||
//
|
||||
// This can be used to validate an instance's SMTP configuration, and to debug any potential issues.
|
||||
//
|
||||
// If an error is returned by the SMTP connection, this handler will return code 422 to indicate that
|
||||
// the request could not be processed, and the SMTP error will be returned to the caller.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// consumes:
|
||||
// - multipart/form-data
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: email
|
||||
// in: formData
|
||||
// description: The email address that the test email should be sent to.
|
||||
// type: string
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '202':
|
||||
// description: Test email was sent.
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '422':
|
||||
// description: >-
|
||||
// An smtp occurred while the email attempt was in progress.
|
||||
// Check the returned json for more information. The smtp error
|
||||
// will be included, to help you debug communication with the
|
||||
// smtp server.
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) EmailTestPOSTHandler(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 !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(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.AdminSendTestEmailRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
email, err := mail.ParseAddress(form.Email)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
errWithCode := m.processor.Admin().EmailTest(c.Request.Context(), authed.Account, email.Address)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusAccepted, gin.H{"status": "test email sent"})
|
||||
}
|
||||
|
|
@ -183,3 +183,9 @@ type MediaCleanupRequest struct {
|
|||
// If value is not specified, the value of media-remote-cache-days in the server config will be used.
|
||||
RemoteCacheDays *int `form:"remote_cache_days" json:"remote_cache_days" xml:"remote_cache_days"`
|
||||
}
|
||||
|
||||
// AdminSendTestEmailRequest models a test email send request (woah).
|
||||
type AdminSendTestEmailRequest struct {
|
||||
// Email address to send the test email to.
|
||||
Email string `form:"email" json:"email" xml:"email"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,20 @@ func (i *instanceDB) CountInstanceDomains(ctx context.Context, domain string) (i
|
|||
return count, nil
|
||||
}
|
||||
|
||||
func (i *instanceDB) GetInstance(ctx context.Context, domain string) (*gtsmodel.Instance, db.Error) {
|
||||
instance := >smodel.Instance{}
|
||||
|
||||
if err := i.conn.
|
||||
NewSelect().
|
||||
Model(instance).
|
||||
Where("? = ?", bun.Ident("instance.domain"), domain).
|
||||
Scan(ctx); err != nil {
|
||||
return nil, i.conn.ProcessError(err)
|
||||
}
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
func (i *instanceDB) GetInstancePeers(ctx context.Context, includeSuspended bool) ([]*gtsmodel.Instance, db.Error) {
|
||||
instances := []*gtsmodel.Instance{}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
)
|
||||
|
||||
type InstanceTestSuite struct {
|
||||
|
|
@ -59,6 +60,18 @@ func (suite *InstanceTestSuite) TestCountInstanceDomains() {
|
|||
suite.Equal(2, count)
|
||||
}
|
||||
|
||||
func (suite *InstanceTestSuite) TestGetInstanceOK() {
|
||||
instance, err := suite.db.GetInstance(context.Background(), "localhost:8080")
|
||||
suite.NoError(err)
|
||||
suite.NotNil(instance)
|
||||
}
|
||||
|
||||
func (suite *InstanceTestSuite) TestGetInstanceNonexistent() {
|
||||
instance, err := suite.db.GetInstance(context.Background(), "doesnt.exist.com")
|
||||
suite.ErrorIs(err, db.ErrNoEntries)
|
||||
suite.Nil(instance)
|
||||
}
|
||||
|
||||
func (suite *InstanceTestSuite) TestGetInstancePeers() {
|
||||
peers, err := suite.db.GetInstancePeers(context.Background(), false)
|
||||
suite.NoError(err)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ type Instance interface {
|
|||
// CountInstanceDomains returns the number of known instances known that the given domain federates with.
|
||||
CountInstanceDomains(ctx context.Context, domain string) (int, Error)
|
||||
|
||||
// GetInstance returns the instance entry for the given domain, if it exists.
|
||||
GetInstance(ctx context.Context, domain string) (*gtsmodel.Instance, Error)
|
||||
|
||||
// GetInstanceAccounts returns a slice of accounts from the given instance, arranged by ID.
|
||||
GetInstanceAccounts(ctx context.Context, domain string, maxID string, limit int) ([]*gtsmodel.Account, Error)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ import (
|
|||
"bytes"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -30,21 +29,6 @@ const (
|
|||
confirmSubject = "GoToSocial Email Confirmation"
|
||||
)
|
||||
|
||||
func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil {
|
||||
return err
|
||||
}
|
||||
confirmBody := buf.String()
|
||||
|
||||
msg, err := assembleMessage(confirmSubject, confirmBody, toAddress, s.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace(nil, s.hostAddress+"\n"+config.GetSMTPUsername()+":password"+"\n"+s.from+"\n"+toAddress+"\n\n"+string(msg)+"\n")
|
||||
return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg)
|
||||
}
|
||||
|
||||
// ConfirmData represents data passed into the confirm email address template.
|
||||
type ConfirmData struct {
|
||||
// Username to be addressed.
|
||||
|
|
@ -57,3 +41,22 @@ type ConfirmData struct {
|
|||
// Should be a full link with protocol eg., https://example.org/confirm_email?token=some-long-token
|
||||
ConfirmLink string
|
||||
}
|
||||
|
||||
func (s *sender) SendConfirmEmail(toAddress string, data ConfirmData) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := s.template.ExecuteTemplate(buf, confirmTemplate, data); err != nil {
|
||||
return err
|
||||
}
|
||||
confirmBody := buf.String()
|
||||
|
||||
msg, err := assembleMessage(confirmSubject, confirmBody, toAddress, s.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg); err != nil {
|
||||
return gtserror.SetType(err, gtserror.TypeSMTP)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,3 +88,24 @@ func (s *noopSender) SendResetEmail(toAddress string, data ResetData) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *noopSender) SendTestEmail(toAddress string, data TestData) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := s.template.ExecuteTemplate(buf, testTemplate, data); err != nil {
|
||||
return err
|
||||
}
|
||||
testBody := buf.String()
|
||||
|
||||
msg, err := assembleMessage(testSubject, testBody, toAddress, "test@example.org")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Tracef(nil, "NOT SENDING test email to %s with contents: %s", toAddress, msg)
|
||||
|
||||
if s.sendCallback != nil {
|
||||
s.sendCallback(toAddress, string(msg))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ package email
|
|||
import (
|
||||
"bytes"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -27,20 +29,6 @@ const (
|
|||
resetSubject = "GoToSocial Password Reset"
|
||||
)
|
||||
|
||||
func (s *sender) SendResetEmail(toAddress string, data ResetData) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil {
|
||||
return err
|
||||
}
|
||||
resetBody := buf.String()
|
||||
|
||||
msg, err := assembleMessage(resetSubject, resetBody, toAddress, s.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg)
|
||||
}
|
||||
|
||||
// ResetData represents data passed into the reset email address template.
|
||||
type ResetData struct {
|
||||
// Username to be addressed.
|
||||
|
|
@ -53,3 +41,22 @@ type ResetData struct {
|
|||
// Should be a full link with protocol eg., https://example.org/reset_password?token=some-reset-password-token
|
||||
ResetLink string
|
||||
}
|
||||
|
||||
func (s *sender) SendResetEmail(toAddress string, data ResetData) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := s.template.ExecuteTemplate(buf, resetTemplate, data); err != nil {
|
||||
return err
|
||||
}
|
||||
resetBody := buf.String()
|
||||
|
||||
msg, err := assembleMessage(resetSubject, resetBody, toAddress, s.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg); err != nil {
|
||||
return gtserror.SetType(err, gtserror.TypeSMTP)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ type Sender interface {
|
|||
|
||||
// SendResetEmail sends a 'reset your password' style email to the given toAddress, with the given data.
|
||||
SendResetEmail(toAddress string, data ResetData) error
|
||||
|
||||
// SendTestEmail sends a 'testing email sending' style email to the given toAddress, with the given data.
|
||||
SendTestEmail(toAddress string, data TestData) error
|
||||
}
|
||||
|
||||
// NewSender returns a new email Sender interface with the given configuration, or an error if something goes wrong.
|
||||
|
|
|
|||
58
internal/email/test.go
Normal file
58
internal/email/test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// 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 email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
const (
|
||||
testTemplate = "email_test_text.tmpl"
|
||||
testSubject = "GoToSocial Test Email"
|
||||
)
|
||||
|
||||
type TestData struct {
|
||||
// Username of admin user who sent the test.
|
||||
SendingUsername string
|
||||
// URL of the instance to present to the receiver.
|
||||
InstanceURL string
|
||||
// Name of the instance to present to the receiver.
|
||||
InstanceName string
|
||||
}
|
||||
|
||||
func (s *sender) SendTestEmail(toAddress string, data TestData) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := s.template.ExecuteTemplate(buf, testTemplate, data); err != nil {
|
||||
return err
|
||||
}
|
||||
testBody := buf.String()
|
||||
|
||||
msg, err := assembleMessage(testSubject, testBody, toAddress, s.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := smtp.SendMail(s.hostAddress, s.auth, s.from, []string{toAddress}, msg); err != nil {
|
||||
return gtserror.SetType(err, gtserror.TypeSMTP)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -24,11 +24,18 @@ import (
|
|||
// package private error key type.
|
||||
type errkey int
|
||||
|
||||
// ErrorType denotes the type of an error, if set.
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
// error value keys.
|
||||
_ errkey = iota
|
||||
statusCodeKey
|
||||
notFoundKey
|
||||
errorTypeKey
|
||||
|
||||
// error types
|
||||
TypeSMTP ErrorType = "smtp" // smtp (mail) error
|
||||
)
|
||||
|
||||
// StatusCode checks error for a stored status code value. For example
|
||||
|
|
@ -57,3 +64,17 @@ func NotFound(err error) bool {
|
|||
func SetNotFound(err error) error {
|
||||
return errors.WithValue(err, notFoundKey, struct{}{})
|
||||
}
|
||||
|
||||
// Type checks error for a stored "type" value. For example
|
||||
// an error from sending an email may set a value of "smtp"
|
||||
// to indicate this was an SMTP error.
|
||||
func Type(err error) ErrorType {
|
||||
s, _ := errors.Value(err, errorTypeKey).(ErrorType)
|
||||
return s
|
||||
}
|
||||
|
||||
// SetType will wrap the given error to store a "type" value,
|
||||
// returning wrapped error. See Type() for example use-cases.
|
||||
func SetType(err error, errType ErrorType) error {
|
||||
return errors.WithValue(err, errorTypeKey, errType)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
|
|
@ -29,14 +30,16 @@ type Processor struct {
|
|||
tc typeutils.TypeConverter
|
||||
mediaManager media.Manager
|
||||
transportController transport.Controller
|
||||
emailSender email.Sender
|
||||
}
|
||||
|
||||
// New returns a new admin processor.
|
||||
func New(state *state.State, tc typeutils.TypeConverter, mediaManager media.Manager, transportController transport.Controller) Processor {
|
||||
func New(state *state.State, tc typeutils.TypeConverter, mediaManager media.Manager, transportController transport.Controller, emailSender email.Sender) Processor {
|
||||
return Processor{
|
||||
state: state,
|
||||
tc: tc,
|
||||
mediaManager: mediaManager,
|
||||
transportController: transportController,
|
||||
emailSender: emailSender,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
internal/processing/admin/email.go
Normal file
61
internal/processing/admin/email.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// 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 admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// EmailTest sends a generic test email to the given toAddress (which
|
||||
// should be a valid email address). To help callers differentiate between
|
||||
// proper errors and the smtp errors they're likely fishing for, will return
|
||||
// 422 + help text on an SMTP error, or error 500 otherwise.
|
||||
func (p *Processor) EmailTest(ctx context.Context, account *gtsmodel.Account, toAddress string) gtserror.WithCode {
|
||||
// Pull our instance entry from the database,
|
||||
// so we can greet the email recipient nicely.
|
||||
instance, err := p.state.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("SendConfirmEmail: error getting instance: %s", err)
|
||||
return gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
testData := email.TestData{
|
||||
SendingUsername: account.Username,
|
||||
InstanceURL: instance.URI,
|
||||
InstanceName: instance.Title,
|
||||
}
|
||||
|
||||
if err := p.emailSender.SendTestEmail(toAddress, testData); err != nil {
|
||||
if errorType := gtserror.Type(err); errorType == gtserror.TypeSMTP {
|
||||
// An error occurred during the SMTP part.
|
||||
// We should indicate this to the caller, as
|
||||
// it will likely help them debug the issue.
|
||||
return gtserror.NewErrorUnprocessableEntity(err, err.Error())
|
||||
}
|
||||
// An actual error has occurred.
|
||||
return gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ func NewProcessor(
|
|||
|
||||
// sub processors
|
||||
processor.account = account.New(state, tc, mediaManager, oauthServer, federator, parseMentionFunc)
|
||||
processor.admin = admin.New(state, tc, mediaManager, federator.TransportController())
|
||||
processor.admin = admin.New(state, tc, mediaManager, federator.TransportController(), emailSender)
|
||||
processor.fedi = fedi.New(state, tc, federator)
|
||||
processor.media = media.New(state, tc, mediaManager, federator.TransportController())
|
||||
processor.report = report.New(state, tc)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue