Compare commits

...

7 commits

Author SHA1 Message Date
03ca524c99
Cleanup, store likes
Some checks failed
/ docker (push) Failing after 3m1s
- Clean up some logging
- Properly store likes in db
2025-05-06 22:18:28 +02:00
c7f875a9c5
Add some samples for like and undo 2025-05-06 22:18:09 +02:00
30e2702df6
Log config on boot in trace mode 2025-05-06 14:35:50 +02:00
e182949a8d
Remove id autogeneration from db models
Remove automatic uuid v4 ID generation from the models and replace it
with `shared.NewId()`, which generates an Id depending on the config
setting
2025-05-06 14:34:32 +02:00
412a8be600
Merge branch 'main' of git.mstar.dev:mstar/linstrom 2025-05-05 17:56:01 +02:00
796e393985
Update testing conf 2025-05-05 17:55:27 +02:00
6df7a354f0
Make debug created users verified by default 2025-05-05 17:55:17 +02:00
24 changed files with 343 additions and 67 deletions

View file

@ -421,11 +421,6 @@ func ImportRemoteAccountByAPUrl(apUrl string) (*models.User, error) {
} }
defer response.Body.Close() defer response.Body.Close()
body, _ := io.ReadAll(response.Body) 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 nil, fmt.Errorf("activitypub: invalid status code: %v", response.StatusCode) return nil, fmt.Errorf("activitypub: invalid status code: %v", response.StatusCode)
} }
@ -452,9 +447,20 @@ func ImportRemoteAccountByAPUrl(apUrl string) (*models.User, error) {
Preload(dbgen.User.InfoFields). Preload(dbgen.User.InfoFields).
Preload(dbgen.User.BeingTypes). Preload(dbgen.User.BeingTypes).
Preload(dbgen.User.Roles). Preload(dbgen.User.Roles).
FirstOrCreate() First()
if err != nil { switch err {
return nil, other.Error("activitypub", "failed to find or create user in db", err) case nil:
case gorm.ErrRecordNotFound:
user = &models.User{
ID: shared.NewId(),
Username: data.PreferredUsername + "@" + targetUrl.Host,
ServerId: hostId,
}
if err = dbgen.User.Select(dbgen.User.ID, dbgen.User.Username, dbgen.User.ServerId).Create(user); err != nil {
return nil, other.Error("activitypub", "failed to create user", err)
}
default:
return nil, other.Error("activitypub", "failed to find user", err)
} }
user.Verified = true user.Verified = true
user.FinishedRegistration = true user.FinishedRegistration = true

View file

@ -3,6 +3,7 @@ package config
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"git.mstar.dev/mstar/goutils/other" "git.mstar.dev/mstar/goutils/other"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
@ -131,12 +132,12 @@ type ConfigExperimental struct {
// if the other server also requires authorized fetch for the server actor // if the other server also requires authorized fetch for the server actor
// Changing this setting is not expected to cause permanent problems // Changing this setting is not expected to cause permanent problems
AuthFetchForServerActor bool `toml:"auth_fetch_for_server_actor"` AuthFetchForServerActor bool `toml:"auth_fetch_for_server_actor"`
// Use cuid2 for generation of new IDs that are expected to be used via Activitypub // Set the provider for ID generation.
// They are shorter than the main method used (uuid v4) but should still provide enough // Default is "xid".
// uniqueness such that collisions are not to be expected. // Options are "uuid", "cuid" and "xid"
// Changing this option will only affect new ID generations, not update existing ones // Changing this option will only affect new ID generations, not update existing ones
// As of now, even that doesn't work due to implementation details // As of now, even that doesn't work due to implementation details
UseCuid2Ids bool `toml:"use_cuid2_ids"` IdGenerator string `toml:"id_generator"`
} }
type Config struct { type Config struct {
@ -220,6 +221,7 @@ var defaultConfig Config = Config{
Experimental: ConfigExperimental{ Experimental: ConfigExperimental{
UseEd25519Keys: false, UseEd25519Keys: false,
AuthFetchForServerActor: false, AuthFetchForServerActor: false,
IdGenerator: "xid",
}, },
} }
@ -322,6 +324,7 @@ func ReadAndWriteToGlobal(fileName string) error {
log.Error().Err(err).Bytes("config-data-raw", data).Msg("Failed to unmarshal config file") log.Error().Err(err).Bytes("config-data-raw", data).Msg("Failed to unmarshal config file")
return err return err
} }
config.Experimental.IdGenerator = strings.ToLower(config.Experimental.IdGenerator)
GlobalConfig = config GlobalConfig = config
log.Info().Str("config-file", fileName).Msg("Read and applied config file") log.Info().Str("config-file", fileName).Msg("Read and applied config file")
return nil return nil

