mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 23:02:25 -05: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
				
			
		|  | @ -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. | ||||||
|  |  | ||||||
							
								
								
									
										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 | # 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; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
							
								
								
									
										51
									
								
								web/template/domain-blocklist.tmpl
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								web/template/domain-blocklist.tmpl
									
										
									
									
									
										Normal file
									
								
							|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue