linstrom/web/public/api/webfinger.go

169 lines
4.7 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"
)
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 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)
}