API Wrappers
This commit is contained in:
parent
67cd59d7bc
commit
e907d24e3b
@ -11,12 +11,6 @@ var (
|
|||||||
ErrInvalidPath = errors.New("invalid path")
|
ErrInvalidPath = errors.New("invalid path")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NamespaceResolver resolves request to the namespace
|
|
||||||
type NamespaceResolver interface {
|
|
||||||
Resolve(r *http.Request) string
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolver resolves requests to endpoints
|
// Resolver resolves requests to endpoints
|
||||||
type Resolver interface {
|
type Resolver interface {
|
||||||
Resolve(r *http.Request) (*Endpoint, error)
|
Resolve(r *http.Request) (*Endpoint, error)
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/micro/go-micro/v2/api/resolver"
|
|
||||||
"github.com/micro/go-micro/v2/auth"
|
|
||||||
"github.com/micro/go-micro/v2/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CombinedAuthHandler wraps a server and authenticates requests
|
|
||||||
func CombinedAuthHandler(r resolver.Resolver, nr resolver.NamespaceResolver, h http.Handler) http.Handler {
|
|
||||||
return authHandler{
|
|
||||||
handler: h,
|
|
||||||
resolver: r,
|
|
||||||
nsResolver: nr,
|
|
||||||
auth: auth.DefaultAuth,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type authHandler struct {
|
|
||||||
handler http.Handler
|
|
||||||
auth auth.Auth
|
|
||||||
resolver resolver.Resolver
|
|
||||||
nsResolver resolver.NamespaceResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
// Determine the namespace and set it in the header
|
|
||||||
namespace := h.nsResolver.Resolve(req)
|
|
||||||
req.Header.Set(auth.NamespaceKey, namespace)
|
|
||||||
|
|
||||||
// Extract the token from the request
|
|
||||||
var token string
|
|
||||||
if header := req.Header.Get("Authorization"); len(header) > 0 {
|
|
||||||
// Extract the auth token from the request
|
|
||||||
if strings.HasPrefix(header, auth.BearerScheme) {
|
|
||||||
token = header[len(auth.BearerScheme):]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Get the token out the cookies if not provided in headers
|
|
||||||
if c, err := req.Cookie("micro-token"); err == nil && c != nil {
|
|
||||||
token = strings.TrimPrefix(c.Value, auth.TokenCookieName+"=")
|
|
||||||
req.Header.Set("Authorization", auth.BearerScheme+token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the account using the token, fallback to a blank account
|
|
||||||
// since some endpoints can be unauthenticated, so the lack of an
|
|
||||||
// account doesn't necesserially mean a forbidden request
|
|
||||||
acc, err := h.auth.Inspect(token)
|
|
||||||
if err != nil {
|
|
||||||
acc = &auth.Account{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the name of the service being requested
|
|
||||||
endpoint, err := h.resolver.Resolve(req)
|
|
||||||
if err == resolver.ErrInvalidPath || err == resolver.ErrNotFound {
|
|
||||||
// a file not served by the resolver has been requested (e.g. favicon.ico)
|
|
||||||
endpoint = &resolver.Endpoint{Path: req.URL.Path}
|
|
||||||
} else if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
http.Error(w, err.Error(), 500)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
// set the endpoint in the context so it can be used to resolve
|
|
||||||
// the request later
|
|
||||||
ctx := context.WithValue(req.Context(), resolver.Endpoint{}, endpoint)
|
|
||||||
*req = *req.Clone(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct the resource name, e.g. home => go.micro.web.home
|
|
||||||
resName := namespace
|
|
||||||
if len(endpoint.Name) > 0 {
|
|
||||||
resName = namespace + "." + endpoint.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine the resource path. there is an inconsistency in how resolvers
|
|
||||||
// use method, some use it as Users.ReadUser (the rpc method), and others
|
|
||||||
// use it as the HTTP method, e.g GET. TODO: Refactor this to make it consistent.
|
|
||||||
resEndpoint := endpoint.Path
|
|
||||||
if len(endpoint.Path) == 0 {
|
|
||||||
resEndpoint = endpoint.Method
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the verification check to see if the account has access to
|
|
||||||
// the resource they're requesting
|
|
||||||
res := &auth.Resource{Type: "service", Name: resName, Endpoint: resEndpoint, Namespace: namespace}
|
|
||||||
if err := h.auth.Verify(acc, res); err == nil {
|
|
||||||
// The account has the necessary permissions to access the resource
|
|
||||||
h.handler.ServeHTTP(w, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The account is set, but they don't have enough permissions, hence
|
|
||||||
// we return a forbidden error.
|
|
||||||
if len(acc.ID) > 0 {
|
|
||||||
http.Error(w, "Forbidden request", 403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is no auth login url set, 401
|
|
||||||
loginURL := h.auth.Options().LoginURL
|
|
||||||
if loginURL == "" {
|
|
||||||
http.Error(w, "unauthorized request", 401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to the login path
|
|
||||||
params := url.Values{"redirect_to": {req.URL.String()}}
|
|
||||||
loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode())
|
|
||||||
http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect)
|
|
||||||
}
|
|
@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/micro/go-micro/v2/api/server"
|
"github.com/micro/go-micro/v2/api/server"
|
||||||
"github.com/micro/go-micro/v2/api/server/auth"
|
|
||||||
"github.com/micro/go-micro/v2/api/server/cors"
|
"github.com/micro/go-micro/v2/api/server/cors"
|
||||||
"github.com/micro/go-micro/v2/logger"
|
"github.com/micro/go-micro/v2/logger"
|
||||||
)
|
)
|
||||||
@ -53,7 +52,11 @@ func (s *httpServer) Init(opts ...server.Option) error {
|
|||||||
|
|
||||||
func (s *httpServer) Handle(path string, handler http.Handler) {
|
func (s *httpServer) Handle(path string, handler http.Handler) {
|
||||||
h := handlers.CombinedLoggingHandler(os.Stdout, handler)
|
h := handlers.CombinedLoggingHandler(os.Stdout, handler)
|
||||||
h = auth.CombinedAuthHandler(s.opts.Resolver, s.opts.NamespaceResolver, handler)
|
|
||||||
|
// apply the wrappers, e.g. auth
|
||||||
|
for _, wrapper := range s.opts.Wrappers {
|
||||||
|
h = wrapper(h)
|
||||||
|
}
|
||||||
|
|
||||||
if s.opts.EnableCORS {
|
if s.opts.EnableCORS {
|
||||||
h = cors.CombinedCORSHandler(h)
|
h = cors.CombinedCORSHandler(h)
|
||||||
|
@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/micro/go-micro/v2/api/resolver"
|
"github.com/micro/go-micro/v2/api/resolver"
|
||||||
"github.com/micro/go-micro/v2/api/server/acme"
|
"github.com/micro/go-micro/v2/api/server/acme"
|
||||||
@ -10,14 +11,22 @@ import (
|
|||||||
type Option func(o *Options)
|
type Option func(o *Options)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
EnableACME bool
|
EnableACME bool
|
||||||
EnableCORS bool
|
EnableCORS bool
|
||||||
ACMEProvider acme.Provider
|
ACMEProvider acme.Provider
|
||||||
EnableTLS bool
|
EnableTLS bool
|
||||||
ACMEHosts []string
|
ACMEHosts []string
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
Resolver resolver.Resolver
|
Resolver resolver.Resolver
|
||||||
NamespaceResolver resolver.NamespaceResolver
|
Wrappers []Wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
type Wrapper func(h http.Handler) http.Handler
|
||||||
|
|
||||||
|
func WrapHandler(w Wrapper) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Wrappers = append(o.Wrappers, w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnableCORS(b bool) Option {
|
func EnableCORS(b bool) Option {
|
||||||
@ -61,9 +70,3 @@ func Resolver(r resolver.Resolver) Option {
|
|||||||
o.Resolver = r
|
o.Resolver = r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NamespaceResolver(r resolver.NamespaceResolver) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.NamespaceResolver = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user