A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

146 lines
3.5 KiB

  1. /*
  2. * Copyright © 2018-2021 Musing Studio LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "encoding/json"
  13. "io"
  14. "net/http"
  15. "strings"
  16. "github.com/writeas/go-webfinger"
  17. "github.com/writeas/impart"
  18. "github.com/writeas/web-core/log"
  19. "github.com/writefreely/writefreely/config"
  20. )
  21. type wfResolver struct {
  22. db *datastore
  23. cfg *config.Config
  24. }
  25. var wfUserNotFoundErr = impart.HTTPError{http.StatusNotFound, "User not found."}
  26. func (wfr wfResolver) FindUser(username string, host, requestHost string, r []webfinger.Rel) (*webfinger.Resource, error) {
  27. var c *Collection
  28. var err error
  29. if username == host {
  30. c = instanceColl
  31. } else if wfr.cfg.App.SingleUser {
  32. c, err = wfr.db.GetCollectionByID(1)
  33. } else {
  34. c, err = wfr.db.GetCollection(username)
  35. }
  36. if err != nil {
  37. log.Error("Unable to get blog: %v", err)
  38. return nil, err
  39. }
  40. c.hostName = wfr.cfg.App.Host
  41. if !c.IsInstanceColl() {
  42. silenced, err := wfr.db.IsUserSilenced(c.OwnerID)
  43. if err != nil {
  44. log.Error("webfinger find user: check is silenced: %v", err)
  45. return nil, err
  46. }
  47. if silenced {
  48. return nil, wfUserNotFoundErr
  49. }
  50. }
  51. if wfr.cfg.App.SingleUser {
  52. // Ensure handle matches user-chosen one on single-user blogs
  53. if username != c.Alias {
  54. log.Info("Username '%s' is not handle '%s'", username, c.Alias)
  55. return nil, wfUserNotFoundErr
  56. }
  57. }
  58. // Only return information if site has federation enabled.
  59. // TODO: enable two levels of federation? Unlisted or Public on timelines?
  60. if !wfr.cfg.App.Federation {
  61. return nil, wfUserNotFoundErr
  62. }
  63. res := webfinger.Resource{
  64. Subject: "acct:" + username + "@" + host,
  65. Aliases: []string{
  66. c.CanonicalURL(),
  67. c.FederatedAccount(),
  68. },
  69. Links: []webfinger.Link{
  70. {
  71. HRef: c.CanonicalURL(),
  72. Type: "text/html",
  73. Rel: "https://webfinger.net/rel/profile-page",
  74. },
  75. {
  76. HRef: c.FederatedAccount(),
  77. Type: "application/activity+json",
  78. Rel: "self",
  79. },
  80. },
  81. }
  82. return &res, nil
  83. }
  84. func (wfr wfResolver) DummyUser(username string, hostname string, r []webfinger.Rel) (*webfinger.Resource, error) {
  85. return nil, wfUserNotFoundErr
  86. }
  87. func (wfr wfResolver) IsNotFoundError(err error) bool {
  88. return err == wfUserNotFoundErr
  89. }
  90. // RemoteLookup looks up a user by handle at a remote server
  91. // and returns the actor URL
  92. func RemoteLookup(handle string) string {
  93. handle = strings.TrimLeft(handle, "@")
  94. // let's take the server part of the handle
  95. parts := strings.Split(handle, "@")
  96. resp, err := http.Get("https://" + parts[1] + "/.well-known/webfinger?resource=acct:" + handle)
  97. if err != nil {
  98. log.Error("Error on webfinger request: %v", err)
  99. return ""
  100. }
  101. body, err := io.ReadAll(resp.Body)
  102. if err != nil {
  103. log.Error("Error on webfinger response: %v", err)
  104. return ""
  105. }
  106. var result webfinger.Resource
  107. err = json.Unmarshal(body, &result)
  108. if err != nil {
  109. log.Error("Unable to parse webfinger response: %v", err)
  110. return ""
  111. }
  112. var href string
  113. // iterate over webfinger links and find the one with
  114. // a self "rel"
  115. for _, link := range result.Links {
  116. if link.Rel == "self" {
  117. href = link.HRef
  118. }
  119. }
  120. // if we didn't find it with the above then
  121. // try using aliases
  122. if href == "" {
  123. // take the last alias because mastodon has the
  124. // https://instance.tld/@user first which
  125. // doesn't work as an href
  126. href = result.Aliases[len(result.Aliases)-1]
  127. }
  128. return href
  129. }