package api import ( "errors" "fmt" "net/http" "regexp" webutils "git.mstar.dev/mstar/goutils/http" "git.mstar.dev/mstar/goutils/other" "github.com/rs/zerolog/hlog" "gorm.io/gorm" "git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/shared" "git.mstar.dev/mstar/linstrom/storage-new" "git.mstar.dev/mstar/linstrom/storage-new/dbgen" ) var webfingerResourceRegex = regexp.MustCompile(`acct:(?P[\w-]+)@(?[\w\.-]+)`) func WellKnownWebfinger(w http.ResponseWriter, r *http.Request) { type OutboundLink struct { Relation string `json:"rel"` Type string `json:"type"` Href *string `json:"href,omitempty"` } type Outbound struct { Subject string `json:"subject"` Links []OutboundLink `json:"links"` Aliases []string `json:"aliases,omitempty"` } log := hlog.FromRequest(r) requestedResource := r.FormValue("resource") matches := webfingerResourceRegex.FindStringSubmatch(requestedResource) if len(matches) == 0 { webutils.ProblemDetails( w, http.StatusBadRequest, "/errors/webfinger-bad-resource", "Bad resource format", other.IntoPointer( `The "resource" parameter must be available and of the form "acct:@`, ), nil, ) return } // NOTE: Safe to access since, if the regex matches, it must include both groups // Index 0 is the full matching string // Index 1 is the username // Index 2 is something vaguely domain-like username := matches[1] domain := matches[2] // Fail if requested user is a different domain // TODO: Decide whether to include the info that it's a different domain if domain != config.GlobalConfig.General.GetFullDomain() { webutils.ProblemDetailsStatusOnly(w, 404) return } user, err := dbgen.User.GetByUsername(username) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { webutils.ProblemDetailsStatusOnly(w, 404) } else { // Fail the request, then attempt to reconnect webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil) if storage.HandleReconnectError(err) { log.Warn().Msg("Connection to db lost. Reconnect attempt started") } else { log.Error().Err(err).Msg("Failed to get user from db") } } return } data := Outbound{ Subject: matches[0], Links: []OutboundLink{ { Relation: "self", Type: "application/activity+json", Href: other.IntoPointer( fmt.Sprintf( "%s/api/activitypub/user/%s", config.GlobalConfig.General.GetFullPublicUrl(), user.ID, ), ), }, { Relation: "http://webfinger.net/rel/profile-page", Type: "text/html", Href: other.IntoPointer( fmt.Sprintf( "%s/user/%s", config.GlobalConfig.General.GetFullPublicUrl(), user.ID, ), ), }, }, } webutils.SendJson(w, &data) } func Nodeinfo(w http.ResponseWriter, r *http.Request) { u := dbgen.User log := hlog.FromRequest(r) userCount, err := u.Where(u.DeletedAt.IsNull(), u.Verified.Is(true)).Count() if err != nil { webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil) if storage.HandleReconnectError(err) { log.Warn().Msg("Connection to db lost. Reconnect attempt started") } else { log.Error().Err(err).Msg("Failed to get total user count from db") } return } n := dbgen.Note noteCount, err := n.Where(n.DeletedAt.IsNull(), n.OriginId.Eq(1)).Count() if err != nil { webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil) if storage.HandleReconnectError(err) { log.Warn().Msg("Connection to db lost. Reconnect attempt started") } else { log.Error().Err(err).Msg("Failed to get total user count from db") } return } data := map[string]any{ "version": "2.1", "software": map[string]string{ "name": "linstrom", "version": shared.Version, "homepage": "https://git.mstar.dev/mstar/linstrom", "repository": "https://git.mstar.dev/mstar/linstrom", }, "protocols": []string{"activitypub"}, "services": map[string]any{ "inbound": []string{}, "outbound": []string{}, }, "openRegistrations": config.GlobalConfig.Admin.AllowRegistration, "usage": map[string]any{ "users": map[string]any{ "total": userCount, "activeHalfyear": nil, "activeMonth": nil, }, "localPosts": noteCount, "localComments": 0, }, "metadata": map[string]any{}, } webutils.SendJson(w, data) } func WellKnownNodeinfo(w http.ResponseWriter, r *http.Request) { data := map[string]any{ "links": []map[string]any{ { "rel": "http://nodeinfo.diaspora.software/ns/schema/2.1", "href": config.GlobalConfig.General.GetFullPublicUrl() + "/nodeinfo/2.1", }, }, } webutils.SendJson(w, data) }