// 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: // . // 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 }