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" webshared "git.mstar.dev/mstar/linstrom/web/shared" ) 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 NodeInfoOverview(w http.ResponseWriter, r *http.Request) { data := webshared.NodeInfoOverview{ Links: []webshared.NodeInfoLink{ { Rel: "http://nodeinfo.diaspora.software/ns/schema/2.1", Href: config.GlobalConfig.General.GetFullPublicUrl() + "/nodeinfo/2.1", }, { Rel: "http://nodeinfo.diaspora.software/ns/schema/2.0", Href: config.GlobalConfig.General.GetFullPublicUrl() + "/nodeinfo/2.0", }, }, } webutils.SendJson(w, data) } func NodeInfo21(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 := webshared.NodeInfo2{ Version: "2.1", Software: webshared.NodeInfo2Software{ Name: "linstrom", Version: shared.Version, Homepage: other.IntoPointer("https://git.mstar.dev/mstar/linstrom"), Repository: other.IntoPointer("https://git.mstar.dev/mstar/linstrom"), }, Protocols: []string{"activitypub"}, Services: map[string][]string{ "inbound": {}, "outbound": {}, }, OpenRegistrations: config.GlobalConfig.Admin.AllowRegistration, Usage: webshared.NodeInfo2Usage{ Users: webshared.NodeInfo2UsageUsers{ Total: uint(userCount), ActiveHalfYear: nil, ActiveMonth: nil, }, LocalPosts: uint(noteCount), LocalComments: 0}, Metadata: map[string]any{}, } webutils.SendJson(w, data) } func NodeInfo20(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 := webshared.NodeInfo2{ Version: "2.1", Software: webshared.NodeInfo2Software{ Name: "linstrom", Version: shared.Version, Homepage: nil, Repository: nil, }, Protocols: []string{"activitypub"}, Services: map[string][]string{ "inbound": {}, "outbound": {}, }, OpenRegistrations: config.GlobalConfig.Admin.AllowRegistration, Usage: webshared.NodeInfo2Usage{ Users: webshared.NodeInfo2UsageUsers{ Total: uint(userCount), ActiveHalfYear: nil, ActiveMonth: nil, }, LocalPosts: uint(noteCount), LocalComments: 0}, Metadata: map[string]any{}, } webutils.SendJson(w, data) }