mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-02 22:38:08 -06:00
[feature] Public list of suspended domains (#1362)
* basic rendered domain blocklist (unauthenticated!) * style basic domain block list * better formatting for domain blocklist * add opt-in config option for showing suspended domains * format/linter * re-use InstancePeersGet for web-accessible domain blocklist * reword explanation, border styling * always attach blocklist handler, update error message * domain blocklist error message grammar
This commit is contained in:
parent
993aae5e48
commit
17eecfb6d9
17 changed files with 265 additions and 66 deletions
|
|
@ -24,6 +24,7 @@ import (
|
|||
"strings"
|
||||
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
|
||||
|
|
@ -105,6 +106,8 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
var isUnauthenticated = authed.Account == nil || authed.User == nil
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||
return
|
||||
|
|
@ -136,7 +139,19 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) {
|
|||
flat = true
|
||||
}
|
||||
|
||||
data, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), authed, includeSuspended, includeOpen, flat)
|
||||
if includeOpen && !config.GetInstanceExposePeers() && isUnauthenticated {
|
||||
err := fmt.Errorf("peers open query requires an authenticated account/user")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||
return
|
||||
}
|
||||
|
||||
if includeSuspended && !config.GetInstanceExposeSuspended() && isUnauthenticated {
|
||||
err := fmt.Errorf("peers suspended query requires an authenticated account/user")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||
return
|
||||
}
|
||||
|
||||
data, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), includeSuspended, includeOpen, flat)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ type Configuration struct {
|
|||
|
||||
InstanceExposePeers bool `name:"instance-expose-peers" usage:"Allow unauthenticated users to query /api/v1/instance/peers?filter=open"`
|
||||
InstanceExposeSuspended bool `name:"instance-expose-suspended" usage:"Expose suspended instances via web UI, and allow unauthenticated users to query /api/v1/instance/peers?filter=suspended"`
|
||||
InstanceExposeSuspendedWeb bool `name:"instance-expose-suspended-web" usage:"Expose list of suspended instances as webpage on /about/suspended"`
|
||||
InstanceExposePublicTimeline bool `name:"instance-expose-public-timeline" usage:"Allow unauthenticated users to query /api/v1/timelines/public"`
|
||||
InstanceDeliverToSharedInboxes bool `name:"instance-deliver-to-shared-inboxes" usage:"Deliver federated messages to shared inboxes, if they're available."`
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ var Defaults = Configuration{
|
|||
|
||||
InstanceExposePeers: false,
|
||||
InstanceExposeSuspended: false,
|
||||
InstanceExposeSuspendedWeb: false,
|
||||
InstanceDeliverToSharedInboxes: true,
|
||||
|
||||
AccountsRegistrationOpen: true,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ func (s *ConfigState) AddServerFlags(cmd *cobra.Command) {
|
|||
// Instance
|
||||
cmd.Flags().Bool(InstanceExposePeersFlag(), cfg.InstanceExposePeers, fieldtag("InstanceExposePeers", "usage"))
|
||||
cmd.Flags().Bool(InstanceExposeSuspendedFlag(), cfg.InstanceExposeSuspended, fieldtag("InstanceExposeSuspended", "usage"))
|
||||
cmd.Flags().Bool(InstanceExposeSuspendedWebFlag(), cfg.InstanceExposeSuspendedWeb, fieldtag("InstanceExposeSuspendedWeb", "usage"))
|
||||
cmd.Flags().Bool(InstanceDeliverToSharedInboxesFlag(), cfg.InstanceDeliverToSharedInboxes, fieldtag("InstanceDeliverToSharedInboxes", "usage"))
|
||||
|
||||
// Accounts
|
||||
|
|
|
|||
|
|
@ -724,6 +724,31 @@ func GetInstanceExposeSuspended() bool { return global.GetInstanceExposeSuspende
|
|||
// SetInstanceExposeSuspended safely sets the value for global configuration 'InstanceExposeSuspended' field
|
||||
func SetInstanceExposeSuspended(v bool) { global.SetInstanceExposeSuspended(v) }
|
||||
|
||||
// GetInstanceExposeSuspendedWeb safely fetches the Configuration value for state's 'InstanceExposeSuspendedWeb' field
|
||||
func (st *ConfigState) GetInstanceExposeSuspendedWeb() (v bool) {
|
||||
st.mutex.Lock()
|
||||
v = st.config.InstanceExposeSuspendedWeb
|
||||
st.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetInstanceExposeSuspendedWeb safely sets the Configuration value for state's 'InstanceExposeSuspendedWeb' field
|
||||
func (st *ConfigState) SetInstanceExposeSuspendedWeb(v bool) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.InstanceExposeSuspendedWeb = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// InstanceExposeSuspendedWebFlag returns the flag name for the 'InstanceExposeSuspendedWeb' field
|
||||
func InstanceExposeSuspendedWebFlag() string { return "instance-expose-suspended-web" }
|
||||
|
||||
// GetInstanceExposeSuspendedWeb safely fetches the value for global configuration 'InstanceExposeSuspendedWeb' field
|
||||
func GetInstanceExposeSuspendedWeb() bool { return global.GetInstanceExposeSuspendedWeb() }
|
||||
|
||||
// SetInstanceExposeSuspendedWeb safely sets the value for global configuration 'InstanceExposeSuspendedWeb' field
|
||||
func SetInstanceExposeSuspendedWeb(v bool) { global.SetInstanceExposeSuspendedWeb(v) }
|
||||
|
||||
// GetInstanceExposePublicTimeline safely fetches the Configuration value for state's 'InstanceExposePublicTimeline' field
|
||||
func (st *ConfigState) GetInstanceExposePublicTimeline() (v bool) {
|
||||
st.mutex.Lock()
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
|
|
@ -48,15 +47,10 @@ func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.I
|
|||
return ai, nil
|
||||
}
|
||||
|
||||
func (p *processor) InstancePeersGet(ctx context.Context, authed *oauth.Auth, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) {
|
||||
func (p *processor) InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) {
|
||||
domains := []*apimodel.Domain{}
|
||||
|
||||
if includeOpen {
|
||||
if !config.GetInstanceExposePeers() && (authed.Account == nil || authed.User == nil) {
|
||||
err := fmt.Errorf("peers open query requires an authenticated account/user")
|
||||
return nil, gtserror.NewErrorUnauthorized(err, err.Error())
|
||||
}
|
||||
|
||||
instances, err := p.db.GetInstancePeers(ctx, false)
|
||||
if err != nil && err != db.ErrNoEntries {
|
||||
err = fmt.Errorf("error selecting instance peers: %s", err)
|
||||
|
|
@ -70,11 +64,6 @@ func (p *processor) InstancePeersGet(ctx context.Context, authed *oauth.Auth, in
|
|||
}
|
||||
|
||||
if includeSuspended {
|
||||
if !config.GetInstanceExposeSuspended() && (authed.Account == nil || authed.User == nil) {
|
||||
err := fmt.Errorf("peers suspended query requires an authenticated account/user")
|
||||
return nil, gtserror.NewErrorUnauthorized(err, err.Error())
|
||||
}
|
||||
|
||||
domainBlocks := []*gtsmodel.DomainBlock{}
|
||||
if err := p.db.GetAll(ctx, &domainBlocks); err != nil && err != db.ErrNoEntries {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ type Processor interface {
|
|||
|
||||
// InstanceGet retrieves instance information for serving at api/v1/instance
|
||||
InstanceGet(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode)
|
||||
InstancePeersGet(ctx context.Context, authed *oauth.Auth, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode)
|
||||
InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode)
|
||||
// InstancePatch updates this instance according to the given form.
|
||||
//
|
||||
// It should already be ascertained that the requesting account is authenticated and an admin.
|
||||
|
|
|
|||
71
internal/web/domain-blocklist.go
Normal file
71
internal/web/domain-blocklist.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
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 web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
const (
|
||||
domainBlockListPath = "/about/suspended"
|
||||
)
|
||||
|
||||
func (m *Module) domainBlockListGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, false, false, false, false)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||
return
|
||||
}
|
||||
|
||||
if !config.GetInstanceExposeSuspendedWeb() && (authed.Account == nil || authed.User == nil) {
|
||||
err := fmt.Errorf("this instance does not expose the list of suspended domains publicly")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
|
||||
return
|
||||
}
|
||||
|
||||
host := config.GetHost()
|
||||
instance, err := m.processor.InstanceGet(c.Request.Context(), host)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)
|
||||
return
|
||||
}
|
||||
|
||||
domainBlocks, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), true, false, false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "domain-blocklist.tmpl", gin.H{
|
||||
"instance": instance,
|
||||
"ogMeta": ogBase(instance),
|
||||
"blocklist": domainBlocks,
|
||||
"stylesheets": []string{
|
||||
assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css",
|
||||
},
|
||||
"javascript": []string{distPathPrefix + "/frontend.js"},
|
||||
})
|
||||
}
|
||||
|
|
@ -49,7 +49,9 @@ Disallow: /emoji/
|
|||
# panels
|
||||
Disallow: /admin
|
||||
Disallow: /user
|
||||
Disallow: /settings/`
|
||||
Disallow: /settings/
|
||||
# domain blocklist
|
||||
Disallow: /about/suspended`
|
||||
)
|
||||
|
||||
// robotsGETHandler returns a decent robots.txt that prevents crawling
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ func (m *Module) Route(r router.Router, mi ...gin.HandlerFunc) {
|
|||
r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler)
|
||||
r.AttachHandler(http.MethodGet, robotsPath, m.robotsGETHandler)
|
||||
|
||||
r.AttachHandler(http.MethodGet, domainBlockListPath, m.domainBlockListGETHandler)
|
||||
|
||||
/*
|
||||
Attach redirects from old endpoints to current ones for backwards compatibility
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue