138 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			138 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package auth
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/micro/go-micro/v2/auth"
 | |
| 	"github.com/micro/go-micro/v2/logger"
 | |
| )
 | |
| 
 | |
| // CombinedAuthHandler wraps a server and authenticates requests
 | |
| func CombinedAuthHandler(h http.Handler) http.Handler {
 | |
| 	return authHandler{
 | |
| 		handler: h,
 | |
| 		auth:    auth.DefaultAuth,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type authHandler struct {
 | |
| 	handler http.Handler
 | |
| 	auth    auth.Auth
 | |
| }
 | |
| 
 | |
| func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | |
| 	// Determine the namespace
 | |
| 	namespace, err := namespaceFromRequest(req)
 | |
| 	if err != nil {
 | |
| 		logger.Error(err)
 | |
| 		namespace = auth.DefaultNamespace
 | |
| 	}
 | |
| 
 | |
| 	// Set the namespace in the header
 | |
| 	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{Namespace: namespace}
 | |
| 	}
 | |
| 
 | |
| 	// Check the accounts namespace matches the namespace we're operating
 | |
| 	// within. If not forbid the request and log the occurance.
 | |
| 	if acc.Namespace != namespace {
 | |
| 		logger.Warnf("Cross namespace request forbidden: account %v (%v) requested access to %v in the %v namespace", acc.ID, acc.Namespace, req.URL.Path, namespace)
 | |
| 		w.WriteHeader(http.StatusForbidden)
 | |
| 	}
 | |
| 
 | |
| 	// Perform the verification check to see if the account has access to
 | |
| 	// the resource they're requesting
 | |
| 	err = h.auth.Verify(acc, &auth.Resource{
 | |
| 		Type:      "service",
 | |
| 		Name:      "go.micro.web",
 | |
| 		Endpoint:  req.URL.Path,
 | |
| 		Namespace: namespace,
 | |
| 	})
 | |
| 
 | |
| 	// The account has the necessary permissions to access the resource
 | |
| 	if err == nil {
 | |
| 		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 {
 | |
| 		w.WriteHeader(http.StatusForbidden)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// If there is no auth login url set, 401
 | |
| 	loginURL := h.auth.Options().LoginURL
 | |
| 	if loginURL == "" {
 | |
| 		w.WriteHeader(http.StatusUnauthorized)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Redirect to the login path
 | |
| 	params := url.Values{"redirect_to": {req.URL.Path}}
 | |
| 	loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode())
 | |
| 	http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect)
 | |
| }
 | |
| 
 | |
| func namespaceFromRequest(req *http.Request) (string, error) {
 | |
| 	// determine the host, e.g. dev.micro.mu:8080
 | |
| 	host := req.URL.Hostname()
 | |
| 	if len(host) == 0 {
 | |
| 		// fallback to req.Host
 | |
| 		host, _, _ = net.SplitHostPort(req.Host)
 | |
| 	}
 | |
| 
 | |
| 	logger.Infof("Host is %v", host)
 | |
| 
 | |
| 	// check for an ip address
 | |
| 	if net.ParseIP(host) != nil {
 | |
| 		return auth.DefaultNamespace, nil
 | |
| 	}
 | |
| 
 | |
| 	// check for dev enviroment
 | |
| 	if host == "localhost" || host == "127.0.0.1" {
 | |
| 		return auth.DefaultNamespace, nil
 | |
| 	}
 | |
| 
 | |
| 	// if host is not a subdomain, deturn default namespace
 | |
| 	comps := strings.Split(host, ".")
 | |
| 	if len(comps) != 3 {
 | |
| 		return auth.DefaultNamespace, nil
 | |
| 	}
 | |
| 
 | |
| 	// check for the micro.mu domain
 | |
| 	domain := fmt.Sprintf("%v.%v", comps[1], comps[2])
 | |
| 	if domain == "micro.mu" {
 | |
| 		return auth.DefaultNamespace, nil
 | |
| 	}
 | |
| 
 | |
| 	// return the subdomain as the host
 | |
| 	return comps[0], nil
 | |
| }
 |