View file

@ -65,6 +65,7 @@ func main() {
Str("config-file", *shared.FlagConfigFile). Str("config-file", *shared.FlagConfigFile).
Msg("Failed to read config and couldn't write default") Msg("Failed to read config and couldn't write default")
} }
log.Trace().Any("config", config.GlobalConfig).Msg("Full config")
// Request to only check config // Request to only check config
if *shared.FlagConfigOnly { if *shared.FlagConfigOnly {

View file

@ -0,0 +1,12 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://activitypub.academy/users/eufrilia_vulbos#likes/1103/undo",
"type": "Undo",
"actor": "https://activitypub.academy/users/eufrilia_vulbos",
"object": {
"id": "https://activitypub.academy/users/eufrilia_vulbos#likes/1103",
"type": "Like",
"actor": "https://activitypub.academy/users/eufrilia_vulbos",
"object": "https://e1cda93c47d7aa.lhr.life/api/activitypub/note/d0d1uqitest2bchhadi0"
}
}

69
samples/mkLike.json Normal file
View file

@ -0,0 +1,69 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"Key": "sec:Key",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"Hashtag": "as:Hashtag",
"quoteUrl": "as:quoteUrl",
"fedibird": "http://fedibird.com/ns#",
"quoteUri": "fedibird:quoteUri",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji",
"featured": "toot:featured",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable",
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"misskey": "https://misskey-hub.net/ns#",
"_misskey_content": "misskey:_misskey_content",
"_misskey_quote": "misskey:_misskey_quote",
"_misskey_reaction": "misskey:_misskey_reaction",
"_misskey_votes": "misskey:_misskey_votes",
"_misskey_summary": "misskey:_misskey_summary",
"_misskey_followedMessage": "misskey:_misskey_followedMessage",
"_misskey_requireSigninToViewContents": "misskey:_misskey_requireSigninToViewContents",
"_misskey_makeNotesFollowersOnlyBefore": "misskey:_misskey_makeNotesFollowersOnlyBefore",
"_misskey_makeNotesHiddenBefore": "misskey:_misskey_makeNotesHiddenBefore",
"_misskey_license": "misskey:_misskey_license",
"freeText": {
"@id": "misskey:freeText",
"@type": "schema:text"
},
"isCat": "misskey:isCat",
"firefish": "https://joinfirefish.org/ns#",
"speakAsCat": "firefish:speakAsCat",
"sharkey": "https://joinsharkey.org/ns#",
"hideOnlineStatus": "sharkey:hideOnlineStatus",
"backgroundUrl": "sharkey:backgroundUrl",
"listenbrainz": "sharkey:listenbrainz",
"enableRss": "sharkey:enableRss",
"vcard": "http://www.w3.org/2006/vcard/ns#"
}
],
"type": "Like",
"id": "https://mk.absturztau.be/likes/a7g8thvezsng01os",
"actor": "https://mk.absturztau.be/users/a1xy7910yc8401z8",
"object": "https://fa1c08293da1646a0971c124f143d7f8.serveo.net/api/activitypub/note/d0d1uqitest2bchhadi0",
"content": ":celeste_hearts_progress_pride:",
"_misskey_reaction": ":celeste_hearts_progress_pride:",
"tag": [
{
"id": "https://mk.absturztau.be/emojis/celeste_hearts_progress_pride",
"type": "Emoji",
"name": ":celeste_hearts_progress_pride:",
"updated": "2024-12-03T12:12:41.676Z",
"icon": {
"type": "Image",
"mediaType": "image/gif",
"url": "https://misskey-taube.s3.eu-central-1.wasabisys.com/files/6823d4ba-110f-4186-abb4-d14cb7aedd76.gif"
},
"_misskey_license": {
"freeText": null
}
}
]
}

View file

