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  }