remoteFollow.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package controllers
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strings"
  8. "github.com/owncast/owncast/core/data"
  9. )
  10. // RemoteFollow handles a request to begin the remote follow redirect flow.
  11. func RemoteFollow(w http.ResponseWriter, r *http.Request) {
  12. type followRequest struct {
  13. Account string `json:"account"`
  14. }
  15. type followResponse struct {
  16. RedirectURL string `json:"redirectUrl"`
  17. }
  18. var request followRequest
  19. decoder := json.NewDecoder(r.Body)
  20. if err := decoder.Decode(&request); err != nil {
  21. WriteSimpleResponse(w, false, "unable to parse request")
  22. return
  23. }
  24. if request.Account == "" {
  25. WriteSimpleResponse(w, false, "Remote Fediverse account is required to follow.")
  26. return
  27. }
  28. localActorPath, _ := url.Parse(data.GetServerURL())
  29. localActorPath.Path = fmt.Sprintf("/federation/user/%s", data.GetDefaultFederationUsername())
  30. var template string
  31. links, err := getWebfingerLinks(request.Account)
  32. if err != nil {
  33. WriteSimpleResponse(w, false, err.Error())
  34. return
  35. }
  36. // Acquire the remote follow redirect template.
  37. for _, link := range links {
  38. for k, v := range link {
  39. if k == "rel" && v == "http://ostatus.org/schema/1.0/subscribe" && link["template"] != nil {
  40. template = link["template"].(string)
  41. }
  42. }
  43. }
  44. if localActorPath.String() == "" || template == "" {
  45. WriteSimpleResponse(w, false, "unable to determine remote follow information for "+request.Account)
  46. return
  47. }
  48. redirectURL := strings.Replace(template, "{uri}", localActorPath.String(), 1)
  49. response := followResponse{
  50. RedirectURL: redirectURL,
  51. }
  52. WriteResponse(w, response)
  53. }
  54. func getWebfingerLinks(account string) ([]map[string]interface{}, error) {
  55. type webfingerResponse struct {
  56. Links []map[string]interface{} `json:"links"`
  57. }
  58. account = strings.TrimLeft(account, "@") // remove any leading @
  59. accountComponents := strings.Split(account, "@")
  60. fediverseServer := accountComponents[1]
  61. // HTTPS is required.
  62. requestURL, err := url.Parse("https://" + fediverseServer)
  63. if err != nil {
  64. return nil, fmt.Errorf("unable to parse fediverse server host %s", fediverseServer)
  65. }
  66. requestURL.Path = "/.well-known/webfinger"
  67. query := requestURL.Query()
  68. query.Add("resource", fmt.Sprintf("acct:%s", account))
  69. requestURL.RawQuery = query.Encode()
  70. response, err := http.DefaultClient.Get(requestURL.String())
  71. if err != nil {
  72. return nil, err
  73. }
  74. defer response.Body.Close()
  75. var links webfingerResponse
  76. decoder := json.NewDecoder(response.Body)
  77. if err := decoder.Decode(&links); err != nil {
  78. return nil, err
  79. }
  80. return links.Links, nil
  81. }