@ -3,15 +3,21 @@ package shared
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nrednav/cuid2" "github.com/nrednav/cuid2"
"github.com/rs/xid"
"git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/config"
) )
// Generate a new string ID // Generate a new string ID
func NewId() string { func NewId() string {
if config.GlobalConfig.Experimental.UseCuid2Ids { switch config.GlobalConfig.Experimental.IdGenerator {
return cuid2.Generate() case "uuid":
} else {
return uuid.NewString() return uuid.NewString()
case "cuid":
return cuid2.Generate()
case "xid":
return xid.New().String()
default:
return xid.New().String()
} }
} }

View file

@ -2,7 +2,7 @@ package models
type Activity struct { type Activity struct {
Id string `gorm:"primarykey"` Id string `gorm:"primarykey"`
Type string `gorm:"type:activitystreams_activity_type"` Type string // `gorm:"type:activitystreams_activity_type"`
ObjectId string ObjectId string
ObjectType uint32 // Target type: ActivitystreamsActivityTargetType ObjectType uint32 // Target type: ActivitystreamsActivityTargetType
} }

View file

@ -12,7 +12,7 @@ import (
// Instead, they are either stored on the remote server they originated from // Instead, they are either stored on the remote server they originated from
// or an s3 bucket if uploaded to Linstrom. // or an s3 bucket if uploaded to Linstrom.
type MediaMetadata struct { type MediaMetadata struct {
ID string `gorm:"primarykey;default:gen_random_uuid()"` // The unique ID of this media file ID string `gorm:"primarykey"` // The unique ID of this media file
CreatedAt time.Time // When this entry was created CreatedAt time.Time // When this entry was created
UpdatedAt time.Time // When this entry was last updated UpdatedAt time.Time // When this entry was last updated
// When this entry was deleted (for soft deletions) // When this entry was deleted (for soft deletions)

View file

@ -9,7 +9,7 @@ import (
// A note describes some user generated text content. // A note describes some user generated text content.
type Note struct { type Note struct {
ID string `gorm:"primarykey;default:gen_random_uuid()"` // Make ID a string (uuid) for other implementations ID string `gorm:"primarykey"` // Make ID a string for other implementations
CreatedAt time.Time // When this entry was created CreatedAt time.Time // When this entry was created
UpdatedAt time.Time // When this entry was last updated UpdatedAt time.Time // When this entry was last updated
// When this entry was deleted (for soft deletions) // When this entry was deleted (for soft deletions)

View file

@ -1,6 +1,10 @@
package models package models
import "gorm.io/gorm" import (
"database/sql"
"gorm.io/gorm"
)
// A Reaction is a user liking a note using an emote // A Reaction is a user liking a note using an emote
type Reaction struct { type Reaction struct {
@ -9,6 +13,6 @@ type Reaction struct {
NoteId string NoteId string
Reactor User Reactor User
ReactorId string ReactorId string
Emote Emote Emote *Emote // Emote is optional. If not set, use the default emote of the server
EmoteId uint EmoteId sql.NullInt64
} }

View file

@ -14,7 +14,7 @@ type AccessToken struct {
User User // The account the token belongs to User User // The account the token belongs to
UserId string UserId string
// The token itself is a uuid value // The token itself is a uuid value
Token string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"` Token string `gorm:"primarykey"`
Name string // Token name will be empty if autogenerated with sucessful login Name string // Token name will be empty if autogenerated with sucessful login
// Every token expires, even if set to "not expire". If set to "not expire", it just expires // Every token expires, even if set to "not expire". If set to "not expire", it just expires
// at a point in the future this server should never reach // at a point in the future this server should never reach

View file

@ -10,7 +10,7 @@ type LoginProcessToken struct {
ID uint64 `gorm:"primarykey"` ID uint64 `gorm:"primarykey"`
User User User User
UserId string `gorm:"unique"` UserId string `gorm:"unique"`
Token string `gorm:"type:uuid;default:gen_random_uuid()"` Token string
Name string Name string
ExpiresAt time.Time ExpiresAt time.Time
} }

View file

@ -27,7 +27,7 @@ type User struct {
// Same also applies for other types that use a UUID as primary key // Same also applies for other types that use a UUID as primary key
// TODO: Change this to generate via cuid or uuid, depending on config // TODO: Change this to generate via cuid or uuid, depending on config
// Or remove autogeneration alltogether // Or remove autogeneration alltogether
ID string `gorm:"primarykey;default:gen_random_uuid()"` ID string `gorm:"primarykey"`
// Username of the user (eg "max" if the full username is @max@example.com) // Username of the user (eg "max" if the full username is @max@example.com)
// Assume unchangable (once set by a user) to be kind to other implementations // Assume unchangable (once set by a user) to be kind to other implementations
// Would be an easy avenue to fuck with them though // Would be an easy avenue to fuck with them though

View file

@ -129,6 +129,7 @@ func insertUser(
) )
} }
user := models.User{ user := models.User{
ID: shared.NewId(),
Username: shared.ServerActorName, Username: shared.ServerActorName,
Server: *server, Server: *server,
ServerId: server.ID, ServerId: server.ID,

View file

@ -1,7 +1,7 @@
[general] [general]
protocol = "https" protocol = "https"
domain = "lhr.life" domain = "serveo.net"
subdomain = "e8adf969423370" subdomain = "b2f4e7c5596220d4c4957b24f6954220"
private_port = 8080 private_port = 8080
public_port = 443 public_port = 443

View file

@ -55,6 +55,7 @@ func postAs(w http.ResponseWriter, r *http.Request) {
} }
n := dbgen.Note n := dbgen.Note
note := models.Note{ note := models.Note{
ID: shared.NewId(),
Creator: *user, Creator: *user,
CreatorId: user.ID, CreatorId: user.ID,
RawContent: data.Content, RawContent: data.Content,
@ -66,6 +67,7 @@ func postAs(w http.ResponseWriter, r *http.Request) {
OriginId: 1, OriginId: 1,
} }
err = n.Select( err = n.Select(
n.ID,
n.CreatorId, n.CreatorId,
n.RawContent, n.RawContent,
n.Remote, n.Remote,

View file

@ -129,6 +129,7 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
u := dbgen.User u := dbgen.User
query := u.Select( query := u.Select(
u.ID,
u.Username, u.Username,
u.DisplayName, u.DisplayName,
u.Description, u.Description,
@ -139,6 +140,8 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
u.PrivateKeyRsa, u.PrivateKeyRsa,
u.PublicKeyRsa, u.PublicKeyRsa,
u.PasskeyId, u.PasskeyId,
u.Verified,
u.FinishedRegistration,
) )
if data.Birthday != nil { if data.Birthday != nil {
query = query.Select(u.Birthday) query = query.Select(u.Birthday)
@ -147,16 +150,19 @@ func createLocalUser(w http.ResponseWriter, r *http.Request) {
query = query.Select(u.Location) query = query.Select(u.Location)
} }
user := models.User{ user := models.User{
Username: data.Username, ID: shared.NewId(),
DisplayName: data.Displayname, Username: data.Username,
Description: data.Description, DisplayName: data.Displayname,
IsBot: data.IsBot, Description: data.Description,
ServerId: 1, // Hardcoded, Self is always first ID IsBot: data.IsBot,
PublicKeyRsa: publicKeyRsaBytes, ServerId: 1, // Hardcoded, Self is always first ID
PublicKeyEd: publicKeyEdBytes, PublicKeyRsa: publicKeyRsaBytes,
PrivateKeyRsa: privateKeyRsaBytes, PublicKeyEd: publicKeyEdBytes,
PrivateKeyEd: privateKeyEdBytes, PrivateKeyRsa: privateKeyRsaBytes,
PasskeyId: pkeyId, PrivateKeyEd: privateKeyEdBytes,
PasskeyId: pkeyId,
Verified: true,
FinishedRegistration: true,
} }
if data.Birthday != nil { if data.Birthday != nil {
user.Birthday = sql.NullString{Valid: true, String: data.Birthday.Format("2006-Jan-02")} user.Birthday = sql.NullString{Valid: true, String: data.Birthday.Format("2006-Jan-02")}

View file

@ -1,7 +1,6 @@
package activitypub package activitypub
import ( import (
"fmt"
"net/http" "net/http"
) )
@ -15,8 +14,5 @@ func BuildActivitypubRouter() http.Handler {
router.HandleFunc("/activity/reject/{id}", activityReject) router.HandleFunc("/activity/reject/{id}", activityReject)
router.HandleFunc("/activity/update/{id}", activityUpdate) router.HandleFunc("/activity/update/{id}", activityUpdate)
router.HandleFunc("/note/{id}", objectNote) router.HandleFunc("/note/{id}", objectNote)
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprint(w, "in ap")
})
return router return router
} }

View file

@ -0,0 +1,193 @@
package activitypub
import (
"database/sql"
"encoding/json"
"fmt"
"io"
"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/activitypub"
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
"git.mstar.dev/mstar/linstrom/storage-new/models"
)
var objectIdRegex = regexp.MustCompile(
`https?://.+/api/activitypub/[a-z]+(?:/[a-z]+)?/([a-zA-Z0-9\-]+)`,
)
func userInbox(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
userId := r.PathValue("id")
body, err := io.ReadAll(r.Body)
log.Info().
Err(err).
Str("userId", userId).
Bytes("body", body).
Any("headers", r.Header).
Msg("Inbox message")
data := map[string]any{}
err = json.Unmarshal(body, &data)
if err != nil {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer("Body to inbox needs to be json"),
nil,
)
return
}
if _, ok := data["@context"]; !ok {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer("Request data needs to contain context"),
nil,
)
return
}
objectType, ok := data["type"].(string)
if !ok {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer(`Request data needs to contain a field "type" with a string value`),
nil,
)
return
}
_, ok = data["id"].(string)
if !ok {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer(`Request data needs to contain a field "id" with a string value`),
nil,
)
return
}
switch objectType {
case "Like":
handleLike(w, r, data)
default:
webutils.ProblemDetailsStatusOnly(w, 500)
}
}
func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
log := hlog.FromRequest(r)
activityId := object["id"].(string)
likerUrl, ok := object["actor"].(string)
if !ok {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
nil,
)
return
}
// TODO: Account for case where object is embedded in like
targetUrl, ok := object["object"].(string)
if !ok {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer(`Request data needs to contain a field "object" with a string value`),
nil,
)
return
}
targetIdMatches := objectIdRegex.FindStringSubmatch(targetUrl)
if len(targetIdMatches) != 2 {
log.Error().
Strs("match-results", targetIdMatches).
Str("url", targetUrl).
Msg("Url didn't match id extractor regex")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
targetId := targetIdMatches[1]
// Assume likes can only happen on notes for now
// Thus check if a note with that Id exists at all
note, err := dbgen.Note.Where(dbgen.Note.ID.Eq(targetId)).First()
switch err {
case nil:
case gorm.ErrRecordNotFound:
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer("There is no note with the target id"),
nil,
)
return
default:
log.Error().Err(err).Str("note-id", targetId).Msg("Failed to get note from db")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
// Import liker after verifying that target note is correct
liker, err := activitypub.ImportRemoteAccountByAPUrl(likerUrl)
if err != nil {
log.Error().
Err(err).
Str("liker-url", likerUrl).
Msg("Failed to import liking remote account")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
reaction := models.Reaction{
Note: *note,
NoteId: note.ID,
Reactor: *liker,
ReactorId: liker.ID,
Emote: nil,
EmoteId: sql.NullInt64{Valid: false},
}
tx := dbgen.Q.Begin()
err = tx.Reaction.Create(&reaction)
if err != nil {
_ = tx.Rollback()
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
}
// TODO: Create corresponding activity too
activity := models.Activity{
Id: activityId,
Type: string(models.ActivityLike),
ObjectId: fmt.Sprint(reaction.ID),
ObjectType: uint32(models.ActivitystreamsActivityTargetReaction),
}
err = tx.Activity.Create(&activity)
if err != nil {
_ = tx.Rollback()
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
}
err = tx.Commit()
if err != nil {
log.Error().Err(err).Msg("Failed to commit reaction transaction to db")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
}
}

View file

@ -3,7 +3,6 @@ package activitypub
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"time" "time"
@ -140,18 +139,6 @@ func users(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, string(encoded)) fmt.Fprint(w, string(encoded))
} }
func userInbox(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
userId := r.PathValue("id")
data, err := io.ReadAll(r.Body)
log.Info().
Err(err).
Str("userId", userId).
Bytes("body", data).
Any("headers", r.Header).
Msg("Inbox message")
}
/* /*
Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then Fine. You win JsonLD. I can't get you to work properly. I'll just treat you like normal json then
Fuck you. Fuck you.

View file

@ -70,15 +70,12 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
} }
// Not an always open path, check methods // Not an always open path, check methods
if r.Method == "GET" && !forGet { if r.Method == "GET" && !forGet {
log.Debug().Msg("Get request to AP resources don't need signature")
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
return return
} else if !forGet && !forNonGet { } else if !forGet && !forNonGet {
log.Info().Msg("Requests to AP resources don't need signature")
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
return return
} }
log.Debug().Msg("Need signature for AP request")
rawDate := r.Header.Get("Date") rawDate := r.Header.Get("Date")
date, err := http.ParseTime(rawDate) date, err := http.ParseTime(rawDate)
if err != nil { if err != nil {
@ -105,7 +102,8 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
} }
signatureHeader := r.Header.Get("Signature") signatureHeader := r.Header.Get("Signature")
if signatureHeader == "" { if signatureHeader == "" {
log.Info().Msg("Received AP request without signature header where one is required") log.Debug().
Msg("Received AP request without signature header where one is required")
webutils.ProblemDetails( webutils.ProblemDetails(
w, w,
http.StatusUnauthorized, http.StatusUnauthorized,
@ -121,7 +119,7 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
Msg("Signature header of inbound AP request") Msg("Signature header of inbound AP request")
match := signatureRegex.FindStringSubmatch(signatureHeader) match := signatureRegex.FindStringSubmatch(signatureHeader)
if len(match) <= 1 { if len(match) <= 1 {
log.Info(). log.Debug().
Str("header", signatureHeader). Str("header", signatureHeader).
Msg("Received signature with invalid pattern") Msg("Received signature with invalid pattern")
webutils.ProblemDetails( webutils.ProblemDetails(
@ -167,7 +165,7 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
} }
_, err = url.Parse(rawKeyId) _, err = url.Parse(rawKeyId)
if err != nil { if err != nil {
log.Warn().Err(err).Msg("Key id is not an url") log.Debug().Err(err).Msg("Key id is not an url")
webutils.ProblemDetails( webutils.ProblemDetails(
w, w,
http.StatusUnauthorized, http.StatusUnauthorized,
@ -181,7 +179,6 @@ func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuil
_ = rawAlgorithm _ = rawAlgorithm
stringToCheck := buildStringToCheck(r, rawHeaders) stringToCheck := buildStringToCheck(r, rawHeaders)
log.Warn().Str("string-to-check", stringToCheck).Send()
requestingActor, err := getRequestingActor(rawKeyId) requestingActor, err := getRequestingActor(rawKeyId)
if err != nil { if err != nil {

View file

@ -28,7 +28,6 @@ package webpublic
import ( import (
"context" "context"
"fmt"
"io/fs" "io/fs"
"net/http" "net/http"
@ -44,10 +43,6 @@ type Server struct {
func New(addr string, duckImg *string, duckFs fs.FS) *Server { func New(addr string, duckImg *string, duckFs fs.FS) *Server {
handler := http.NewServeMux() handler := http.NewServeMux()
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
fmt.Fprint(w, "not implemented")
})
handler.Handle("/api/", http.StripPrefix("/api", api.BuildApiRouter())) handler.Handle("/api/", http.StripPrefix("/api", api.BuildApiRouter()))
handler.HandleFunc("GET /.well-known/webfinger", api.WellKnownWebfinger) handler.HandleFunc("GET /.well-known/webfinger", api.WellKnownWebfinger)
handler.HandleFunc("GET /.well-known/nodeinfo", api.NodeInfoOverview) handler.HandleFunc("GET /.well-known/nodeinfo", api.NodeInfoOverview)

View file

@ -10,7 +10,6 @@ import (
"slices" "slices"
"time" "time"
"github.com/rs/zerolog/log"
"github.com/yaronf/httpsign" "github.com/yaronf/httpsign"
"git.mstar.dev/mstar/linstrom/config" "git.mstar.dev/mstar/linstrom/config"
@ -108,7 +107,6 @@ func RequestSignedCavage(
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug().Bytes("body", body).Any("headers", req.Header).Msg("Sending signed request")
return RequestClient.Do(req) return RequestClient.Do(req)
} }