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