This commit is contained in:
parent
98191fd098
commit
d272fa90b4
20 changed files with 574 additions and 27 deletions
15
web/public/api/activitypub/activitypub.go
Normal file
15
web/public/api/activitypub/activitypub.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func BuildActivitypubRouter() http.Handler {
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("/user/{id}", users)
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "in ap")
|
||||
})
|
||||
return router
|
||||
}
|
96
web/public/api/activitypub/user.go
Normal file
96
web/public/api/activitypub/user.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
)
|
||||
|
||||
var baseLdContext = []any{
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
}
|
||||
|
||||
func users(w http.ResponseWriter, r *http.Request) {
|
||||
type OutboundKey struct {
|
||||
Id string `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Pem string `json:"publicKeyPem"`
|
||||
}
|
||||
type Outbound struct {
|
||||
Context []any `json:"@context"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
PreferredUsername string `json:"preferredUsername"`
|
||||
Inbox string `json:"inbox"`
|
||||
// FIXME: Public key stuff is borken. Focus on fixing
|
||||
// PublicKey OutboundKey `json:"publicKey"`
|
||||
}
|
||||
userId := r.PathValue("id")
|
||||
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).First()
|
||||
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
|
||||
}
|
||||
// fmt.Println(x509.ParsePKCS1PublicKey(user.PublicKey))
|
||||
|
||||
apUrl := userIdToApUrl(user.ID)
|
||||
data := Outbound{
|
||||
Context: baseLdContext,
|
||||
Id: apUrl,
|
||||
Type: "Person",
|
||||
PreferredUsername: user.DisplayName,
|
||||
Inbox: apUrl + "/inbox",
|
||||
// PublicKey: OutboundKey{
|
||||
// Id: apUrl + "#main-key",
|
||||
// Owner: apUrl,
|
||||
// Pem: keyBytesToPem(user.PublicKey),
|
||||
// },
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(data)
|
||||
w.Header().Add("Content-Type", "application/activity+json")
|
||||
fmt.Fprint(w, string(encoded))
|
||||
}
|
||||
|
||||
/*
|
||||
Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then
|
||||
Fuck you.
|
||||
|
||||
If anyone wants to get this shit working *the propper way* with JsonLD, here's the
|
||||
original code
|
||||
var chain goap.BaseApChain = &goap.EmptyBaseObject{}
|
||||
chain = goap.AppendUDIdData(chain, apUrl)
|
||||
chain = goap.AppendUDTypeData(chain, "Person")
|
||||
chain = goap.AppendASPreferredNameData(chain, goap.ValueValue[string]{Value: user.DisplayName})
|
||||
chain = goap.AppendW3SecurityPublicKeyData(
|
||||
chain,
|
||||
apUrl+"#main-key",
|
||||
apUrl,
|
||||
keyBytesToPem(user.PublicKey),
|
||||
)
|
||||
|
||||
chainMap := chain.MarshalToMap()
|
||||
proc := ld.NewJsonLdProcessor()
|
||||
options := ld.NewJsonLdOptions("")
|
||||
tmp, tmperr := json.Marshal(chainMap)
|
||||
fmt.Println(string(tmp), tmperr)
|
||||
data, err := proc.Compact(chainMap, baseLdContext, options)
|
||||
// data, err := goap.Compact(chain, baseLdContext)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to marshal ap chain")
|
||||
webutils.ProblemDetailsStatusOnly(w, 500)
|
||||
return
|
||||
}
|
||||
*/
|
25
web/public/api/activitypub/util.go
Normal file
25
web/public/api/activitypub/util.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
)
|
||||
|
||||
func userIdToApUrl(id string) string {
|
||||
return fmt.Sprintf(
|
||||
"%s/api/ap/users/%s",
|
||||
config.GlobalConfig.General.GetFullPublicUrl(),
|
||||
id,
|
||||
)
|
||||
}
|
||||
|
||||
func keyBytesToPem(bytes []byte) string {
|
||||
block := pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Headers: nil,
|
||||
Bytes: bytes,
|
||||
}
|
||||
return string(pem.EncodeToMemory(&block))
|
||||
}
|
20
web/public/api/api.go
Normal file
20
web/public/api/api.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/web/public/api/activitypub"
|
||||
)
|
||||
|
||||
func BuildApiRouter() http.Handler {
|
||||
router := http.NewServeMux()
|
||||
router.Handle(
|
||||
"/activitypub/",
|
||||
http.StripPrefix("/activitypub", activitypub.BuildActivitypubRouter()),
|
||||
)
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "in api")
|
||||
})
|
||||
return router
|
||||
}
|
167
web/public/api/webfinger.go
Normal file
167
web/public/api/webfinger.go
Normal file
|
@ -0,0 +1,167 @@
|
|||
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/log"
|
||||
"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"`
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue