[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
|
@ -292,6 +292,12 @@ instance-expose-peers: false
|
||||||
# Default: false
|
# Default: false
|
||||||
instance-expose-suspended: false
|
instance-expose-suspended: false
|
||||||
|
|
||||||
|
# Bool. Allow unauthenticated users to view /about/suspended,
|
||||||
|
# showing the HTML rendered list of instances that this instance blocks/suspends.
|
||||||
|
# Options: [true, false]
|
||||||
|
# Default: false
|
||||||
|
instance-expose-suspended-web: false
|
||||||
|
|
||||||
# Bool. Allow unauthenticated users to make queries to /api/v1/timelines/public in order
|
# Bool. Allow unauthenticated users to make queries to /api/v1/timelines/public in order
|
||||||
# to see a list of public posts on this server. Even if set to 'false', then authenticated
|
# to see a list of public posts on this server. Even if set to 'false', then authenticated
|
||||||
# users (members of the instance) will still be able to query the endpoint.
|
# users (members of the instance) will still be able to query the endpoint.
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
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/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
|
|
||||||
|
@ -105,6 +106,8 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isUnauthenticated = authed.Account == nil || authed.User == nil
|
||||||
|
|
||||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
|
||||||
return
|
return
|
||||||
|
@ -136,7 +139,19 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) {
|
||||||
flat = true
|
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 {
|
if errWithCode != nil {
|
||||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
|
||||||
return
|
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"`
|
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"`
|
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"`
|
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."`
|
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,
|
InstanceExposePeers: false,
|
||||||
InstanceExposeSuspended: false,
|
InstanceExposeSuspended: false,
|
||||||
|
InstanceExposeSuspendedWeb: false,
|
||||||
InstanceDeliverToSharedInboxes: true,
|
InstanceDeliverToSharedInboxes: true,
|
||||||
|
|
||||||
AccountsRegistrationOpen: true,
|
AccountsRegistrationOpen: true,
|
||||||
|
|
|
@ -78,6 +78,7 @@ func (s *ConfigState) AddServerFlags(cmd *cobra.Command) {
|
||||||
// Instance
|
// Instance
|
||||||
cmd.Flags().Bool(InstanceExposePeersFlag(), cfg.InstanceExposePeers, fieldtag("InstanceExposePeers", "usage"))
|
cmd.Flags().Bool(InstanceExposePeersFlag(), cfg.InstanceExposePeers, fieldtag("InstanceExposePeers", "usage"))
|
||||||
cmd.Flags().Bool(InstanceExposeSuspendedFlag(), cfg.InstanceExposeSuspended, fieldtag("InstanceExposeSuspended", "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"))
|
cmd.Flags().Bool(InstanceDeliverToSharedInboxesFlag(), cfg.InstanceDeliverToSharedInboxes, fieldtag("InstanceDeliverToSharedInboxes", "usage"))
|
||||||
|
|
||||||
// Accounts
|
// Accounts
|
||||||
|
|
|
@ -724,6 +724,31 @@ func GetInstanceExposeSuspended() bool { return global.GetInstanceExposeSuspende
|
||||||
// SetInstanceExposeSuspended safely sets the value for global configuration 'InstanceExposeSuspended' field
|
// SetInstanceExposeSuspended safely sets the value for global configuration 'InstanceExposeSuspended' field
|
||||||
func SetInstanceExposeSuspended(v bool) { global.SetInstanceExposeSuspended(v) }
|
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
|
// GetInstanceExposePublicTimeline safely fetches the Configuration value for state's 'InstanceExposePublicTimeline' field
|
||||||
func (st *ConfigState) GetInstanceExposePublicTimeline() (v bool) {
|
func (st *ConfigState) GetInstanceExposePublicTimeline() (v bool) {
|
||||||
st.mutex.Lock()
|
st.mutex.Lock()
|
||||||
|
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||||
|
@ -48,15 +47,10 @@ func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.I
|
||||||
return ai, nil
|
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{}
|
domains := []*apimodel.Domain{}
|
||||||
|
|
||||||
if includeOpen {
|
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)
|
instances, err := p.db.GetInstancePeers(ctx, false)
|
||||||
if err != nil && err != db.ErrNoEntries {
|
if err != nil && err != db.ErrNoEntries {
|
||||||
err = fmt.Errorf("error selecting instance peers: %s", err)
|
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 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{}
|
domainBlocks := []*gtsmodel.DomainBlock{}
|
||||||
if err := p.db.GetAll(ctx, &domainBlocks); err != nil && err != db.ErrNoEntries {
|
if err := p.db.GetAll(ctx, &domainBlocks); err != nil && err != db.ErrNoEntries {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
|
|
@ -172,7 +172,7 @@ type Processor interface {
|
||||||
|
|
||||||
// InstanceGet retrieves instance information for serving at api/v1/instance
|
// InstanceGet retrieves instance information for serving at api/v1/instance
|
||||||
InstanceGet(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode)
|
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.
|
// InstancePatch updates this instance according to the given form.
|
||||||
//
|
//
|
||||||
// It should already be ascertained that the requesting account is authenticated and an admin.
|
// It should already be ascertained that the requesting account is authenticated and an admin.
|
||||||
|
|
|
@ -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
|
# panels
|
||||||
Disallow: /admin
|
Disallow: /admin
|
||||||
Disallow: /user
|
Disallow: /user
|
||||||
Disallow: /settings/`
|
Disallow: /settings/
|
||||||
|
# domain blocklist
|
||||||
|
Disallow: /about/suspended`
|
||||||
)
|
)
|
||||||
|
|
||||||
// robotsGETHandler returns a decent robots.txt that prevents crawling
|
// 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, confirmEmailPath, m.confirmEmailGETHandler)
|
||||||
r.AttachHandler(http.MethodGet, robotsPath, m.robotsGETHandler)
|
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
|
Attach redirects from old endpoints to current ones for backwards compatibility
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"report-max-size":100,"report-sweep-freq":10000000000,"report-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-sqlite-busy-timeout":1000000000,"db-sqlite-cache-size":0,"db-sqlite-journal-mode":"DELETE","db-sqlite-synchronous":"FULL","db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
|
EXPECT='{"account-domain":"peepee","accounts-allow-custom-css":true,"accounts-approval-required":false,"accounts-reason-required":false,"accounts-registration-open":true,"advanced-cookies-samesite":"strict","advanced-rate-limit-requests":6969,"advanced-throttling-multiplier":-1,"application-name":"gts","bind-address":"127.0.0.1","cache":{"gts":{"account-max-size":99,"account-sweep-freq":1000000000,"account-ttl":10800000000000,"block-max-size":100,"block-sweep-freq":10000000000,"block-ttl":300000000000,"domain-block-max-size":1000,"domain-block-sweep-freq":60000000000,"domain-block-ttl":86400000000000,"emoji-category-max-size":100,"emoji-category-sweep-freq":10000000000,"emoji-category-ttl":300000000000,"emoji-max-size":500,"emoji-sweep-freq":10000000000,"emoji-ttl":300000000000,"mention-max-size":500,"mention-sweep-freq":10000000000,"mention-ttl":300000000000,"notification-max-size":500,"notification-sweep-freq":10000000000,"notification-ttl":300000000000,"report-max-size":100,"report-sweep-freq":10000000000,"report-ttl":300000000000,"status-max-size":500,"status-sweep-freq":10000000000,"status-ttl":300000000000,"tombstone-max-size":100,"tombstone-sweep-freq":10000000000,"tombstone-ttl":300000000000,"user-max-size":100,"user-sweep-freq":10000000000,"user-ttl":300000000000}},"config-path":"internal/config/testdata/test.yaml","db-address":":memory:","db-database":"gotosocial_prod","db-password":"hunter2","db-port":6969,"db-sqlite-busy-timeout":1000000000,"db-sqlite-cache-size":0,"db-sqlite-journal-mode":"DELETE","db-sqlite-synchronous":"FULL","db-tls-ca-cert":"","db-tls-mode":"disable","db-type":"sqlite","db-user":"sex-haver","dry-run":false,"email":"","host":"example.com","instance-deliver-to-shared-inboxes":false,"instance-expose-peers":true,"instance-expose-public-timeline":true,"instance-expose-suspended":true,"instance-expose-suspended-web":true,"landing-page-user":"admin","letsencrypt-cert-dir":"/gotosocial/storage/certs","letsencrypt-email-address":"","letsencrypt-enabled":true,"letsencrypt-port":80,"log-db-queries":true,"log-level":"info","media-description-max-chars":5000,"media-description-min-chars":69,"media-emoji-local-max-size":420,"media-emoji-remote-max-size":420,"media-image-max-size":420,"media-remote-cache-days":30,"media-video-max-size":420,"oidc-client-id":"1234","oidc-client-secret":"shhhh its a secret","oidc-enabled":true,"oidc-idp-name":"sex-haver","oidc-issuer":"whoknows","oidc-link-existing":true,"oidc-scopes":["read","write"],"oidc-skip-verification":true,"password":"","path":"","port":6969,"protocol":"http","smtp-from":"queen.rip.in.piss@terfisland.org","smtp-host":"example.com","smtp-password":"hunter2","smtp-port":4269,"smtp-username":"sex-haver","software-version":"","statuses-cw-max-chars":420,"statuses-max-chars":69,"statuses-media-max-files":1,"statuses-poll-max-options":1,"statuses-poll-option-max-chars":50,"storage-backend":"local","storage-local-base-path":"/root/store","storage-s3-access-key":"minio","storage-s3-bucket":"gts","storage-s3-endpoint":"localhost:9000","storage-s3-proxy":true,"storage-s3-secret-key":"miniostorage","storage-s3-use-ssl":false,"syslog-address":"127.0.0.1:6969","syslog-enabled":true,"syslog-protocol":"udp","trusted-proxies":["127.0.0.1/32","docker.host.local"],"username":"","web-asset-base-dir":"/root","web-template-base-dir":"/root"}'
|
||||||
|
|
||||||
# Set all the environment variables to
|
# Set all the environment variables to
|
||||||
# ensure that these are parsed without panic
|
# ensure that these are parsed without panic
|
||||||
|
@ -32,6 +32,7 @@ GTS_WEB_TEMPLATE_BASE_DIR='/root' \
|
||||||
GTS_WEB_ASSET_BASE_DIR='/root' \
|
GTS_WEB_ASSET_BASE_DIR='/root' \
|
||||||
GTS_INSTANCE_EXPOSE_PEERS=true \
|
GTS_INSTANCE_EXPOSE_PEERS=true \
|
||||||
GTS_INSTANCE_EXPOSE_SUSPENDED=true \
|
GTS_INSTANCE_EXPOSE_SUSPENDED=true \
|
||||||
|
GTS_INSTANCE_EXPOSE_SUSPENDED_WEB=true \
|
||||||
GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE=true \
|
GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE=true \
|
||||||
GTS_INSTANCE_DELIVER_TO_SHARED_INBOXES=false \
|
GTS_INSTANCE_DELIVER_TO_SHARED_INBOXES=false \
|
||||||
GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \
|
GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \
|
||||||
|
|
|
@ -62,6 +62,7 @@ var testDefaults = config.Configuration{
|
||||||
|
|
||||||
InstanceExposePeers: true,
|
InstanceExposePeers: true,
|
||||||
InstanceExposeSuspended: true,
|
InstanceExposeSuspended: true,
|
||||||
|
InstanceExposeSuspendedWeb: true,
|
||||||
InstanceDeliverToSharedInboxes: true,
|
InstanceDeliverToSharedInboxes: true,
|
||||||
|
|
||||||
AccountsRegistrationOpen: true,
|
AccountsRegistrationOpen: true,
|
||||||
|
|
|
@ -114,6 +114,6 @@ $settings-nav-bg-active: $gray2;
|
||||||
$error-fg: $error1;
|
$error-fg: $error1;
|
||||||
$error-bg: $error2;
|
$error-bg: $error2;
|
||||||
|
|
||||||
$settings-entry-bg: $gray2;
|
$list-entry-bg: $gray2;
|
||||||
$settings-entry-alternate-bg: $gray3;
|
$list-entry-alternate-bg: $gray3;
|
||||||
$settings-entry-hover-bg: $gray4;
|
$list-entry-hover-bg: $gray4;
|
|
@ -414,3 +414,79 @@ label {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.scrolling {
|
||||||
|
max-height: 40rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header, .entry {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
border: 0.1rem solid transparent !important; /* for alignment with .entry border padding */
|
||||||
|
background: $gray1 !important;
|
||||||
|
display: flex;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
background: $list-entry-bg;
|
||||||
|
border: 0.1rem solid transparent;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: $list-entry-alternate-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $list-entry-hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active, &:focus, &:hover, &:target {
|
||||||
|
border-color: $fg-accent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain-blocklist {
|
||||||
|
box-shadow: $boxshadow;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 15rem 1fr;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: start;
|
||||||
|
border: $boxshadow-border;
|
||||||
|
border-top-color: transparent;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.domain a {
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block; /* so it wraps properly */
|
||||||
|
}
|
||||||
|
|
||||||
|
.public_comment p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .domain {
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -368,49 +368,6 @@ span.form-info {
|
||||||
font-weight: initial;
|
font-weight: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
&.scrolling {
|
|
||||||
max-height: 40rem;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header, .entry {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
border: 0.1rem solid transparent; /* for alignment with .entry border padding */
|
|
||||||
background: $gray2;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=checkbox] {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entry {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
background: $settings-entry-bg;
|
|
||||||
border: 0.1rem solid transparent;
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
background: $settings-entry-alternate-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $settings-entry-hover-bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active, &:focus, &:hover {
|
|
||||||
border-color: $fg-accent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-list {
|
.checkbox-list {
|
||||||
.header, .entry {
|
.header, .entry {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
@ -446,7 +403,7 @@ span.form-info {
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-list {
|
.emoji-list {
|
||||||
background: $settings-entry-bg;
|
background: $list-entry-bg;
|
||||||
|
|
||||||
.entry {
|
.entry {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -472,7 +429,7 @@ span.form-info {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $settings-entry-hover-bg;
|
background: $list-entry-hover-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
{{- /*
|
||||||
|
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/>.
|
||||||
|
*/ -}}
|
||||||
|
|
||||||
|
{{ template "header.tmpl" .}}
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h1>Suspended Instances</h1>
|
||||||
|
<p>
|
||||||
|
The following list of domains have been suspended by the administrator(s) of this server.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
All current and future accounts on these instances are blocked, and no more data is federated to the remote
|
||||||
|
servers.
|
||||||
|
This extends to subdomains, so an entry for 'example.com' includes 'social.example.com' as well.
|
||||||
|
</p>
|
||||||
|
<div class="list domain-blocklist">
|
||||||
|
<div class="header entry">
|
||||||
|
<div class="domain">Domain</div>
|
||||||
|
<div class="public_comment">Public comment</div>
|
||||||
|
</div>
|
||||||
|
{{range .blocklist}}
|
||||||
|
<div class="entry" id="{{.Domain}}">
|
||||||
|
<div class="domain">
|
||||||
|
<a class="text-cutoff" href="#{{.Domain}}" title="{{.Domain}}">{{.Domain}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="public_comment">
|
||||||
|
<p>
|
||||||
|
{{.PublicComment}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.tmpl" .}}
|
Loading…
Reference in New Issue