[feature] Allow exposing allows, implement /api/v1/domain_blocks and /api/v1/domain_allows (#4169)

- adds config flags `instance-expose-allowlist` and `instance-expose-allowlist-web` to allow instance admins to expose their allowlist via the web + api.
- renames `instance-expose-suspended` and `instance-expose-suspended-web` to  `instance-expose-blocklist` and `instance-expose-blocklist-web`.
- deprecates the `suspended` filter on `/api/v1/instance/peers` endpoint and adds `blocked` and `allowed` filters
- adds the `flat` query param to `/api/v1/instance/peers` to allow forcing return of a flat list of domains
- implements `/api/v1/instance/domain_blocks` and `/api/v1/instance/domain_allows` endpoints with or without auth depending on config
- rejigs the instance about page to include a general section on domain permissions, with block and allow subsections (and appropriate links)

Closes https://codeberg.org/superseriousbusiness/gotosocial/issues/3847
Closes https://codeberg.org/superseriousbusiness/gotosocial/issues/4150

Prerequisite to https://codeberg.org/superseriousbusiness/gotosocial/issues/3711

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4169
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
tobi
2025-05-20 11:47:40 +02:00
committed by kim
parent 3a29a59e55
commit ec4d4d0115
23 changed files with 986 additions and 271 deletions

View File

@@ -19,8 +19,10 @@ package processing
import (
"context"
"errors"
"fmt"
"sort"
"slices"
"strings"
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/config"
@@ -31,6 +33,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/text"
"code.superseriousbusiness.org/gotosocial/internal/typeutils"
"code.superseriousbusiness.org/gotosocial/internal/util"
"code.superseriousbusiness.org/gotosocial/internal/util/xslices"
"code.superseriousbusiness.org/gotosocial/internal/validate"
)
@@ -62,70 +65,126 @@ func (p *Processor) InstanceGetV2(ctx context.Context) (*apimodel.InstanceV2, gt
return ai, nil
}
func (p *Processor) InstancePeersGet(ctx context.Context, includeSuspended bool, includeOpen bool, flat bool) (interface{}, gtserror.WithCode) {
domains := []*apimodel.Domain{}
func (p *Processor) InstancePeersGet(
ctx context.Context,
includeBlocked bool,
includeAllowed bool,
includeOpen bool,
flatten bool,
includeSeverity bool,
) (any, gtserror.WithCode) {
var (
domainPerms []gtsmodel.DomainPermission
apiDomains []*apimodel.Domain
)
if includeBlocked {
blocks, err := p.state.DB.GetDomainBlocks(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err := gtserror.Newf("db error getting domain blocks: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
for _, block := range blocks {
domainPerms = append(domainPerms, block)
}
} else if includeAllowed {
allows, err := p.state.DB.GetDomainAllows(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err := gtserror.Newf("db error getting domain allows: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
for _, allow := range allows {
domainPerms = append(domainPerms, allow)
}
}
for _, domainPerm := range domainPerms {
// Domain may be in Punycode,
// de-punify it just in case.
domain := domainPerm.GetDomain()
depunied, err := util.DePunify(domain)
if err != nil {
log.Errorf(ctx, "couldn't depunify domain %s: %v", domain, err)
continue
}
if util.PtrOrZero(domainPerm.GetObfuscate()) {
// Obfuscate the de-punified version.
depunied = obfuscate(depunied)
}
apiDomain := &apimodel.Domain{
Domain: depunied,
Comment: util.Ptr(domainPerm.GetPublicComment()),
}
if domainPerm.GetType() == gtsmodel.DomainPermissionBlock {
const severity = "suspend"
apiDomain.Severity = severity
suspendedAt := domainPerm.GetCreatedAt()
apiDomain.SuspendedAt = util.FormatISO8601(suspendedAt)
}
apiDomains = append(apiDomains, apiDomain)
}
if includeOpen {
instances, err := p.state.DB.GetInstancePeers(ctx, false)
if err != nil && err != db.ErrNoEntries {
err = fmt.Errorf("error selecting instance peers: %s", err)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("db error getting instance peers: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
for _, i := range instances {
for _, instance := range instances {
// Domain may be in Punycode,
// de-punify it just in case.
d, err := util.DePunify(i.Domain)
domain := instance.Domain
depunied, err := util.DePunify(domain)
if err != nil {
log.Errorf(ctx, "couldn't depunify domain %s: %s", i.Domain, err)
log.Errorf(ctx, "couldn't depunify domain %s: %v", domain, err)
continue
}
domains = append(domains, &apimodel.Domain{Domain: d})
apiDomains = append(
apiDomains,
&apimodel.Domain{
Domain: depunied,
},
)
}
}
if includeSuspended {
domainBlocks := []*gtsmodel.DomainBlock{}
if err := p.state.DB.GetAll(ctx, &domainBlocks); err != nil && err != db.ErrNoEntries {
return nil, gtserror.NewErrorInternalError(err)
}
// Sort a-z.
slices.SortFunc(
apiDomains,
func(a, b *apimodel.Domain) int {
return strings.Compare(a.Domain, b.Domain)
},
)
for _, domainBlock := range domainBlocks {
// Domain may be in Punycode,
// de-punify it just in case.
d, err := util.DePunify(domainBlock.Domain)
if err != nil {
log.Errorf(ctx, "couldn't depunify domain %s: %s", domainBlock.Domain, err)
continue
}
// Deduplicate.
apiDomains = xslices.DeduplicateFunc(
apiDomains,
func(v *apimodel.Domain) string {
return v.Domain
},
)
if *domainBlock.Obfuscate {
// Obfuscate the de-punified version.
d = obfuscate(d)
}
domains = append(domains, &apimodel.Domain{
Domain: d,
SuspendedAt: util.FormatISO8601(domainBlock.CreatedAt),
Comment: &domainBlock.PublicComment,
})
}
if flatten {
// Return just the domains.
return xslices.Gather(
[]string{},
apiDomains,
func(v *apimodel.Domain) string {
return v.Domain
},
), nil
}
sort.Slice(domains, func(i, j int) bool {
return domains[i].Domain < domains[j].Domain
})
if flat {
flattened := []string{}
for _, d := range domains {
flattened = append(flattened, d.Domain)
}
return flattened, nil
}
return domains, nil
return apiDomains, nil
}
func (p *Processor) InstanceGetRules(ctx context.Context) ([]apimodel.InstanceRule, gtserror.WithCode) {