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.

webfinger.go 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package web
  4. import (
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/setting"
  13. )
  14. // https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
  15. type webfingerJRD struct {
  16. Subject string `json:"subject,omitempty"`
  17. Aliases []string `json:"aliases,omitempty"`
  18. Properties map[string]any `json:"properties,omitempty"`
  19. Links []*webfingerLink `json:"links,omitempty"`
  20. }
  21. type webfingerLink struct {
  22. Rel string `json:"rel,omitempty"`
  23. Type string `json:"type,omitempty"`
  24. Href string `json:"href,omitempty"`
  25. Titles map[string]string `json:"titles,omitempty"`
  26. Properties map[string]any `json:"properties,omitempty"`
  27. }
  28. // WebfingerQuery returns information about a resource
  29. // https://datatracker.ietf.org/doc/html/rfc7565
  30. func WebfingerQuery(ctx *context.Context) {
  31. appURL, _ := url.Parse(setting.AppURL)
  32. resource, err := url.Parse(ctx.FormTrim("resource"))
  33. if err != nil {
  34. ctx.Error(http.StatusBadRequest)
  35. return
  36. }
  37. var u *user_model.User
  38. switch resource.Scheme {
  39. case "acct":
  40. // allow only the current host
  41. parts := strings.SplitN(resource.Opaque, "@", 2)
  42. if len(parts) != 2 {
  43. ctx.Error(http.StatusBadRequest)
  44. return
  45. }
  46. if parts[1] != appURL.Host {
  47. ctx.Error(http.StatusBadRequest)
  48. return
  49. }
  50. u, err = user_model.GetUserByName(ctx, parts[0])
  51. case "mailto":
  52. u, err = user_model.GetUserByEmail(ctx, resource.Opaque)
  53. if u != nil && u.KeepEmailPrivate {
  54. err = user_model.ErrUserNotExist{}
  55. }
  56. default:
  57. ctx.Error(http.StatusBadRequest)
  58. return
  59. }
  60. if err != nil {
  61. if user_model.IsErrUserNotExist(err) {
  62. ctx.Error(http.StatusNotFound)
  63. } else {
  64. log.Error("Error getting user: %s Error: %v", resource.Opaque, err)
  65. ctx.Error(http.StatusInternalServerError)
  66. }
  67. return
  68. }
  69. if !user_model.IsUserVisibleToViewer(ctx, u, ctx.Doer) {
  70. ctx.Error(http.StatusNotFound)
  71. return
  72. }
  73. aliases := []string{
  74. u.HTMLURL(),
  75. appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
  76. }
  77. if !u.KeepEmailPrivate {
  78. aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
  79. }
  80. links := []*webfingerLink{
  81. {
  82. Rel: "http://webfinger.net/rel/profile-page",
  83. Type: "text/html",
  84. Href: u.HTMLURL(),
  85. },
  86. {
  87. Rel: "http://webfinger.net/rel/avatar",
  88. Href: u.AvatarLink(ctx),
  89. },
  90. {
  91. Rel: "self",
  92. Type: "application/activity+json",
  93. Href: appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
  94. },
  95. {
  96. Rel: "http://openid.net/specs/connect/1.0/issuer",
  97. Href: appURL.String(),
  98. },
  99. }
  100. ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
  101. ctx.JSON(http.StatusOK, &webfingerJRD{
  102. Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
  103. Aliases: aliases,
  104. Links: links,
  105. })
  106. }