linstrom/web/public/api/webfinger.go

225 lines
6.5 KiB
Go

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<username>[\w\.-]+)@(?<domain>[\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:<username>@<domain>`,
),
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)
}