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 }