diff options
author | 2023-07-07 14:58:53 +0200 | |
---|---|---|
committer | 2023-07-07 14:58:53 +0200 | |
commit | ac564c18624aea229defc38bc1cc516d6c787520 (patch) | |
tree | 00fabbb17f9432bbb8fd9b2de4b185ede40f3f39 /internal/web/thread.go | |
parent | [docs] Rework backups a bit (#1942) (diff) | |
download | gotosocial-ac564c18624aea229defc38bc1cc516d6c787520.tar.xz |
[bugfix] Reorder web view logic, other small fixes (#1954)
Diffstat (limited to 'internal/web/thread.go')
-rw-r--r-- | internal/web/thread.go | 116 |
1 files changed, 77 insertions, 39 deletions
diff --git a/internal/web/thread.go b/internal/web/thread.go index 41f81eb14..d2ae29c07 100644 --- a/internal/web/thread.go +++ b/internal/web/thread.go @@ -20,7 +20,6 @@ package web import ( "context" "encoding/json" - "errors" "fmt" "net/http" "strings" @@ -36,66 +35,97 @@ import ( func (m *Module) threadGETHandler(c *gin.Context) { ctx := c.Request.Context() - authed, err := oauth.Authed(c, false, false, false, false) - if err != nil { - apiutil.WebErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) + // We'll need the instance later, and we can also use it + // before then to make it easier to return a web error. + instance, errWithCode := m.processor.InstanceGetV1(ctx) + if errWithCode != nil { + apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } - // usernames on our instance will always be lowercase - username := strings.ToLower(c.Param(usernameKey)) - if username == "" { - err := errors.New("no account username specified") - apiutil.WebErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + // Return instance we already got from the db, + // don't try to fetch it again when erroring. + instanceGet := func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) { + return instance, nil + } + + // Parse account targetUsername and status ID from the URL. + targetUsername, errWithCode := apiutil.ParseWebUsername(c.Param(apiutil.WebUsernameKey)) + if errWithCode != nil { + apiutil.WebErrorHandler(c, errWithCode, instanceGet) return } - // status ids will always be uppercase - statusID := strings.ToUpper(c.Param(statusIDKey)) - if statusID == "" { - err := errors.New("no status id specified") - apiutil.WebErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + targetStatusID, errWithCode := apiutil.ParseWebStatusID(c.Param(apiutil.WebStatusIDKey)) + if errWithCode != nil { + apiutil.WebErrorHandler(c, errWithCode, instanceGet) return } - instance, err := m.processor.InstanceGetV1(ctx) + // Normalize requested username + status ID: + // + // - Usernames on our instance are (currently) always lowercase. + // - StatusIDs on our instance are (currently) always ULIDs. + // + // todo: Update this logic when different username patterns + // are allowed, and/or when status slugs are introduced. + targetUsername = strings.ToLower(targetUsername) + targetStatusID = strings.ToUpper(targetStatusID) + + // Check what type of content is being requested. If we're getting an AP + // request on this endpoint we should render the AP representation instead. + accept, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...) if err != nil { - apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) + apiutil.WebErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), instanceGet) return } - instanceGet := func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) { - return instance, nil + if accept == string(apiutil.AppActivityJSON) || accept == string(apiutil.AppActivityLDJSON) { + // AP status representation has been requested. + m.returnAPStatus(c, targetUsername, targetStatusID, accept, instanceGet) + return } - // do this check to make sure the status is actually from a local account, - // we shouldn't render threads from statuses that don't belong to us! - if _, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, username); errWithCode != nil { - apiutil.WebErrorHandler(c, errWithCode, instanceGet) + // text/html has been requested. Proceed with getting the web view of the status. + + // Don't require auth for web endpoints, but do take it if it was provided. + // authed.Account might end up nil here, but that's fine in case of public pages. + authed, err := oauth.Authed(c, false, false, false, false) + if err != nil { + apiutil.WebErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) return } - status, errWithCode := m.processor.Status().Get(ctx, authed.Account, statusID) + // Fetch the target account so we can do some checks on it. + targetAccount, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, targetUsername) if errWithCode != nil { apiutil.WebErrorHandler(c, errWithCode, instanceGet) return } - if !strings.EqualFold(username, status.Account.Username) { - err := gtserror.NewErrorNotFound(errors.New("path username not equal to status author username")) + // If target account is suspended, this page should not be visible. + if targetAccount.Suspended { + err := fmt.Errorf("target account %s is suspended", targetUsername) apiutil.WebErrorHandler(c, gtserror.NewErrorNotFound(err), instanceGet) return } - // if we're getting an AP request on this endpoint we - // should render the status's AP representation instead - accept := apiutil.NegotiateFormat(c, string(apiutil.TextHTML), string(apiutil.AppActivityJSON), string(apiutil.AppActivityLDJSON)) - if accept == string(apiutil.AppActivityJSON) || accept == string(apiutil.AppActivityLDJSON) { - m.returnAPStatus(c, username, statusID, accept) + // Get the status itself from the processor using provided ID and authorization (if any). + status, errWithCode := m.processor.Status().Get(ctx, authed.Account, targetStatusID) + if errWithCode != nil { + apiutil.WebErrorHandler(c, errWithCode, instanceGet) + return + } + + // Ensure status actually belongs to target account. + if status.GetAccountID() != targetAccount.ID { + err := fmt.Errorf("target account %s does not own status %s", targetUsername, targetStatusID) + apiutil.WebErrorHandler(c, gtserror.NewErrorNotFound(err), instanceGet) return } - context, errWithCode := m.processor.Status().ContextGet(ctx, authed.Account, statusID) + // Fill in the rest of the thread context. + context, errWithCode := m.processor.Status().ContextGet(ctx, authed.Account, targetStatusID) if errWithCode != nil { apiutil.WebErrorHandler(c, errWithCode, instanceGet) return @@ -106,7 +136,7 @@ func (m *Module) threadGETHandler(c *gin.Context) { distPathPrefix + "/status.css", } if config.GetAccountsAllowCustomCSS() { - stylesheets = append(stylesheets, "/@"+username+"/custom.css") + stylesheets = append(stylesheets, "/@"+targetUsername+"/custom.css") } c.HTML(http.StatusOK, "thread.tmpl", gin.H{ @@ -119,17 +149,25 @@ func (m *Module) threadGETHandler(c *gin.Context) { }) } -func (m *Module) returnAPStatus(c *gin.Context, username string, statusID string, accept string) { - status, errWithCode := m.processor.Fedi().StatusGet(c.Request.Context(), username, statusID) +// returnAPStatus returns an ActivityPub representation of target status, +// created by targetUsername. It will do http signature authentication. +func (m *Module) returnAPStatus( + c *gin.Context, + targetUsername string, + targetStatusID string, + accept string, + instanceGet func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode), +) { + status, errWithCode := m.processor.Fedi().StatusGet(c.Request.Context(), targetUsername, targetStatusID) if errWithCode != nil { - apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + apiutil.WebErrorHandler(c, errWithCode, instanceGet) return } - b, mErr := json.Marshal(status) - if mErr != nil { - err := fmt.Errorf("could not marshal json: %s", mErr) - apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) + b, err := json.Marshal(status) + if err != nil { + err := gtserror.Newf("could not marshal json: %w", err) + apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), instanceGet) return } |