package server import ( "encoding/json" "fmt" "net/http" "strings" "github.com/sirupsen/logrus" "gitlab.com/mstarongitlab/linstrom/config" "gitlab.com/mstarongitlab/linstrom/server/middlewares" "gitlab.com/mstarongitlab/linstrom/storage" ) type webfingerUrl struct { Relation string `json:"rel"` Type string `json:"type"` Url string `json:"href"` } // NOTE: Unused for now // Reason: No endpoint for eg follow authorisation yet type webfingerTemplate struct { Relation string `json:"rel"` Template string `json:"template"` } type webfingerResponse struct { Subject string `json:"subject"` // Any because it's either a webfingerTemplate or webfingerUrl Links []any `json:"links"` } // Mount under /.well-known/webfinger // Handles webfinger requests which are used to determine whether an account exists on this server // Additionally, a sucessful query will return a set of links related to that account, such as the activitypub view and the web view func webfingerHandler(w http.ResponseWriter, r *http.Request) { logEntry, ok := r.Context().Value(middlewares.CONTEXT_KEY_LOGRUS).(*logrus.Entry) if !ok { http.Error(w, "couldn't get logging entry from context", http.StatusInternalServerError) return } store := storage.Storage{} requestedResource := r.FormValue("resource") if requestedResource == "" { http.Error(w, "bad request. Include \"resource\" parameter", http.StatusBadRequest) logEntry.Infoln("No resource parameter. Cancelling") return } accName := strings.TrimPrefix(requestedResource, "acc:") acc, err := store.FindLocalAccount(accName) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) logEntry.WithError(err).Warningln("couldn't find account") return } finger, err := accToWebfinger(acc) if err != nil { http.Error(w, "failed to build webfinger", http.StatusInternalServerError) return } data, err := json.Marshal(finger) if err != nil { http.Error(w, "failed to build json", http.StatusInternalServerError) return } fmt.Fprint(w, string(data)) } func accToWebfinger(acc *storage.User) (*webfingerResponse, error) { // First ensure config is set if config.Global == nil { return nil, config.ErrGlobalConfigNotSet } // Then build the ap link apLink := webfingerUrl{ Relation: "self", Type: "application/activity+json", Url: config.Global.General.FullDomain, } if config.Global.General.PublicPort != 80 && config.Global.General.PublicPort != 443 { apLink.Url += fmt.Sprintf(":%d", config.Global.General.PublicPort) } apLink.Url += "/api/ap/user/" + acc.ID // Now the web view viewLink := webfingerUrl{ Relation: "http://webfinger.net/rel/profile-page", Type: "text/html", Url: config.Global.General.FullDomain, } if config.Global.General.PublicPort != 80 && config.Global.General.PublicPort != 443 { viewLink.Url += fmt.Sprintf(":%d", config.Global.General.PublicPort) } viewLink.Url += "/@" + acc.GetHandleNameOnly() // TODO: Add follow authorisation template once the endpoint is available response := webfingerResponse{ Subject: fmt.Sprintf("acct:%s", strings.TrimPrefix(acc.Handle, "@")), Links: []any{ apLink, viewLink, }, } return &response, nil }