commit d773028eb1cfefd2b4777aee16381c406dd1734a Author: Sheena Artrip Date: Sat Jan 7 16:41:01 2017 -0500 Initial commit of webfinger service diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4376fb1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Sheena Artrip + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f53407c --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# go-webfinger + +go-webfinger is a golang webfinger server implementation. + +## Usage + +`webfinger.Service` is implemented as a net/http handler, which means +usage is simply registering the object with your http service. + +Using the webfinger service as the main ServeHTTP: + +```go +myResolver = ... +wf := webfinger.Default(myResolver{}) +wf.NotFoundHandler = // the rest of your app +http.ListenAndService(":8080", wf) +``` + +Using the webfinger service as a route on an existing HTTP router: + +```go +myResolver = ... +wf := webfinger.Default(myResolver{}) +http.Handle(webfinger.WebFingerPath, http.HandlerFunc(wf.Webfinger)) +http.ListenAndService(":8080", nil) +``` + +## Defaults + +The webfinger service is installed with a few defaults. Some of these +defaults ensure we stick closely to the webfinger specification (tls-only, CORS, Content-Type) +and other defaults are simply useful for development (no-cache) + +The full list of defaults can be found in the godoc for `webfinger.Service`. They are exposed +as public variables which can be overriden. + +`PreHandlers` are the list of preflight HTTP handlers to run. You can add your own via `wf.PreHandlers["my-custom-name"] = ...`, however, +execution order is not guaranteed. + +### TLS-Only + +Handler which routes to the TLS version of the page. Disable via `wf.NoTLSHandler = nil`. + +### No-Cache + +A PreFlight handler which sets no-cache headers on anything under `/.well-known/webfinger`. Disable or override via `wf.PreHandlers[webfinger.NoCacheMiddleware]` + +### Content Type as application/jrd+json + +A PreFlight handler which sets the Content-Type to `application/jrd+json`. Disable or override via `wf.PreHandlers[webfinger.ContentTypeMiddleware]`. + +### CORS + +A PreFlight handler which adds the CORS headers. Disable or override via `wf.PreHandlers[webfinger.CorsMiddleware].` \ No newline at end of file diff --git a/account.go b/account.go new file mode 100644 index 0000000..b435e3c --- /dev/null +++ b/account.go @@ -0,0 +1,30 @@ +package webfinger + +import ( + "errors" + "strings" +) + +type account struct { + Name string + Hostname string +} + +func (a *account) ParseString(str string) (err error) { + if !strings.HasPrefix(str, "acct:") { + err = errors.New("URI is not an account") + return + } + + items := strings.Split(str, "@") + a.Name = items[0][5:] + if len(items) < 2 { + //TODO: this might not be required + err = errors.New("No domain on account") + return + } + + a.Hostname = strings.Split(items[1], "/")[0] + + return +} diff --git a/account_test.go b/account_test.go new file mode 100644 index 0000000..e7b18d1 --- /dev/null +++ b/account_test.go @@ -0,0 +1,51 @@ +package webfinger + +import ( + "testing" + + "reflect" + + "github.com/pkg/errors" +) + +type acp struct { + Input string + Output account + Error string +} + +func (a *acp) Invoke() error { + var ax account + err := ax.ParseString(a.Input) + + failed := false + failed = failed || err != nil && a.Error == "" + failed = failed || err == nil && a.Error != "" + failed = failed || err != nil && a.Error != err.Error() + failed = failed || err != nil && a.Error != errors.Cause(err).Error() + + failed = failed || !reflect.DeepEqual(a.Output, ax) + + if failed { + return errors.Errorf("ax.ParseString('%v') => '%v', '%v'; expected '%v', '%v'", a.Input, err, ax, a.Error, a.Output) + } + return nil +} + +func TestAccountParse(t *testing.T) { + var tests = []acp{ + {"", account{}, "URI is not an account"}, + {"http://hello.world", account{}, "URI is not an account"}, + + {"acct:hello", account{"hello", ""}, "No domain on account"}, + {"acct:hello@domain", account{"hello", "domain"}, ""}, + + {"acct:hello@domain/uri", account{"hello", "domain"}, ""}, + } + + for _, tx := range tests { + if err := tx.Invoke(); err != nil { + t.Error(err.Error()) + } + } +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..88559d1 --- /dev/null +++ b/doc.go @@ -0,0 +1,23 @@ +// Package webfinger is a server implementation of the webfinger specification. This +// is a general-case package which provides the HTTP handlers and interfaces +// for adding webfinger support for your system and resources. +// +// The simplest way to use this is to call webfinger.Default() and +// then register the object as an HTTP handler: +// +// myResolver = ... +// wf := webfinger.Default(myResolver{}) +// wf.NotFoundHandler = // the rest of your app +// http.ListenAndService(":8080", wf) +// +// However, you can also register the specific webfinger handler to a path. This should +// work on any router that supports net/http. +// +// myResolver = ... +// wf := webfinger.Default(myResolver{}) +// http.Handle(webfinger.WebFingerPath, http.HandlerFunc(wf.Webfinger)) +// http.ListenAndService(":8080", nil) +// +// In either case, the handlers attached to the webfinger service get invoked as +// needed. +package webfinger diff --git a/error.go b/error.go new file mode 100644 index 0000000..f2ec032 --- /dev/null +++ b/error.go @@ -0,0 +1,31 @@ +package webfinger + +import ( + "context" + "net/http" +) + +// ErrorKeyType is the type for the context error key +type ErrorKeyType int + +// ErrorKey is the key for the context error +var ErrorKey ErrorKeyType + +// ErrorFromContext gets the error from the context +func ErrorFromContext(ctx context.Context) error { + v, ok := ctx.Value(ErrorKey).(error) + if !ok { + return nil + } + return v +} + +func addError(r *http.Request, err error) *http.Request { + if err == nil { + return r + } + ctx := r.Context() + ctx = context.WithValue(ctx, ErrorKey, err) + r = r.WithContext(ctx) + return r +} diff --git a/error_test.go b/error_test.go new file mode 100644 index 0000000..52b086f --- /dev/null +++ b/error_test.go @@ -0,0 +1,47 @@ +package webfinger + +import ( + "context" + "errors" + "net/http" + "testing" +) + +func TestAddAndGetError(t *testing.T) { + + { // test with no error + ctx := context.Background() + r := &http.Request{} + r = r.WithContext(ctx) + + err2 := ErrorFromContext(r.Context()) + if err2 != nil { + t.Errorf("No error should result in no error") + } + } + + { // Test with addError(nil) + ctx := context.Background() + r := &http.Request{} + r = r.WithContext(ctx) + r = addError(r, nil) + + err2 := ErrorFromContext(r.Context()) + if err2 != nil { + t.Errorf("Error was nil, is now not nil") + } + } + + { // Test with addError(errors.New("X")) + ctx := context.Background() + r := &http.Request{} + r = r.WithContext(ctx) + r = addError(r, errors.New("X")) + + err2 := ErrorFromContext(r.Context()) + if err2 == nil || err2.Error() != "X" { + t.Errorf("Err is %v, expected 'X'", err2) + } + } + +} diff --git a/http.go b/http.go new file mode 100644 index 0000000..a0b39f5 --- /dev/null +++ b/http.go @@ -0,0 +1,113 @@ +package webfinger + +import ( + "encoding/json" + "errors" + "net/http" +) + +// WebFingerPath defines the default path of the webfinger handler. +const WebFingerPath = "/.well-known/webfinger" + +func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + + //TODO: support host-meta as a path + + path := r.URL.Path + switch path { + case WebFingerPath: + s.Webfinger(w, r) + default: + s.NotFoundHandler.ServeHTTP(w, r) + } +} + +// Webfinger is the webfinger handler +func (s *Service) Webfinger(w http.ResponseWriter, r *http.Request) { + s.runPrehandlers(w, r) + + if r.TLS == nil && s.NoTLSHandler != nil { + s.NoTLSHandler.ServeHTTP(w, r) + return + } + + //NOTE: should this run before or after the pre-run handlers? + if r.Method != "GET" { + s.MethodNotSupportedHandler.ServeHTTP(w, r) + return + } + + if len(r.URL.Query()["resource"]) != 1 { + s.MalformedRequestHandler.ServeHTTP(w, addError(r, errors.New("Malformed resource parameter"))) + return + } + resource := r.URL.Query().Get("resource") + var a account + if err := a.ParseString(resource); err != nil { + s.MalformedRequestHandler.ServeHTTP(w, addError(r, err)) + return + } + + relStrings := r.URL.Query()["rel"] + var rels []Rel + for _, r := range relStrings { + rels = append(rels, Rel(r)) + } + + rsc, err := s.Resolver.FindUser(a.Name, a.Hostname, rels) + if err != nil { + if !s.Resolver.IsNotFoundError(err) { + s.ErrorHandler.ServeHTTP(w, addError(r, err)) + return + } + + rsc, err = s.Resolver.DummyUser(a.Name, a.Hostname, rels) + if err != nil && !s.Resolver.IsNotFoundError(err) { + s.ErrorHandler.ServeHTTP(w, addError(r, err)) + return + } else if s.Resolver.IsNotFoundError(err) { + s.NotFoundHandler.ServeHTTP(w, r) + return + } + } + + if err := json.NewEncoder(w).Encode(&rsc); err != nil { + s.ErrorHandler.ServeHTTP(w, addError(r, err)) + return + } +} + +func (s *Service) runPrehandlers(w http.ResponseWriter, r *http.Request) { + if s.PreHandlers == nil { + return + } + + for _, val := range s.PreHandlers { + if val != nil { + val.ServeHTTP(w, r) + } + } +} + +func (s *Service) defaultErrorHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) +} + +func (s *Service) defaultNotFoundHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) +} + +func (s *Service) defaultMethodNotSupportedHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusMethodNotAllowed) +} + +func (s *Service) defaultMalformedRequestHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) +} + +func (s *Service) defaultNoTLSHandler(w http.ResponseWriter, r *http.Request) { + u := *r.URL + u.Scheme = "https" + w.Header().Set("Location", u.String()) + w.WriteHeader(http.StatusSeeOther) +} diff --git a/http_test.go b/http_test.go new file mode 100644 index 0000000..a8cb08f --- /dev/null +++ b/http_test.go @@ -0,0 +1,288 @@ +package webfinger + +import ( + "bytes" + "net/http" + "net/url" + "sort" + "testing" + + "reflect" + + "crypto/tls" + + "strings" + + "github.com/pkg/errors" +) + +type dummyUserResolver struct { +} + +func (d *dummyUserResolver) FindUser(username string, hostname string, rel []Rel) (*Resource, error) { + if username == "hello" { + if len(rel) == 2 && rel[0] == "x" && rel[1] == "y" { + return &Resource{ + Links: []Link{ + Link{ + HRef: string(rel[0]), + Rel: string(rel[0]), + }, + Link{ + HRef: string(rel[1]), + Rel: string(rel[1]), + }, + }, + }, nil + } + return &Resource{ + Links: []Link{ + Link{ + HRef: string("x"), + Rel: string("x"), + }, + Link{ + HRef: string("y"), + Rel: string("y"), + }, + Link{ + HRef: string("z"), + Rel: string("z"), + }, + }, + }, nil + } + + return nil, errors.New("User not found") +} + +func (d *dummyUserResolver) DummyUser(username string, hostname string, rel []Rel) (*Resource, error) { + return nil, errors.New("User not found") +} + +func (d *dummyUserResolver) IsNotFoundError(err error) bool { + if err == nil { + return false + } + if err.Error() == "User not found" { + return true + } + if errors.Cause(err).Error() == "User not found" { + return true + } + return false +} + +type dummyResponseWriter struct { + bytes.Buffer + + code int + headers http.Header +} + +func (d *dummyResponseWriter) Header() http.Header { + if d.headers == nil { + d.headers = make(http.Header) + } + return d.headers +} + +func (d *dummyResponseWriter) WriteHeader(i int) { + d.code = i +} + +type serveHTTPTest struct { + Description string + Input *http.Request + OutputCode int + OutputHeaders http.Header + OutputBody string +} + +type kv struct { + key string + val string +} + +func buildRequest(method string, path string, query string, kvx ...kv) *http.Request { + r := &http.Request{} + r.Host = "http://localhost" + r.Header = make(http.Header) + r.URL = &url.URL{ + Path: path, + RawQuery: query, + Host: "localhost", + Scheme: "http", + } + for _, k := range kvx { + r.Header.Add(k.key, k.val) + } + r.Method = method + return r +} + +func buildRequestTLS(method string, path string, query string, kvx ...kv) *http.Request { + r := &http.Request{} + r.Host = "https://localhost" + r.Header = make(http.Header) + r.URL = &url.URL{ + Path: path, + RawQuery: query, + Host: "localhost", + Scheme: "https", + } + r.TLS = &tls.ConnectionState{} // marks the request as TLS + for _, k := range kvx { + r.Header.Add(k.key, k.val) + } + r.Method = method + return r +} + +var defaultHeaders = http.Header(map[string][]string{ + "Cache-Control": []string{"no-cache"}, + "Pragma": []string{"no-cache"}, + "Content-Type": []string{"application/jrd+json"}, +}) + +func plusHeader(h http.Header, kvx ...kv) http.Header { + var h2 = make(http.Header) + for k, vx := range h { + for _, v := range vx { + h2.Add(k, v) + } + } + + for _, k := range kvx { + h2.Add(k.key, k.val) + } + + return h2 +} + +func compareHeaders(h1 http.Header, h2 http.Header) bool { + if len(h1) != len(h2) { + return false + } + keys := []string{} + for k := range h1 { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + if !reflect.DeepEqual(h1[k], h2[k]) { + return false + } + } + return true +} + +func TestServiceServeHTTP(t *testing.T) { + + var tests = []serveHTTPTest{ + + {"GET root URL should return 404 not found with no headers", + buildRequestTLS("GET", "/", ""), http.StatusNotFound, make(http.Header), ""}, + {"POST root URL should return 404 not found with no headers", + buildRequestTLS("POST", "/", ""), http.StatusNotFound, make(http.Header), ""}, + {"GET /.well-known should return 404 not found with no headers", + buildRequestTLS("GET", "/.well-known", ""), http.StatusNotFound, make(http.Header), ""}, + {"POST /.well-known should return 404 not found with no headers", + buildRequestTLS("POST", "/.well-known", ""), http.StatusNotFound, make(http.Header), ""}, + + /* + 4.2. Performing a WebFinger Query + */ + + /* + A WebFinger client issues a query using the GET method to the well- + known [3] resource identified by the URI whose path component is + "/.well-known/webfinger" and whose query component MUST include the + "resource" parameter exactly once and set to the value of the URI for + which information is being sought. + */ + {"GET Webfinger resource should return valid response with default headers", + buildRequestTLS("GET", WebFingerPath, "resource=acct:hello@domain"), http.StatusOK, defaultHeaders, + `{"links":[{"href":"x","ref":"x"},{"href":"y","ref":"y"},{"href":"z","ref":"z"}]}`}, + + {"POST Webfinger URL should fail with MethodNotAllowed", + buildRequestTLS("POST", WebFingerPath, ""), http.StatusMethodNotAllowed, defaultHeaders, ""}, + {"GET multiple resources should fail with BadRequest", + buildRequestTLS("GET", WebFingerPath, "resource=acct:hello@domain&resource=acct:hello2@domain"), http.StatusBadRequest, defaultHeaders, ""}, + + /* + The "rel" parameter MAY be included multiple times in order to + request multiple link relation types. + */ + {"GET Webfinger resource with rel should filter results", + buildRequestTLS("GET", WebFingerPath, "resource=acct:hello@domain&rel=x&rel=y"), http.StatusOK, defaultHeaders, + `{"links":[{"href":"x","ref":"x"},{"href":"y","ref":"y"}]}`}, + + /* + A WebFinger resource MAY redirect the client; if it does, the + redirection MUST only be to an "https" URI and the client MUST + perform certificate validation again when redirected. + */ + {"GET non-TLS should redirect to TLS", + buildRequest("GET", WebFingerPath, "resource=acct:hello@domain"), http.StatusSeeOther, plusHeader( + defaultHeaders, kv{"Location", "https://localhost/.well-known/webfinger?resource=acct:hello@domain"}), ""}, + + /* + A WebFinger resource MUST return a JRD as the representation for the + resource if the client requests no other supported format explicitly + via the HTTP "Accept" header. The client MAY include the "Accept" + header to indicate a desired representation; representations other + than JRD might be defined in future specifications. The WebFinger + resource MUST silently ignore any requested representations that it + does not understand or support. The media type used for the JSON + Resource Descriptor (JRD) is "application/jrd+json" (see Section + 10.2). + */ + {"GET with Accept should return as normal", + buildRequestTLS("GET", WebFingerPath, "resource=acct:hello@domain", kv{"Accept", "application/json"}), http.StatusOK, defaultHeaders, + `{"links":[{"href":"x","ref":"x"},{"href":"y","ref":"y"},{"href":"z","ref":"z"}]}`}, + + /* + If the "resource" parameter is a value for which the server has no + information, the server MUST indicate that it was unable to match the + request as per Section 10.4.5 of RFC 2616. + */ + {"GET with a missing user should return 404", + buildRequestTLS("GET", WebFingerPath, "resource=acct:missinguser@domain"), http.StatusNotFound, defaultHeaders, ""}, + + /* + If the "resource" parameter is absent or malformed, the WebFinger + resource MUST indicate that the request is bad as per Section 10.4.1 + of RFC 2616 [2]. (400 bad request) + */ + {"GET with no resource should fail with BadRequest", + buildRequestTLS("GET", WebFingerPath, ""), http.StatusBadRequest, defaultHeaders, ""}, + {"GET with malformed resource URI should fail with BadRequest", + buildRequestTLS("GET", WebFingerPath, "resource=hello-world"), http.StatusBadRequest, defaultHeaders, ""}, + {"GET with http resource URI should fail with BadRequest", + buildRequestTLS("GET", WebFingerPath, "resource=http://hello-world"), http.StatusBadRequest, defaultHeaders, ""}, + } + svc := Default(&dummyUserResolver{}) + + for _, tx := range tests { + w := &dummyResponseWriter{code: 200} + svc.ServeHTTP(w, tx.Input) + // code should be 404 + // headers should be empty + body := strings.TrimSpace(string(w.Buffer.Bytes())) + + failed := false + failed = failed || w.code != tx.OutputCode + failed = failed || !compareHeaders(tx.OutputHeaders, w.headers) + failed = failed || body != tx.OutputBody + if failed { + t.Errorf("%s\nHTTP '%v' '%v' => \n\t'%v'\n\t'%v'\n\t'%v';\nexpected \n\t'%v \n\t'%v' \n\t'%v'", + tx.Description, + tx.Input.Method, tx.Input.URL, + w.code, w.headers, body, + tx.OutputCode, tx.OutputHeaders, tx.OutputBody) + } + } + +} diff --git a/link.go b/link.go new file mode 100644 index 0000000..037ab78 --- /dev/null +++ b/link.go @@ -0,0 +1,13 @@ +package webfinger + +// A Link is a series of user details +type Link struct { + HRef string `json:"href"` + Type string `json:"type,omitempty"` + Rel string `json:"ref"` + Properties map[string]*string `json:"properties,omitempty"` + Titles map[string]string `json:"titles,omitempty"` +} + +// Rel allows referencing a subset of the users details +type Rel string diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..dc9a5e5 --- /dev/null +++ b/middleware.go @@ -0,0 +1,21 @@ +package webfinger + +import "net/http" + +// Middleware constant keys +const ( + NoCacheMiddleware string = "NoCache" + CorsMiddleware string = "Cors" + ContentTypeMiddleware string = "Content-Type" +) + +// noCache sets the headers to disable caching +func noCache(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Pragma", "no-cache") +} + +// jrdSetup sets the content-type +func jrdSetup(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/jrd+json") +} diff --git a/resolver.go b/resolver.go new file mode 100644 index 0000000..abc86b3 --- /dev/null +++ b/resolver.go @@ -0,0 +1,21 @@ +package webfinger + +// The Resolver is how the webfinger service looks up a user or resource. The resolver +// must be provided by the developer using this package, as each webfinger +// service may be exposing a different set or users or resources or services. +type Resolver interface { + + // FindUser finds the user given the username and hostname. + FindUser(username string, hostname string, r []Rel) (*Resource, error) + + // DummyUser allows us to return a dummy user to avoid user-enumeration via webfinger 404s. This + // can be done in the webfinger code itself but then it would be obvious which users are real + // and which are not real via differences in how the implementation works vs how + // the general webfinger code works. This does not match the webfinger specification + // but is an extra precaution. Returning a NotFound error here will + // keep the webfinger 404 behavior. + DummyUser(username string, hostname string, r []Rel) (*Resource, error) + + // IsNotFoundError returns true if the given error is a not found error. + IsNotFoundError(err error) bool +} diff --git a/resource.go b/resource.go new file mode 100644 index 0000000..02440ff --- /dev/null +++ b/resource.go @@ -0,0 +1,9 @@ +package webfinger + +// A Resource is the top-level JRD resource object. +type Resource struct { + Subject string `json:"subject,omitempty"` + Aliases []string `json:"aliases,omitempty"` + Properties map[string]string `json:"properties,omitempty"` + Links []Link `json:"links"` +} diff --git a/service.go b/service.go new file mode 100644 index 0000000..36f8c83 --- /dev/null +++ b/service.go @@ -0,0 +1,65 @@ +package webfinger + +import ( + "net/http" + + "github.com/captncraig/cors" +) + +// Service is the webfinger service containing the required +// HTTP handlers and defaults for webfinger implementations. +type Service struct { + + // PreHandlers are invoked at the start of each HTTP method, used to + // setup things like CORS, caching, etc. You can delete or replace + // a handler by setting service.PreHandlers[name] = nil + PreHandlers map[string]http.Handler + + // NotFoundHandler is the handler to invoke when a URL is not matched. It does NOT + // handle the case of a non-existing users unless your Resolver.DummyUser returns + // an error that matches Resolver.IsNotFoundError(err) == true. + NotFoundHandler http.Handler + + // MethodNotSupportedHandler is the handler invoked when an unsupported + // method is called on the webfinger HTTP service. + MethodNotSupportedHandler http.Handler + + // MalformedRequestHandler is the handler invoked if the request routes + // but is malformed in some way. The default behavior is to return 400 BadRequest, + // per the webfinger specification + MalformedRequestHandler http.Handler + + // NoTLSHandler is the handler invoked if the request is not + // a TLS request. The default behavior is to redirect to the TLS + // version of the URL, per the webfinger specification. Setting + // this to nil will allow nonTLS connections, but that is not advised. + NoTLSHandler http.Handler + + // ErrorHandler is the handler invoked when an error is called. The request + // context contains the error in the webfinger.ErrorKey and can be fetched + // via webfinger.ErrorFromContext(ctx) + ErrorHandler http.Handler + + // Resolver is the interface for resolving user details + Resolver Resolver +} + +// Default creates a new service with the default registered handlers +func Default(ur Resolver) *Service { + var c = cors.Default() + + s := &Service{} + s.Resolver = ur + s.ErrorHandler = http.HandlerFunc(s.defaultErrorHandler) + s.NotFoundHandler = http.HandlerFunc(s.defaultNotFoundHandler) + s.MethodNotSupportedHandler = http.HandlerFunc(s.defaultMethodNotSupportedHandler) + s.MalformedRequestHandler = http.HandlerFunc(s.defaultMalformedRequestHandler) + s.NoTLSHandler = http.HandlerFunc(s.defaultNoTLSHandler) + + s.PreHandlers = make(map[string]http.Handler) + s.PreHandlers[NoCacheMiddleware] = http.HandlerFunc(noCache) + s.PreHandlers[CorsMiddleware] = http.HandlerFunc(c.HandleRequest) + s.PreHandlers[ContentTypeMiddleware] = http.HandlerFunc(jrdSetup) + + return s +}