Move stuff, keep working on authenticated fetch
All checks were successful
/ docker (push) Successful in 4m5s

This commit is contained in:
Melody Becker 2025-04-12 21:39:25 +02:00
parent f8b3a6ff06
commit e3a97170a9
No known key found for this signature in database
11 changed files with 81 additions and 39 deletions

View file

@ -1,4 +1,4 @@
package types package activitypub
var BaseLdContext = []any{ var BaseLdContext = []any{
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",

View file

@ -4,13 +4,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
"net/http"
"time" "time"
"git.mstar.dev/mstar/goutils/other"
"git.mstar.dev/mstar/goutils/sliceutils" "git.mstar.dev/mstar/goutils/sliceutils"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
apshared "git.mstar.dev/mstar/linstrom/activitypub/shared"
"git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/config"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen" "git.mstar.dev/mstar/linstrom/storage-new/dbgen"
webshared "git.mstar.dev/mstar/linstrom/web/shared" webshared "git.mstar.dev/mstar/linstrom/web/shared"
@ -47,18 +46,18 @@ func ImportRemoteAccount(targetName string) (string, error) {
RestrictedFollow *bool `json:"manuallyApprovesFollowers"` RestrictedFollow *bool `json:"manuallyApprovesFollowers"`
} }
// Get the target user's link first // Get the target user's link first
webfinger, err := apshared.GetAccountWebfinger(targetName) webfinger, err := GetAccountWebfinger(targetName)
if err != nil { if err != nil {
return "", err return "", other.Error("activitypub", "webfinger request failed", err)
} }
selfLinks := sliceutils.Filter(webfinger.Links, func(t apshared.LinkData) bool { selfLinks := sliceutils.Filter(webfinger.Links, func(t LinkData) bool {
return t.Relation == "self" return t.Relation == "self"
}) })
if len(selfLinks) == 0 { if len(selfLinks) == 0 {
return "", errors.New("No self link") return "", errors.New("No self link")
} }
APLink := selfLinks[0] APLink := selfLinks[0]
req, err := http.NewRequest("GET", *APLink.Href, nil) req, err := webshared.NewRequest("GET", *APLink.Href, nil)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -86,12 +85,16 @@ func ImportRemoteAccount(targetName string) (string, error) {
return "", err return "", err
} }
defer response.Body.Close() defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
log.Debug().
Int("status", response.StatusCode).
Bytes("body", body).
Any("headers", response.Header).
Msg("Response information")
if response.StatusCode != 200 { if response.StatusCode != 200 {
return "", errors.New("Bad status") return "", errors.New("Bad status")
} }
var data InboundUser var data InboundUser
body, _ := io.ReadAll(response.Body)
log.Info().Bytes("body", body).Msg("Body from request")
err = json.Unmarshal(body, &data) err = json.Unmarshal(body, &data)
if err != nil { if err != nil {
return "", err return "", err

View file

@ -1,4 +1,4 @@
package apshared package activitypub
import "strings" import "strings"

View file

@ -1,10 +1,9 @@
package apshared package activitypub
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
"net/http"
webshared "git.mstar.dev/mstar/linstrom/web/shared" webshared "git.mstar.dev/mstar/linstrom/web/shared"
) )
@ -44,7 +43,7 @@ func GetAccountWebfinger(fullHandle string) (*WebfingerData, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
webfingerRequest, err := http.NewRequest( webfingerRequest, err := webshared.NewRequest(
// Webfinger requests are GET // Webfinger requests are GET
"GET", "GET",
// The webfinger url is located at <domain>/.well-known/webfinger // The webfinger url is located at <domain>/.well-known/webfinger

View file

@ -207,7 +207,7 @@ var defaultConfig Config = Config{
// Example: "git.mstar.dev" (with subdomain = "git" and domain = "mstar.dev") // Example: "git.mstar.dev" (with subdomain = "git" and domain = "mstar.dev")
func (gc *ConfigGeneral) GetFullDomain() string { func (gc *ConfigGeneral) GetFullDomain() string {
if gc.Subdomain != nil { if gc.Subdomain != nil {
return *gc.Subdomain + gc.Domain return *gc.Subdomain + "." + gc.Domain
} }
return gc.Domain return gc.Domain
} }

View file

@ -18,6 +18,7 @@ const (
ServerSoftwareWafrn = ServerSoftwareType("Wafrn") ServerSoftwareWafrn = ServerSoftwareType("Wafrn")
// Linstrom with no known forks // Linstrom with no known forks
ServerSoftwareLinstrom = ServerSoftwareType("Linstrom") ServerSoftwareLinstrom = ServerSoftwareType("Linstrom")
ServerSoftwareUnknown = ServerSoftwareType("Unknown")
) )
// A list of all known server software systems // A list of all known server software systems
@ -27,6 +28,7 @@ var AllServerSoftwareTypes = []ServerSoftwareType{
ServerSoftwarePlemora, ServerSoftwarePlemora,
ServerSoftwareWafrn, ServerSoftwareWafrn,
ServerSoftwareLinstrom, ServerSoftwareLinstrom,
ServerSoftwareUnknown,
} }
func (r *ServerSoftwareType) Value() (driver.Value, error) { func (r *ServerSoftwareType) Value() (driver.Value, error) {

View file

@ -8,7 +8,7 @@ import (
"strings" "strings"
"time" "time"
ap "git.mstar.dev/mstar/linstrom/activitypub/shared" "git.mstar.dev/mstar/linstrom/activitypub"
"git.mstar.dev/mstar/linstrom/shared" "git.mstar.dev/mstar/linstrom/shared"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
"github.com/google/uuid" "github.com/google/uuid"
@ -121,7 +121,7 @@ func (s *Storage) FindAccountByFullHandle(handle string) (*Account, error) {
// Failed to find in cache, go the slow route of hitting the db // Failed to find in cache, go the slow route of hitting the db
log.Debug().Str("account-handle", handle).Msg("Didn't hit account in cache, going to db") log.Debug().Str("account-handle", handle).Msg("Didn't hit account in cache, going to db")
name, server, err := ap.SplitFullHandle(handle) name, server, err := activitypub.SplitFullHandle(handle)
if err != nil { if err != nil {
log.Warn().Err(err).Str("account-handle", handle).Msg("Failed to split up account handle") log.Warn().Err(err).Str("account-handle", handle).Msg("Failed to split up account handle")
return nil, err return nil, err

View file

@ -12,7 +12,7 @@ import (
"git.mstar.dev/mstar/goutils/sliceutils" "git.mstar.dev/mstar/goutils/sliceutils"
"github.com/rs/zerolog/hlog" "github.com/rs/zerolog/hlog"
"git.mstar.dev/mstar/linstrom/activitypub/shared/types" "git.mstar.dev/mstar/linstrom/activitypub"
"git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/config"
"git.mstar.dev/mstar/linstrom/storage-new" "git.mstar.dev/mstar/linstrom/storage-new"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen" "git.mstar.dev/mstar/linstrom/storage-new/dbgen"
@ -53,6 +53,7 @@ func users(w http.ResponseWriter, r *http.Request) {
} }
log := hlog.FromRequest(r) log := hlog.FromRequest(r)
userId := r.PathValue("id") userId := r.PathValue("id")
log.Debug().Any("headers", r.Header).Msg("request headers")
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)). user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).
Preload(dbgen.User.Icon).Preload(dbgen.User.Banner). Preload(dbgen.User.Icon).Preload(dbgen.User.Banner).
Preload(dbgen.User.BeingTypes). Preload(dbgen.User.BeingTypes).
@ -75,7 +76,7 @@ func users(w http.ResponseWriter, r *http.Request) {
keyBytes = keyBytesToPem(user.PublicKeyRsa) keyBytes = keyBytesToPem(user.PublicKeyRsa)
} }
data := Outbound{ data := Outbound{
Context: types.BaseLdContext, Context: activitypub.BaseLdContext,
Id: apUrl, Id: apUrl,
Type: "Person", Type: "Person",
PreferredUsername: user.Username, PreferredUsername: user.Username,
@ -136,7 +137,12 @@ func userInbox(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r) log := hlog.FromRequest(r)
userId := r.PathValue("id") userId := r.PathValue("id")
data, err := io.ReadAll(r.Body) data, err := io.ReadAll(r.Body)
log.Info().Err(err).Str("userId", userId).Bytes("body", data).Msg("Inbox message") log.Info().
Err(err).
Str("userId", userId).
Bytes("body", data).
Any("headers", r.Header).
Msg("Inbox message")
} }
/* /*

View file

@ -9,7 +9,7 @@ import (
func userIdToApUrl(id string) string { func userIdToApUrl(id string) string {
return fmt.Sprintf( return fmt.Sprintf(
"%s/api/ap/users/%s", "%s/api/activitypub/user/%s",
config.GlobalConfig.General.GetFullPublicUrl(), config.GlobalConfig.General.GetFullPublicUrl(),
id, id,
) )

View file

@ -2,17 +2,20 @@ package webshared
import ( import (
"crypto/sha256" "crypto/sha256"
"io"
"net/http" "net/http"
"strings"
"time" "time"
"git.mstar.dev/mstar/goutils/maputils" "git.mstar.dev/mstar/goutils/maputils"
"github.com/rs/zerolog/log"
"git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/config"
) )
// No init needed, zero value is good // No init needed, zero value is good
var RequestClient http.Client var RequestClient = http.Client{}
const xRandomHeader = "X-Auth-Random" const xRandomHeader = "X-Auth-Random"
@ -36,35 +39,40 @@ func SignRequest(r *http.Request, keyId string, privateKeyBytes, postBody []byte
host = hostString host = hostString
} else { } else {
host = config.GlobalConfig.General.GetFullDomain() host = config.GlobalConfig.General.GetFullDomain()
headers.Set("Date", host) headers.Set("Host", host)
} }
applyBodyHash(headers, postBody) applyBodyHash(headers, postBody)
mappedHeaders := maputils.MapSameKeys(headers, func(k string, v []string) string { mappedHeaders := maputils.MapNewKeys(headers, func(k string, v []string) (string, string) {
if len(v) > 0 { if len(v) > 0 {
return v[0] return strings.ToLower(k), v[0]
} else { } else {
return "" return strings.ToLower(k), ""
} }
}) })
var signedString string var signedString string
var usedHeaders []string
if config.GlobalConfig.Experimental.UseEd25519Keys { if config.GlobalConfig.Experimental.UseEd25519Keys {
tmp, err := CreateSignatureED(method, r.URL.RawPath, mappedHeaders, privateKeyBytes) tmp, tmp2, err := CreateSignatureED(method, r.URL.Path, mappedHeaders, privateKeyBytes)
if err != nil { if err != nil {
return err return err
} }
signedString = tmp signedString = tmp
usedHeaders = tmp2
} else { } else {
tmp, err := CreateSignatureRSA(method, r.URL.RawPath, mappedHeaders, privateKeyBytes) tmp, tmp2, err := CreateSignatureRSA(method, r.URL.Path, mappedHeaders, privateKeyBytes)
if err != nil { if err != nil {
return err return err
} }
signedString = tmp signedString = tmp
usedHeaders = tmp2
} }
log.Debug().Str("string-to-sign", signedString).Any("headers", mappedHeaders).Send()
signature := CreateSignatureHeaderContent( signature := CreateSignatureHeaderContent(
keyId, keyId,
signedString, signedString,
maputils.KeysFromMap(mappedHeaders)..., usedHeaders...,
) )
log.Debug().Str("signature-header", signature).Send()
headers.Set("Signature", signature) headers.Set("Signature", signature)
return nil return nil
} }
@ -77,3 +85,17 @@ func applyBodyHash(headers http.Header, body []byte) error {
headers.Set("Digest", string(hash[:])) headers.Set("Digest", string(hash[:]))
return nil return nil
} }
func NewRequest(method string, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Add(
"User-Agent",
"Linstrom v0.0.0-pre-alpha ("+config.GlobalConfig.General.GetFullDomain()+")",
)
req.Header.Add("Date", time.Now().Format(time.RFC1123))
req.Header.Add("Host", config.GlobalConfig.General.GetFullDomain())
return req, nil
}

View file

@ -4,6 +4,9 @@ import (
"encoding/base64" "encoding/base64"
"strings" "strings"
"github.com/rs/zerolog/log"
"git.mstar.dev/mstar/linstrom/config"
"git.mstar.dev/mstar/linstrom/shared" "git.mstar.dev/mstar/linstrom/shared"
) )
@ -15,10 +18,10 @@ func CreateSignatureRSA(
target string, target string,
headers map[string]string, headers map[string]string,
privateKeyBytes []byte, privateKeyBytes []byte,
) (string, error) { ) (string, []string, error) {
message := genPreSignatureString(method, target, headers) message, usedHeaders := genPreSignatureString(method, target, headers)
signed, err := shared.Sign(message, privateKeyBytes, true) signed, err := shared.Sign(message, privateKeyBytes, true)
return base64.StdEncoding.EncodeToString(signed), err return base64.StdEncoding.EncodeToString(signed), usedHeaders, err
} }
// Generate the signed string of the headers, method and target // Generate the signed string of the headers, method and target
@ -29,24 +32,29 @@ func CreateSignatureED(
target string, target string,
headers map[string]string, headers map[string]string,
privateKeyBytes []byte, privateKeyBytes []byte,
) (string, error) { ) (string, []string, error) {
message := genPreSignatureString(method, target, headers) message, usedHeaders := genPreSignatureString(method, target, headers)
signed, err := shared.Sign(message, privateKeyBytes, false) signed, err := shared.Sign(message, privateKeyBytes, false)
if err != nil { if err != nil {
return "", err return "", nil, err
} }
return base64.StdEncoding.EncodeToString(signed), nil return base64.StdEncoding.EncodeToString(signed), usedHeaders, nil
} }
func genPreSignatureString(method, target string, headers map[string]string) string { func genPreSignatureString(method, target string, headers map[string]string) (string, []string) {
dataBuilder := strings.Builder{} dataBuilder := strings.Builder{}
dataBuilder.WriteString("(request-target) ") dataBuilder.WriteString("(request-target): ")
dataBuilder.WriteString(strings.ToLower(method) + " ") dataBuilder.WriteString(strings.ToLower(method) + " ")
dataBuilder.WriteString(target + "\n") dataBuilder.WriteString(target + "\n")
dataBuilder.WriteString("algorithm: rsa-sha256\n")
usedHeaders := []string{"(request-target)", "algorithm"}
for k, v := range headers { for k, v := range headers {
dataBuilder.WriteString(k + ": " + v + "\n") dataBuilder.WriteString(k + ": " + v + "\n")
usedHeaders = append(usedHeaders, k)
} }
return dataBuilder.String() tmp := dataBuilder.String()
log.Debug().Str("Raw signature string", tmp).Send()
return tmp, usedHeaders
} }
// Generate the content of the "Signature" header based on // Generate the content of the "Signature" header based on
@ -55,6 +63,8 @@ func genPreSignatureString(method, target string, headers map[string]string) str
func CreateSignatureHeaderContent(userId string, hash string, headerNames ...string) string { func CreateSignatureHeaderContent(userId string, hash string, headerNames ...string) string {
builder := strings.Builder{} builder := strings.Builder{}
builder.WriteString("keyId=\"") builder.WriteString("keyId=\"")
builder.WriteString(config.GlobalConfig.General.GetFullPublicUrl())
builder.WriteString("/api/activitypub/user/")
builder.WriteString(userId) builder.WriteString(userId)
builder.WriteString("\",headers=\"") builder.WriteString("\",headers=\"")
for i, header := range headerNames { for i, header := range headerNames {
@ -63,7 +73,7 @@ func CreateSignatureHeaderContent(userId string, hash string, headerNames ...str
builder.WriteRune(' ') builder.WriteRune(' ')
} }
} }
builder.WriteString("\",signature=\"") builder.WriteString("\",algorithm=\"rsa-sha256\",signature=\"")
builder.WriteString(hash) builder.WriteString(hash)
builder.WriteRune('"') builder.WriteRune('"')