mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-25 08:30:29 +00:00
[feature] Provide .well-known/host-meta endpoint (#1604)
* [feature] Provide .well-known/host-meta endpoint This adds the host-meta endpoint as Mastodon clients use this to discover the API domain to use when the host and account domains aren't the same. * Address review comments
This commit is contained in:
parent
9ba35c65eb
commit
a312238e79
9 changed files with 167 additions and 4 deletions
|
@ -43,6 +43,9 @@ host: "localhost"
|
|||
# to "gts.example.org/.well-known/webfinger" so that GtS can handle them properly.
|
||||
#
|
||||
# You should also redirect requests at "example.org/.well-known/nodeinfo" in the same way.
|
||||
#
|
||||
# You should also redirect requests at "example.org/.well-known/host-meta" in the same way. This endpoint is used by a number of clients to discover the API endpoint to use when the host and account domain are different.
|
||||
#
|
||||
# An empty string (ie., not set) means that the same value as 'host' will be used.
|
||||
#
|
||||
# DO NOT change this after your server has already run once, or you will break things!
|
||||
|
|
|
@ -32,6 +32,9 @@ host: "localhost"
|
|||
# to "gts.example.org/.well-known/webfinger" so that GtS can handle them properly.
|
||||
#
|
||||
# You should also redirect requests at "example.org/.well-known/nodeinfo" in the same way.
|
||||
#
|
||||
# You should also redirect requests at "example.org/.well-known/host-meta" in the same way. This endpoint is used by a number of clients to discover the API endpoint to use when the host and account domain are different.
|
||||
#
|
||||
# An empty string (ie., not set) means that the same value as 'host' will be used.
|
||||
#
|
||||
# DO NOT change this after your server has already run once, or you will break things!
|
||||
|
@ -71,6 +74,10 @@ http {
|
|||
rewrite ^.*$ https://fedi.example.org/.well-known/webfinger permanent;
|
||||
}
|
||||
|
||||
location /.well-known/host-meta {
|
||||
rewrite ^.*$ https://fedi.example.org/.well-known/host-meta permanent;
|
||||
}
|
||||
|
||||
location /.well-known/nodeinfo {
|
||||
rewrite ^.*$ https://fedi.example.org/.well-known/nodeinfo permanent;
|
||||
}
|
||||
|
@ -91,7 +98,7 @@ If `example.org` is running on [Traefik](https://doc.traefik.io/traefik/), we co
|
|||
labels:
|
||||
- 'traefik.http.routers.myservice.rule=Host(`example.org`)'
|
||||
- 'traefik.http.middlewares.myservice-gts.redirectregex.permanent=true'
|
||||
- 'traefik.http.middlewares.myservice-gts.redirectregex.regex=^https://(.*)/.well-known/(webfinger|nodeinfo)$$'
|
||||
- 'traefik.http.middlewares.myservice-gts.redirectregex.regex=^https://(.*)/.well-known/(webfinger|nodeinfo|host-meta)$$'
|
||||
- 'traefik.http.middlewares.myservice-gts.redirectregex.replacement=https://fedi.$${1}/.well-known/$${2}'
|
||||
- 'traefik.http.routers.myservice.middlewares=myservice-gts@docker'
|
||||
```
|
||||
|
@ -279,9 +286,9 @@ This section contains a number of additional things for configuring nginx.
|
|||
|
||||
If you want to harden up your NGINX deployment with advanced configuration options, there are many guides online for doing so ([for example](https://beaglesecurity.com/blog/article/nginx-server-security.html)). Try to find one that's up to date. Mozilla also publishes best-practice ssl configuration [here](https://ssl-config.mozilla.org/).
|
||||
|
||||
### Caching Webfinger and Public Key responses
|
||||
### Caching Webfinger, Webhost Metadata and Public Key responses
|
||||
|
||||
It's possible to use nginx to cache webfinger and public key responses. This may be useful in order to ensure clients still get a response on these endpoints even if your GoToSocial instance is (temporarily) down, or requests are being throttled.
|
||||
It's possible to use nginx to cache webfinger, host-meta and public key responses. This may be useful in order to ensure clients still get a response on these endpoints even if your GoToSocial instance is (temporarily) down, or requests are being throttled.
|
||||
|
||||
You'll need to configure two things:
|
||||
|
||||
|
@ -311,7 +318,7 @@ server {
|
|||
|
||||
### NEW STUFF STARTS HERE ###
|
||||
|
||||
location /.well-known/webfinger {
|
||||
location ~ /.well-known/(webfinger|host-meta)$ {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
|
|
@ -55,6 +55,11 @@ host: "localhost"
|
|||
# to "gts.example.org/.well-known/webfinger" so that GtS can handle them properly.
|
||||
#
|
||||
# You should also redirect requests at "example.org/.well-known/nodeinfo" in the same way.
|
||||
#
|
||||
# You should also redirect requests at "example.org/.well-known/host-meta" in the same way. This endpoint
|
||||
# is used by a number of clients to discover the API endpoint to use when the host and account domain are
|
||||
# different.
|
||||
#
|
||||
# An empty string (ie., not set) means that the same value as 'host' will be used.
|
||||
#
|
||||
# DO NOT change this after your server has already run once, or you will break things!
|
||||
|
|
|
@ -25,6 +25,7 @@ type MIME string
|
|||
const (
|
||||
AppJSON MIME = `application/json`
|
||||
AppXML MIME = `application/xml`
|
||||
AppXMLXRD MIME = `application/xrd+xml`
|
||||
AppRSSXML MIME = `application/rss+xml`
|
||||
AppActivityJSON MIME = `application/activity+json`
|
||||
AppActivityLDJSON MIME = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
|
||||
|
|
|
@ -58,6 +58,11 @@ var HTMLOrActivityPubHeaders = []MIME{
|
|||
AppActivityLDJSON,
|
||||
}
|
||||
|
||||
var HostMetaHeaders = []MIME{
|
||||
AppXMLXRD,
|
||||
AppXML,
|
||||
}
|
||||
|
||||
// NegotiateAccept takes the *gin.Context from an incoming request, and a
|
||||
// slice of Offers, and performs content negotiation for the given request
|
||||
// with the given content-type offers. It will return a string representation
|
||||
|
|
|
@ -20,6 +20,7 @@ package api
|
|||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/hostmeta"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/nodeinfo"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
|
@ -30,6 +31,7 @@ import (
|
|||
type WellKnown struct {
|
||||
nodeInfo *nodeinfo.Module
|
||||
webfinger *webfinger.Module
|
||||
hostMeta *hostmeta.Module
|
||||
}
|
||||
|
||||
func (w *WellKnown) Route(r router.Router, m ...gin.HandlerFunc) {
|
||||
|
@ -45,11 +47,13 @@ func (w *WellKnown) Route(r router.Router, m ...gin.HandlerFunc) {
|
|||
|
||||
w.nodeInfo.Route(wellKnownGroup.Handle)
|
||||
w.webfinger.Route(wellKnownGroup.Handle)
|
||||
w.hostMeta.Route(wellKnownGroup.Handle)
|
||||
}
|
||||
|
||||
func NewWellKnown(p *processing.Processor) *WellKnown {
|
||||
return &WellKnown{
|
||||
nodeInfo: nodeinfo.New(p),
|
||||
webfinger: webfinger.New(p),
|
||||
hostMeta: hostmeta.New(p),
|
||||
}
|
||||
}
|
||||
|
|
45
internal/api/wellknown/hostmeta/hostmeta.go
Normal file
45
internal/api/wellknown/hostmeta/hostmeta.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
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 hostmeta
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
)
|
||||
|
||||
const (
|
||||
HostMetaContentType = "application/xrd+xml"
|
||||
HostMetaPath = "/host-meta"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
}
|
||||
|
||||
func New(processor *processing.Processor) *Module {
|
||||
return &Module{
|
||||
processor: processor,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
|
||||
attachHandler(http.MethodGet, HostMetaPath, m.HostMetaGETHandler)
|
||||
}
|
73
internal/api/wellknown/hostmeta/hostmetaget.go
Normal file
73
internal/api/wellknown/hostmeta/hostmetaget.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
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 hostmeta
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
// HostMetaGETHandler swagger:operation GET /.well-known/host-meta hostMetaGet
|
||||
//
|
||||
// Returns a compliant hostmeta response to web host metadata queries.
|
||||
//
|
||||
// See: https://www.rfc-editor.org/rfc/rfc6415.html
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - .well-known
|
||||
//
|
||||
// produces:
|
||||
// - application/xrd+xml"
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// schema:
|
||||
// "$ref": "#/definitions/hostmeta"
|
||||
func (m *Module) HostMetaGETHandler(c *gin.Context) {
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.HostMetaHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
hostMeta := m.processor.Fedi().HostMetaGet()
|
||||
|
||||
// this setup with a separate buffer we encode into is used because
|
||||
// xml.Marshal does not emit xml.Header by itself
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Preallocate buffer of reasonable length.
|
||||
buf.Grow(len(xml.Header) + 64)
|
||||
|
||||
// No need to check for error on write to buffer.
|
||||
_, _ = buf.WriteString(xml.Header)
|
||||
|
||||
// Encode host-meta as XML to in-memory buffer.
|
||||
if err := xml.NewEncoder(&buf).Encode(hostMeta); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, HostMetaContentType, buf.Bytes())
|
||||
}
|
|
@ -28,6 +28,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
hostMetaXMLNS = "http://docs.oasis-open.org/ns/xri/xrd-1.0"
|
||||
hostMetaRel = "lrdd"
|
||||
hostMetaType = "application/xrd+xml"
|
||||
hostMetaTemplate = ".well-known/webfinger?resource={uri}"
|
||||
nodeInfoVersion = "2.0"
|
||||
nodeInfoSoftwareName = "gotosocial"
|
||||
nodeInfoRel = "http://nodeinfo.diaspora.software/ns/schema/" + nodeInfoVersion
|
||||
|
@ -96,6 +100,22 @@ func (p *Processor) NodeInfoGet(ctx context.Context) (*apimodel.Nodeinfo, gtserr
|
|||
}, nil
|
||||
}
|
||||
|
||||
// HostMetaGet returns a host-meta struct in response to a host-meta request.
|
||||
func (p *Processor) HostMetaGet() *apimodel.HostMeta {
|
||||
protocol := config.GetProtocol()
|
||||
host := config.GetHost()
|
||||
return &apimodel.HostMeta{
|
||||
XMLNS: hostMetaXMLNS,
|
||||
Link: []apimodel.Link{
|
||||
{
|
||||
Rel: hostMetaRel,
|
||||
Type: hostMetaType,
|
||||
Template: fmt.Sprintf("%s://%s/%s", protocol, host, hostMetaTemplate),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WebfingerGet handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups.
|
||||
func (p *Processor) WebfingerGet(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) {
|
||||
// Get the local account the request is referring to.
|
||||
|
|
Loading…
Reference in a new issue