linstrom/endpoints/api/ap/webfinger.go
2024-01-29 17:08:52 +00:00

105 lines
2.8 KiB
Go

// Copyright (c) 2024 mStar
//
// Licensed under the EUPL, Version 1.2
//
// You may not use this work except in compliance with the Licence.
// You should have received a copy of the Licence along with this work. If not, see:
// <https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12>.
// See the Licence for the specific language governing permissions and limitations under the Licence.
//
package ap
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/julienschmidt/httprouter"
"gitlab.com/mstarongitlab/goutils/other"
"gitlab.com/mstarongitlab/linstrom/server"
"gitlab.com/mstarongitlab/linstrom/storage"
"gitlab.com/mstarongitlab/linstrom/types"
)
var ErrInvalidResource = errors.New("invalid resource")
type webfingerLink struct {
Rel string `json:"self"`
Type string `json:"type"`
Target url.URL `json:"href"`
}
type WebfingerResponse struct {
Subject string `json:"subject"`
Links []webfingerLink `json:"links"`
}
func WebfingerEndpoint(server *server.Server) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
r.ParseForm()
req := r.FormValue("resource")
if req == "" {
http.NotFound(w, r)
return
}
acc, err := getAccountFromWebfingerResource(req)
if errors.Is(err, ErrInvalidResource) {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if acc.Host.Hostname() != server.Config.General.Domain {
http.Error(w, fmt.Sprintf("wrong host, try %s", &acc.Host), http.StatusNotFound)
return
}
person, err := server.Storage.GetPersonByName(acc.Name)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
http.Error(w, fmt.Sprintf("Account %s not found", acc), http.StatusNotFound)
return
} else {
http.Error(w, fmt.Sprintf("Internal error: %s, please report", err.Error()), http.StatusInternalServerError)
}
}
response := WebfingerResponse{
Links: []webfingerLink{
{
Rel: "me",
Type: "application/activity+json",
Target: *other.Must(url.Parse(fmt.Sprintf(
"https://%s/api/ap/user/%s",
server.Config.General.Domain,
person.Uid,
))),
},
},
}
json.Marshal(response)
}
}
// turns "acct:bugle@bugle.lol" into { Name: bugle, Host: bugle.lol }
func getAccountFromWebfingerResource(r string) (*types.AccountHandle, error) {
acctCheck, acc, found := strings.Cut(r, ":")
if !found || acctCheck != "acct" {
return nil, ErrInvalidResource
}
name, rawHost, found := strings.Cut(acc, "@")
if !found {
return nil, ErrInvalidResource
}
host, err := url.Parse(rawHost)
if err != nil {
return nil, fmt.Errorf("%w: Invalid url: %w", ErrInvalidResource, err)
}
return &types.AccountHandle{
Name: name,
Host: *host,
}, nil
}