Skip to content

Commit 2e2e4c7

Browse files
committed
Implemented Webfinger endpoint.
1 parent 1e319ba commit 2e2e4c7

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

routers/web/web.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ func RegisterRoutes(m *web.Route) {
288288
m.Get("/openid-configuration", auth.OIDCWellKnown)
289289
if setting.Federation.Enabled {
290290
m.Get("/nodeinfo", NodeInfoLinks)
291+
m.Get("/webfinger", WebfingerQuery)
291292
}
292293
m.Get("/change-password", func(w http.ResponseWriter, req *http.Request) {
293294
http.Redirect(w, req, "/user/settings/account", http.StatusTemporaryRedirect)

routers/web/webfinger.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package web
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
"net/url"
11+
"regexp"
12+
"strings"
13+
14+
user_model "code.gitea.io/gitea/models/user"
15+
"code.gitea.io/gitea/modules/context"
16+
"code.gitea.io/gitea/modules/log"
17+
"code.gitea.io/gitea/modules/setting"
18+
)
19+
20+
var webfingerRessourcePattern = regexp.MustCompile(`(?i)\A([a-z^:]+):(.*)\z`)
21+
22+
// https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
23+
24+
type webfingerJRD struct {
25+
Subject string `json:"subject,omitempty"`
26+
Aliases []string `json:"aliases,omitempty"`
27+
Properties map[string]interface{} `json:"properties,omitempty"`
28+
Links []*webfingerLink `json:"links,omitempty"`
29+
}
30+
31+
type webfingerLink struct {
32+
Rel string `json:"rel,omitempty"`
33+
Type string `json:"type,omitempty"`
34+
Href string `json:"href,omitempty"`
35+
Titles map[string]string `json:"titles,omitempty"`
36+
Properties map[string]interface{} `json:"properties,omitempty"`
37+
}
38+
39+
// WebfingerQuery returns informations about a resource
40+
// https://datatracker.ietf.org/doc/html/rfc7565
41+
func WebfingerQuery(ctx *context.Context) {
42+
resource := ctx.FormTrim("resource")
43+
44+
scheme := "acct"
45+
uri := resource
46+
47+
match := webfingerRessourcePattern.FindStringSubmatch(resource)
48+
if match != nil {
49+
scheme = match[1]
50+
uri = match[2]
51+
}
52+
53+
appURL, _ := url.Parse(setting.AppURL)
54+
55+
var u *user_model.User
56+
var err error
57+
58+
switch scheme {
59+
case "acct":
60+
// allow only the current host
61+
parts := strings.SplitN(uri, "@", 2)
62+
if len(parts) != 2 {
63+
ctx.Error(http.StatusBadRequest)
64+
return
65+
}
66+
if parts[1] != appURL.Host {
67+
ctx.Error(http.StatusBadRequest)
68+
return
69+
}
70+
71+
u, err = user_model.GetUserByNameCtx(ctx, parts[0])
72+
case "mailto":
73+
u, err = user_model.GetUserByEmailContext(ctx, uri)
74+
default:
75+
ctx.Error(http.StatusBadRequest)
76+
return
77+
}
78+
if err != nil {
79+
if user_model.IsErrUserNotExist(err) {
80+
ctx.Error(http.StatusNotFound)
81+
} else {
82+
log.Error("Error getting user: %v", err)
83+
ctx.Error(http.StatusInternalServerError)
84+
}
85+
return
86+
}
87+
88+
// Should we check IsUserVisibleToViewer here?
89+
90+
aliases := make([]string, 0, 1)
91+
if !u.KeepEmailPrivate {
92+
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
93+
}
94+
95+
links := []*webfingerLink{
96+
{
97+
Rel: "http://webfinger.net/rel/profile-page",
98+
Type: "text/html",
99+
Href: u.HTMLURL(),
100+
},
101+
{
102+
Rel: "http://webfinger.net/rel/avatar",
103+
Href: u.AvatarLink(),
104+
},
105+
}
106+
107+
ctx.JSON(http.StatusOK, &webfingerJRD{
108+
Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
109+
Aliases: aliases,
110+
Links: links,
111+
})
112+
}

0 commit comments

Comments
 (0)