Compare commits
6 commits
6cc699cbbd
...
ef95a0552d
Author | SHA1 | Date | |
---|---|---|---|
ef95a0552d | |||
5cc6314360 | |||
66fbf9afbb | |||
d8dd5eb671 | |||
ca8f5fd483 | |||
ef58b5ca9b |
7 changed files with 194 additions and 16 deletions
|
@ -1,3 +1,66 @@
|
|||
package activitypub
|
||||
|
||||
func ImportRemoteNote(noteId string) {}
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
func ImportRemoteNote(noteId string, requester *models.User) (string, error) {
|
||||
type Note struct {
|
||||
Type string
|
||||
Id string
|
||||
Summary *string
|
||||
Content string
|
||||
MkContent *string `json:"_misskey_content"`
|
||||
Published time.Time
|
||||
To []string
|
||||
Cc []string
|
||||
InReplyTo *string
|
||||
Sensitive bool
|
||||
AttributedTo string
|
||||
}
|
||||
res, _, err := webshared.RequestSigned("GET", noteId, nil, requester)
|
||||
if err != nil {
|
||||
return "", other.Error("activitypub", "failed to request object", err)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", fmt.Errorf("activitypub: invalid status code: %v", res.StatusCode)
|
||||
}
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
data := Note{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return "", other.Error("activitypub", "json unmarshalling failed", err)
|
||||
}
|
||||
if data.Type != "Note" {
|
||||
return "", fmt.Errorf("activitypub: invalid object type: %q", data.Type)
|
||||
}
|
||||
// (Re-)Import account this note belongs to
|
||||
_, err = ImportRemoteAccountByAPUrl(data.AttributedTo)
|
||||
if err != nil {
|
||||
return "", other.Error("activitypub", "failed to import note author", err)
|
||||
}
|
||||
dbNote, err := dbgen.Note.Where(dbgen.Note.ID.Eq(data.Id)).First()
|
||||
switch err {
|
||||
case nil:
|
||||
case gorm.ErrRecordNotFound:
|
||||
dbNote = &models.Note{
|
||||
ID: data.Id,
|
||||
CreatedAt: data.Published,
|
||||
CreatorId: data.AttributedTo,
|
||||
}
|
||||
default:
|
||||
return "", other.Error("activitypub", "failed to check db for note", err)
|
||||
}
|
||||
return dbNote.ID, nil
|
||||
}
|
||||
|
|
|
@ -419,25 +419,14 @@ func ImportRemoteAccountByAPUrl(apUrl string) (*models.User, error) {
|
|||
// Reason: Implementations should be switching over from cavage to the final implementation
|
||||
// (rfc9421) slowly, but might not support the latter. Double-knocking will work
|
||||
// around this
|
||||
response, err = webshared.RequestSignedRFC9421("GET", apUrl, nil, linstromActor)
|
||||
response, _, err = webshared.RequestSigned("GET", apUrl, nil, linstromActor)
|
||||
if err != nil {
|
||||
return nil, other.Error("activitypub", "failed to complete rfc9421 signed request", err)
|
||||
return nil, other.Error("activitypub", "failed to complete signed request", err)
|
||||
}
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
log.Debug().
|
||||
Int("status-code", response.StatusCode).
|
||||
Msg("RFC9421 signed request failed, trying cavage signature")
|
||||
response, err = webshared.RequestSignedCavage("GET", apUrl, nil, linstromActor)
|
||||
if err != nil {
|
||||
return nil, other.Error("activitypub", "failed to complete cavage signed request", err)
|
||||
}
|
||||
body, _ = io.ReadAll(response.Body)
|
||||
response.Body.Close()
|
||||
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)
|
||||
}
|
||||
var data inboundImportUser
|
||||
err = json.Unmarshal(body, &data)
|
||||
|
|
63
samples/mkNote.json
Normal file
63
samples/mkNote.json
Normal file
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"@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#"
|
||||
}
|
||||
],
|
||||
"id": "https://sharkey.team/notes/a73pi9r9eehz006z",
|
||||
"type": "Note",
|
||||
"attributedTo": "https://sharkey.team/users/a2xsj3dufv930001",
|
||||
"content": "<p><span>We've officially released version 2025.2.3 of Sharkey!<br><br></span><b>This update contains critical security fixes. Please update as soon as possible.</b> Disclosures for the relevant vulnerabilities will be made available once instances have been patched.</p>",
|
||||
"_misskey_content": "We've officially released version 2025.2.3 of Sharkey!\n\n**This update contains critical security fixes. Please update as soon as possible.** Disclosures for the relevant vulnerabilities will be made available once instances have been patched.",
|
||||
"source": {
|
||||
"content": "We've officially released version 2025.2.3 of Sharkey!\n\n**This update contains critical security fixes. Please update as soon as possible.** Disclosures for the relevant vulnerabilities will be made available once instances have been patched.",
|
||||
"mediaType": "text/x.misskeymarkdown"
|
||||
},
|
||||
"published": "2025-04-27T21:09:18.693Z",
|
||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc": ["https://sharkey.team/users/a2xsj3dufv930001/followers"],
|
||||
"inReplyTo": null,
|
||||
"attachment": [],
|
||||
"sensitive": false,
|
||||
"tag": []
|
||||
}
|
|
@ -35,6 +35,7 @@ func New(addr string) *Server {
|
|||
handler.HandleFunc("GET /request-follow", kickoffFollow)
|
||||
handler.HandleFunc("POST /send-as", proxyMessageToTarget)
|
||||
handler.HandleFunc("GET /replies-to/{id}", inReplyTo)
|
||||
handler.HandleFunc("POST /fetch", requestAs)
|
||||
web := http.Server{
|
||||
Addr: addr,
|
||||
Handler: webutils.ChainMiddlewares(
|
||||
|
|
|
@ -295,3 +295,36 @@ func kickoffFollow(w http.ResponseWriter, r *http.Request) {
|
|||
dec := json.NewDecoder(r.Body)
|
||||
dec.Decode(&data)
|
||||
}
|
||||
|
||||
func requestAs(w http.ResponseWriter, r *http.Request) {
|
||||
type Inbound struct {
|
||||
Username string
|
||||
TargetUrl string
|
||||
}
|
||||
log := hlog.FromRequest(r)
|
||||
data := Inbound{}
|
||||
dec := json.NewDecoder(r.Body)
|
||||
err := dec.Decode(&data)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to decode json body")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
user, err := dbgen.User.GetByUsername(data.Username)
|
||||
if err != nil {
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
res, _, err := webshared.RequestSigned("GET", data.TargetUrl, nil, user)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Request failed")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
webutils.ProblemDetailsStatusOnly(w, res.StatusCode)
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
fmt.Fprint(w, string(body))
|
||||
}
|
||||
|
|
|
@ -694,7 +694,17 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
|||
}
|
||||
switch val := activity.Object.(type) {
|
||||
case string:
|
||||
activitypub.ImportRemoteNote(val)
|
||||
actor, err := dbgen.User.GetById(r.PathValue("id"))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get local actor for importing targeted note")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = activitypub.ImportRemoteNote(val, actor)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("note-url", val).Msg("Failed to import remote note that landed as id in the inbox")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
case map[string]any:
|
||||
default:
|
||||
|
|
|
@ -29,6 +29,25 @@ Links for home:
|
|||
- https://datatracker.ietf.org/doc/html/rfc9421
|
||||
*/
|
||||
|
||||
// Perform a signed request. Tries RFC9421 first and on fail cavage.
|
||||
// This double-knocking is because implementations currently use cavage (a precursor to RFC9421).
|
||||
// However, Cavage is deprecated now and the RFC should be used instead. And so
|
||||
// implementations have slowly begun to implement the RFC in addition to cavage
|
||||
//
|
||||
// Returns the unmodified response, if the request completed with RFC signing and an error, if any
|
||||
func RequestSigned(
|
||||
method, target string,
|
||||
body []byte,
|
||||
actor *models.User,
|
||||
) (response *http.Response, wasRfc9421 bool, err error) {
|
||||
res, err := RequestSignedRFC9421(method, target, body, actor)
|
||||
if err == nil {
|
||||
return res, true, nil
|
||||
}
|
||||
res, err = RequestSignedCavage(method, target, body, actor)
|
||||
return res, false, err
|
||||
}
|
||||
|
||||
// Perform a request, signing it as specified in RFC 9421
|
||||
func RequestSignedRFC9421(
|
||||
method, target string,
|
||||
|
|
Loading…
Reference in a new issue