From 7206d5f964ee89a1b09c3566bbd47674e847b5bf Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 09:40:40 +0100
Subject: [PATCH 01/15] Add Namespace to CombinedAuthHandler

---
 api/server/auth/auth.go      | 80 ++++++++++++++++++------------------
 api/server/auth/auth_test.go | 34 +++++++++++++++
 api/server/http/http.go      |  2 +-
 api/server/options.go        | 23 +++++++----
 4 files changed, 91 insertions(+), 48 deletions(-)
 create mode 100644 api/server/auth/auth_test.go

diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index f0e38562..07d28068 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -15,38 +15,32 @@ import (
 )
 
 // CombinedAuthHandler wraps a server and authenticates requests
-func CombinedAuthHandler(namespace string, r resolver.Resolver, h http.Handler) http.Handler {
+func CombinedAuthHandler(prefix, namespace string, r resolver.Resolver, h http.Handler) http.Handler {
 	if r == nil {
 		r = path.NewResolver()
 	}
-	if len(namespace) == 0 {
-		namespace = "go.micro"
-	}
 
 	return authHandler{
-		handler:   h,
-		resolver:  r,
-		auth:      auth.DefaultAuth,
-		namespace: namespace,
+		handler:       h,
+		resolver:      r,
+		auth:          auth.DefaultAuth,
+		servicePrefix: prefix,
+		namespace:     namespace,
 	}
 }
 
 type authHandler struct {
-	handler   http.Handler
-	auth      auth.Auth
-	resolver  resolver.Resolver
-	namespace string
+	handler       http.Handler
+	auth          auth.Auth
+	resolver      resolver.Resolver
+	namespace     string
+	servicePrefix string
 }
 
 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
+	// Determine the namespace and set it in the header
+	namespace := h.namespaceFromRequest(req)
+	fmt.Printf("Namespace is %v\n", namespace)
 	req.Header.Set(auth.NamespaceKey, namespace)
 
 	// Extract the token from the request
@@ -96,7 +90,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	}
 
 	// construct the resource name, e.g. home => go.micro.web.home
-	resName := h.namespace
+	resName := h.servicePrefix
 	if len(endpoint.Name) > 0 {
 		resName = resName + "." + endpoint.Name
 	}
@@ -138,39 +132,47 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect)
 }
 
-func namespaceFromRequest(req *http.Request) (string, error) {
-	// needed to tmp debug host in prod. will be removed.
-	logger.Infof("Host is '%v'; URL Host is '%v'; URL Hostname is '%v'", req.Host, req.URL.Host, req.URL.Hostname())
+func (h authHandler) namespaceFromRequest(req *http.Request) string {
+	// check to see what the provided namespace is, we only do
+	// domain mapping if the namespace is set to 'domain'
+	if h.namespace != "domain" {
+		return h.namespace
+	}
 
 	// 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)
+	var host string
+	if h, _, err := net.SplitHostPort(req.Host); err == nil {
+		host = h // host does contain a port
+	} else {
+		host = req.Host // host does not contain a port
+	}
+
+	// check for the micro.mu domain
+	if strings.HasSuffix(host, "micro.mu") {
+		return auth.DefaultNamespace
 	}
 
 	// check for an ip address
 	if net.ParseIP(host) != nil {
-		return auth.DefaultNamespace, nil
+		return auth.DefaultNamespace
 	}
 
 	// check for dev enviroment
 	if host == "localhost" || host == "127.0.0.1" {
-		return auth.DefaultNamespace, nil
+		return auth.DefaultNamespace
 	}
 
 	// if host is not a subdomain, deturn default namespace
 	comps := strings.Split(host, ".")
-	if len(comps) != 3 {
-		return auth.DefaultNamespace, nil
+	if len(comps) < 3 {
+		return auth.DefaultNamespace
 	}
 
-	// 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 reversed subdomain as the namespace
+	nComps := comps[0 : len(comps)-2]
+	for i := len(nComps)/2 - 1; i >= 0; i-- {
+		opp := len(nComps) - 1 - i
+		nComps[i], nComps[opp] = nComps[opp], nComps[i]
 	}
-
-	// return the subdomain as the host
-	return comps[0], nil
+	return strings.Join(nComps, ".")
 }
diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go
new file mode 100644
index 00000000..cf454e28
--- /dev/null
+++ b/api/server/auth/auth_test.go
@@ -0,0 +1,34 @@
+package auth
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/micro/go-micro/v2/auth"
+)
+
+func TestNamespaceFromRequest(t *testing.T) {
+	tt := []struct {
+		Host      string
+		Namespace string
+	}{
+		{Host: "micro.mu", Namespace: auth.DefaultNamespace},
+		{Host: "web.micro.mu", Namespace: auth.DefaultNamespace},
+		{Host: "api.micro.mu", Namespace: auth.DefaultNamespace},
+		{Host: "myapp.com", Namespace: auth.DefaultNamespace},
+		{Host: "staging.myapp.com", Namespace: "staging"},
+		{Host: "staging.myapp.m3o.app", Namespace: "myapp.staging"},
+		{Host: "127.0.0.1", Namespace: auth.DefaultNamespace},
+		{Host: "localhost", Namespace: auth.DefaultNamespace},
+		{Host: "81.151.101.146", Namespace: auth.DefaultNamespace},
+	}
+
+	for _, tc := range tt {
+		t.Run(tc.Host, func(t *testing.T) {
+			ns := namespaceFromRequest(&http.Request{Host: tc.Host})
+			if ns != tc.Namespace {
+				t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns)
+			}
+		})
+	}
+}
diff --git a/api/server/http/http.go b/api/server/http/http.go
index 2599d2db..02238aa9 100644
--- a/api/server/http/http.go
+++ b/api/server/http/http.go
@@ -53,7 +53,7 @@ func (s *httpServer) Init(opts ...server.Option) error {
 
 func (s *httpServer) Handle(path string, handler http.Handler) {
 	h := handlers.CombinedLoggingHandler(os.Stdout, handler)
-	h = auth.CombinedAuthHandler(s.opts.Namespace, s.opts.Resolver, handler)
+	h = auth.CombinedAuthHandler(s.opts.ServiceNamespace, s.opts.Namespace, s.opts.Resolver, handler)
 
 	if s.opts.EnableCORS {
 		h = cors.CombinedCORSHandler(h)
diff --git a/api/server/options.go b/api/server/options.go
index 5d167ced..9d429436 100644
--- a/api/server/options.go
+++ b/api/server/options.go
@@ -10,14 +10,15 @@ import (
 type Option func(o *Options)
 
 type Options struct {
-	EnableACME   bool
-	EnableCORS   bool
-	ACMEProvider acme.Provider
-	EnableTLS    bool
-	ACMEHosts    []string
-	TLSConfig    *tls.Config
-	Namespace    string
-	Resolver     resolver.Resolver
+	EnableACME       bool
+	EnableCORS       bool
+	ACMEProvider     acme.Provider
+	EnableTLS        bool
+	ACMEHosts        []string
+	TLSConfig        *tls.Config
+	Resolver         resolver.Resolver
+	Namespace        string
+	ServiceNamespace string
 }
 
 func EnableCORS(b bool) Option {
@@ -56,6 +57,12 @@ func TLSConfig(t *tls.Config) Option {
 	}
 }
 
+func ServiceNamespace(n string) Option {
+	return func(o *Options) {
+		o.ServiceNamespace = n
+	}
+}
+
 func Namespace(n string) Option {
 	return func(o *Options) {
 		o.Namespace = n

From 11e1e9120a90d20d8d4349161a2b479e07a2686a Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 10:10:37 +0100
Subject: [PATCH 02/15] Remove debugging

---
 api/server/auth/auth.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index 5837dfa8..ab93443f 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -40,7 +40,6 @@ type authHandler struct {
 func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	// Determine the namespace and set it in the header
 	namespace := h.namespaceFromRequest(req)
-	fmt.Printf("Namespace is %v\n", namespace)
 	req.Header.Set(auth.NamespaceKey, namespace)
 
 	// Extract the token from the request

From 501fc5c05907ed876d66901983026d20b87a47c3 Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 10:28:39 +0100
Subject: [PATCH 03/15] Refactor to use publicsuffix

---
 api/server/auth/auth.go      | 34 ++++++++++++++++++++++------------
 api/server/auth/auth_test.go |  5 ++++-
 go.sum                       |  1 +
 3 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index ab93443f..eb064421 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -12,6 +12,7 @@ import (
 	"github.com/micro/go-micro/v2/api/resolver/path"
 	"github.com/micro/go-micro/v2/auth"
 	"github.com/micro/go-micro/v2/logger"
+	"golang.org/x/net/publicsuffix"
 )
 
 // CombinedAuthHandler wraps a server and authenticates requests
@@ -39,7 +40,7 @@ type authHandler struct {
 
 func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	// Determine the namespace and set it in the header
-	namespace := h.namespaceFromRequest(req)
+	namespace := h.NamespaceFromRequest(req)
 	req.Header.Set(auth.NamespaceKey, namespace)
 
 	// Extract the token from the request
@@ -131,7 +132,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect)
 }
 
-func (h authHandler) namespaceFromRequest(req *http.Request) string {
+func (h authHandler) NamespaceFromRequest(req *http.Request) string {
 	// check to see what the provided namespace is, we only do
 	// domain mapping if the namespace is set to 'domain'
 	if h.namespace != "domain" {
@@ -161,18 +162,27 @@ func (h authHandler) namespaceFromRequest(req *http.Request) string {
 		return auth.DefaultNamespace
 	}
 
-	// TODO: this logic needs to be replaced with usage of publicsuffix
-	// if host is not a subdomain, deturn default namespace
-	comps := strings.Split(host, ".")
-	if len(comps) < 3 {
+	// extract the top level domain plus one (e.g. 'myapp.com')
+	domain, err := publicsuffix.EffectiveTLDPlusOne(host)
+	if err != nil {
+		logger.Debugf("Unable to extract domain from %v", host)
 		return auth.DefaultNamespace
 	}
 
-	// return the reversed subdomain as the namespace
-	nComps := comps[0 : len(comps)-2]
-	for i := len(nComps)/2 - 1; i >= 0; i-- {
-		opp := len(nComps) - 1 - i
-		nComps[i], nComps[opp] = nComps[opp], nComps[i]
+	// check to see if the domain is the host, in this
+	// case we return the default namespace
+	if domain == host {
+		return auth.DefaultNamespace
 	}
-	return strings.Join(nComps, ".")
+
+	// remove the domain from the host, leaving the subdomain
+	subdomain := strings.TrimSuffix(host, "."+domain)
+
+	// return the reversed subdomain as the namespace
+	comps := strings.Split(subdomain, ".")
+	for i := len(comps)/2 - 1; i >= 0; i-- {
+		opp := len(comps) - 1 - i
+		comps[i], comps[opp] = comps[opp], comps[i]
+	}
+	return strings.Join(comps, ".")
 }
diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go
index cf454e28..04923e35 100644
--- a/api/server/auth/auth_test.go
+++ b/api/server/auth/auth_test.go
@@ -13,6 +13,7 @@ func TestNamespaceFromRequest(t *testing.T) {
 		Namespace string
 	}{
 		{Host: "micro.mu", Namespace: auth.DefaultNamespace},
+		{Host: "micro.com.au", Namespace: auth.DefaultNamespace},
 		{Host: "web.micro.mu", Namespace: auth.DefaultNamespace},
 		{Host: "api.micro.mu", Namespace: auth.DefaultNamespace},
 		{Host: "myapp.com", Namespace: auth.DefaultNamespace},
@@ -23,9 +24,11 @@ func TestNamespaceFromRequest(t *testing.T) {
 		{Host: "81.151.101.146", Namespace: auth.DefaultNamespace},
 	}
 
+	h := &authHandler{namespace: "domain"}
+
 	for _, tc := range tt {
 		t.Run(tc.Host, func(t *testing.T) {
-			ns := namespaceFromRequest(&http.Request{Host: tc.Host})
+			ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host})
 			if ns != tc.Namespace {
 				t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns)
 			}
diff --git a/go.sum b/go.sum
index 7d8a1ee1..b7d9c90b 100644
--- a/go.sum
+++ b/go.sum
@@ -461,6 +461,7 @@ golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200222125558-5a598a2470a0 h1:MsuvTghUPjX762sGLnGsxC3HM0B5r83wEtYcYR8/vRs=
 golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

From bd23dc1f18fd0a660bc96e9012a0adfbcccf3c4a Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 10:34:26 +0100
Subject: [PATCH 04/15] Improve micro.mu check

---
 api/server/auth/auth.go | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index eb064421..84bebb49 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -147,11 +147,6 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string {
 		host = req.Host // host does not contain a port
 	}
 
-	// check for the micro.mu domain
-	if strings.HasSuffix(host, "micro.mu") {
-		return auth.DefaultNamespace
-	}
-
 	// check for an ip address
 	if net.ParseIP(host) != nil {
 		return auth.DefaultNamespace
@@ -169,9 +164,9 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string {
 		return auth.DefaultNamespace
 	}
 
-	// check to see if the domain is the host, in this
-	// case we return the default namespace
-	if domain == host {
+	// check to see if the domain matches the host of micr.mu, in
+	// these casees we return the default namespace
+	if domain == host || domain == "micro.mu" {
 		return auth.DefaultNamespace
 	}
 

From 316424f0f7c7f0bac8f68447550518a54e130226 Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 10:35:57 +0100
Subject: [PATCH 05/15] Fix comments typo

---
 api/server/auth/auth.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index 84bebb49..ad6a8c87 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -164,8 +164,8 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string {
 		return auth.DefaultNamespace
 	}
 
-	// check to see if the domain matches the host of micr.mu, in
-	// these casees we return the default namespace
+	// check to see if the domain matches the host of micro.mu, in
+	// these cases we return the default namespace
 	if domain == host || domain == "micro.mu" {
 		return auth.DefaultNamespace
 	}

From 9e116731b10f5c21b6cd729d7606e791d8f144e3 Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 10:38:27 +0100
Subject: [PATCH 06/15] ServiceNamespace => ServicePrefix in api server

---
 api/server/options.go | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/api/server/options.go b/api/server/options.go
index 9d429436..6fd61672 100644
--- a/api/server/options.go
+++ b/api/server/options.go
@@ -10,15 +10,15 @@ import (
 type Option func(o *Options)
 
 type Options struct {
-	EnableACME       bool
-	EnableCORS       bool
-	ACMEProvider     acme.Provider
-	EnableTLS        bool
-	ACMEHosts        []string
-	TLSConfig        *tls.Config
-	Resolver         resolver.Resolver
-	Namespace        string
-	ServiceNamespace string
+	EnableACME    bool
+	EnableCORS    bool
+	ACMEProvider  acme.Provider
+	EnableTLS     bool
+	ACMEHosts     []string
+	TLSConfig     *tls.Config
+	Resolver      resolver.Resolver
+	Namespace     string
+	ServicePrefix string
 }
 
 func EnableCORS(b bool) Option {
@@ -57,9 +57,9 @@ func TLSConfig(t *tls.Config) Option {
 	}
 }
 
-func ServiceNamespace(n string) Option {
+func ServicePrefix(n string) Option {
 	return func(o *Options) {
-		o.ServiceNamespace = n
+		o.ServicePrefix = n
 	}
 }
 

From 977934f8fd8ecfa4984ecb3f8097f6a8a1c453da Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 10:39:27 +0100
Subject: [PATCH 07/15] ServiceNamespace => ServicePrefix in api server

---
 api/server/http/http.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/server/http/http.go b/api/server/http/http.go
index 02238aa9..3d1030ac 100644
--- a/api/server/http/http.go
+++ b/api/server/http/http.go
@@ -53,7 +53,7 @@ func (s *httpServer) Init(opts ...server.Option) error {
 
 func (s *httpServer) Handle(path string, handler http.Handler) {
 	h := handlers.CombinedLoggingHandler(os.Stdout, handler)
-	h = auth.CombinedAuthHandler(s.opts.ServiceNamespace, s.opts.Namespace, s.opts.Resolver, handler)
+	h = auth.CombinedAuthHandler(s.opts.ServicePrefix, s.opts.Namespace, s.opts.Resolver, handler)
 
 	if s.opts.EnableCORS {
 		h = cors.CombinedCORSHandler(h)

From 76f6f8031847c8c373bb2df0ac217834a4acbede Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 11:23:21 +0100
Subject: [PATCH 08/15] Default to Hostname

---
 api/server/auth/auth.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index 43c3a888..0faf1447 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -140,7 +140,7 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string {
 	}
 
 	// determine the host, e.g. dev.micro.mu:8080
-	var host string
+	host := req.URL.Hostname()
 	if h, _, err := net.SplitHostPort(req.Host); err == nil {
 		host = h // host does contain a port
 	} else if strings.Contains(err.Error(), "missing port in address") {

From 05ac3ff274c539e0ab918dc05df0604c322f0fec Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 11:24:13 +0100
Subject: [PATCH 09/15] Tweak

---
 api/server/auth/auth.go | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index 0faf1447..7ae99214 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -141,10 +141,12 @@ func (h authHandler) NamespaceFromRequest(req *http.Request) string {
 
 	// determine the host, e.g. dev.micro.mu:8080
 	host := req.URL.Hostname()
-	if h, _, err := net.SplitHostPort(req.Host); err == nil {
-		host = h // host does contain a port
-	} else if strings.Contains(err.Error(), "missing port in address") {
-		host = req.Host // host does not contain a port
+	if len(host) == 0 {
+		if h, _, err := net.SplitHostPort(req.Host); err == nil {
+			host = h // host does contain a port
+		} else if strings.Contains(err.Error(), "missing port in address") {
+			host = req.Host // host does not contain a port
+		}
 	}
 
 	// check for an ip address

From 9d598836c307bfd4bcf7ba69c7e64e0b0667570b Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 11:37:04 +0100
Subject: [PATCH 10/15] Fix Tests

---
 api/server/auth/auth_test.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go
index 04923e35..7fd09581 100644
--- a/api/server/auth/auth_test.go
+++ b/api/server/auth/auth_test.go
@@ -2,6 +2,7 @@ package auth
 
 import (
 	"net/http"
+	"net/url"
 	"testing"
 
 	"github.com/micro/go-micro/v2/auth"
@@ -28,7 +29,7 @@ func TestNamespaceFromRequest(t *testing.T) {
 
 	for _, tc := range tt {
 		t.Run(tc.Host, func(t *testing.T) {
-			ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host})
+			ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host, URL: &url.URL{Host: tc.Host}})
 			if ns != tc.Namespace {
 				t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns)
 			}

From 3df87510a150621ec8b9168774410a9521aa1c60 Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 12:46:44 +0100
Subject: [PATCH 11/15] Add namespace

---
 auth/auth.go      | 2 +-
 auth/options.go   | 9 +++++++++
 config/cmd/cmd.go | 9 +++++++++
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/auth/auth.go b/auth/auth.go
index 5f954776..e34051fa 100644
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -89,7 +89,7 @@ type Token struct {
 
 const (
 	// DefaultNamespace used for auth
-	DefaultNamespace = "micro"
+	DefaultNamespace = "go.micro"
 	// NamespaceKey is the key used when storing the namespace in metadata
 	NamespaceKey = "Micro-Namespace"
 	// MetadataKey is the key used when storing the account in metadata
diff --git a/auth/options.go b/auth/options.go
index 929cf674..d3c0f4ea 100644
--- a/auth/options.go
+++ b/auth/options.go
@@ -8,6 +8,8 @@ import (
 )
 
 type Options struct {
+	// Namespace the service belongs to
+	Namespace string
 	// ID is the services auth ID
 	ID string
 	// Secret is used to authenticate the service
@@ -28,6 +30,13 @@ type Options struct {
 
 type Option func(o *Options)
 
+// Namespace the service belongs to
+func Namespace(n string) Option {
+	return func(o *Options) {
+		o.Namespace = n
+	}
+}
+
 // Store to back auth
 func Store(s store.Store) Option {
 	return func(o *Options) {
diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go
index 34e417dd..3eb6fb5e 100644
--- a/config/cmd/cmd.go
+++ b/config/cmd/cmd.go
@@ -314,6 +314,12 @@ var (
 			EnvVars: []string{"MICRO_CONFIG"},
 			Usage:   "The source of the config to be used to get configuration",
 		},
+		&cli.StringFlag{
+			Name:    "namespace",
+			EnvVars: []string{"MICRO_NAMESPACE"},
+			Usage:   "The namespace the service belongs to",
+			Value:   auth.DefaultNamespace,
+		},
 	}
 
 	DefaultBrokers = map[string]func(...broker.Option) broker.Broker{
@@ -678,6 +684,9 @@ func (c *cmd) Before(ctx *cli.Context) error {
 			ctx.String("auth_id"), ctx.String("auth_secret"),
 		))
 	}
+	if len(ctx.String("namespace")) > 0 {
+		authOpts = append(authOpts, auth.Namespace(ctx.String("namespace")))
+	}
 
 	if len(ctx.String("auth_public_key")) > 0 {
 		authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key")))

From 4362a885eb824cc583b0d05cb0dd1c884ea0c19a Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 16:24:51 +0100
Subject: [PATCH 12/15] Refactor Namespace Resolver

---
 api/resolver/resolver.go     |  8 +++
 api/server/auth/auth.go      | 96 ++++++------------------------------
 api/server/auth/auth_test.go | 38 --------------
 api/server/http/http.go      |  2 +-
 api/server/options.go        | 35 ++++++-------
 auth/service/service.go      | 12 +++--
 go.sum                       |  4 ++
 util/wrapper/wrapper.go      | 30 ++---------
 8 files changed, 53 insertions(+), 172 deletions(-)
 delete mode 100644 api/server/auth/auth_test.go

diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go
index 12854b19..daa7078d 100644
--- a/api/resolver/resolver.go
+++ b/api/resolver/resolver.go
@@ -11,6 +11,12 @@ var (
 	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
 type Resolver interface {
 	Resolve(r *http.Request) (*Endpoint, error)
@@ -27,6 +33,8 @@ type Endpoint struct {
 	Method string
 	// HTTP Path e.g /greeter.
 	Path string
+	// Namespace, g.g. go.micro
+	Namespace string
 }
 
 type Options struct {
diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
index 7ae99214..deffa3dc 100644
--- a/api/server/auth/auth.go
+++ b/api/server/auth/auth.go
@@ -3,44 +3,35 @@ package auth
 import (
 	"context"
 	"fmt"
-	"net"
 	"net/http"
 	"net/url"
 	"strings"
 
 	"github.com/micro/go-micro/v2/api/resolver"
-	"github.com/micro/go-micro/v2/api/resolver/path"
 	"github.com/micro/go-micro/v2/auth"
 	"github.com/micro/go-micro/v2/logger"
-	"golang.org/x/net/publicsuffix"
 )
 
 // CombinedAuthHandler wraps a server and authenticates requests
-func CombinedAuthHandler(prefix, namespace string, r resolver.Resolver, h http.Handler) http.Handler {
-	if r == nil {
-		r = path.NewResolver()
-	}
-
+func CombinedAuthHandler(r resolver.Resolver, nr resolver.NamespaceResolver, h http.Handler) http.Handler {
 	return authHandler{
-		handler:       h,
-		resolver:      r,
-		auth:          auth.DefaultAuth,
-		servicePrefix: prefix,
-		namespace:     namespace,
+		handler:    h,
+		resolver:   r,
+		nsResolver: nr,
+		auth:       auth.DefaultAuth,
 	}
 }
 
 type authHandler struct {
-	handler       http.Handler
-	auth          auth.Auth
-	resolver      resolver.Resolver
-	namespace     string
-	servicePrefix string
+	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.NamespaceFromRequest(req)
+	namespace := h.nsResolver.Resolve(req)
 	req.Header.Set(auth.NamespaceKey, namespace)
 
 	// Extract the token from the request
@@ -63,14 +54,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	// 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.Debugf("Cross namespace request warning: account %v (%v) requested access to %v in the %v namespace", acc.ID, acc.Namespace, req.URL.Path, namespace)
-		// http.Error(w, "Forbidden namespace", 403)
+		acc = &auth.Account{}
 	}
 
 	// Determine the name of the service being requested
@@ -90,9 +74,9 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	}
 
 	// construct the resource name, e.g. home => go.micro.web.home
-	resName := h.servicePrefix
+	resName := namespace
 	if len(endpoint.Name) > 0 {
-		resName = resName + "." + endpoint.Name
+		resName = namespace + "." + endpoint.Name
 	}
 
 	// determine the resource path. there is an inconsistency in how resolvers
@@ -127,59 +111,7 @@ func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	}
 
 	// Redirect to the login path
-	params := url.Values{"redirect_to": {req.URL.Path}}
+	params := url.Values{"redirect_to": {req.URL.String()}}
 	loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode())
 	http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect)
 }
-
-func (h authHandler) NamespaceFromRequest(req *http.Request) string {
-	// check to see what the provided namespace is, we only do
-	// domain mapping if the namespace is set to 'domain'
-	if h.namespace != "domain" {
-		return h.namespace
-	}
-
-	// determine the host, e.g. dev.micro.mu:8080
-	host := req.URL.Hostname()
-	if len(host) == 0 {
-		if h, _, err := net.SplitHostPort(req.Host); err == nil {
-			host = h // host does contain a port
-		} else if strings.Contains(err.Error(), "missing port in address") {
-			host = req.Host // host does not contain a port
-		}
-	}
-
-	// check for an ip address
-	if net.ParseIP(host) != nil {
-		return auth.DefaultNamespace
-	}
-
-	// check for dev enviroment
-	if host == "localhost" || host == "127.0.0.1" {
-		return auth.DefaultNamespace
-	}
-
-	// extract the top level domain plus one (e.g. 'myapp.com')
-	domain, err := publicsuffix.EffectiveTLDPlusOne(host)
-	if err != nil {
-		logger.Debugf("Unable to extract domain from %v", host)
-		return auth.DefaultNamespace
-	}
-
-	// check to see if the domain matches the host of micro.mu, in
-	// these cases we return the default namespace
-	if domain == host || domain == "micro.mu" {
-		return auth.DefaultNamespace
-	}
-
-	// remove the domain from the host, leaving the subdomain
-	subdomain := strings.TrimSuffix(host, "."+domain)
-
-	// return the reversed subdomain as the namespace
-	comps := strings.Split(subdomain, ".")
-	for i := len(comps)/2 - 1; i >= 0; i-- {
-		opp := len(comps) - 1 - i
-		comps[i], comps[opp] = comps[opp], comps[i]
-	}
-	return strings.Join(comps, ".")
-}
diff --git a/api/server/auth/auth_test.go b/api/server/auth/auth_test.go
deleted file mode 100644
index 7fd09581..00000000
--- a/api/server/auth/auth_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package auth
-
-import (
-	"net/http"
-	"net/url"
-	"testing"
-
-	"github.com/micro/go-micro/v2/auth"
-)
-
-func TestNamespaceFromRequest(t *testing.T) {
-	tt := []struct {
-		Host      string
-		Namespace string
-	}{
-		{Host: "micro.mu", Namespace: auth.DefaultNamespace},
-		{Host: "micro.com.au", Namespace: auth.DefaultNamespace},
-		{Host: "web.micro.mu", Namespace: auth.DefaultNamespace},
-		{Host: "api.micro.mu", Namespace: auth.DefaultNamespace},
-		{Host: "myapp.com", Namespace: auth.DefaultNamespace},
-		{Host: "staging.myapp.com", Namespace: "staging"},
-		{Host: "staging.myapp.m3o.app", Namespace: "myapp.staging"},
-		{Host: "127.0.0.1", Namespace: auth.DefaultNamespace},
-		{Host: "localhost", Namespace: auth.DefaultNamespace},
-		{Host: "81.151.101.146", Namespace: auth.DefaultNamespace},
-	}
-
-	h := &authHandler{namespace: "domain"}
-
-	for _, tc := range tt {
-		t.Run(tc.Host, func(t *testing.T) {
-			ns := h.NamespaceFromRequest(&http.Request{Host: tc.Host, URL: &url.URL{Host: tc.Host}})
-			if ns != tc.Namespace {
-				t.Errorf("Expected namespace %v for host %v, actually got %v", tc.Namespace, tc.Host, ns)
-			}
-		})
-	}
-}
diff --git a/api/server/http/http.go b/api/server/http/http.go
index 3d1030ac..c35d24b7 100644
--- a/api/server/http/http.go
+++ b/api/server/http/http.go
@@ -53,7 +53,7 @@ func (s *httpServer) Init(opts ...server.Option) error {
 
 func (s *httpServer) Handle(path string, handler http.Handler) {
 	h := handlers.CombinedLoggingHandler(os.Stdout, handler)
-	h = auth.CombinedAuthHandler(s.opts.ServicePrefix, s.opts.Namespace, s.opts.Resolver, handler)
+	h = auth.CombinedAuthHandler(s.opts.Resolver, s.opts.NamespaceResolver, handler)
 
 	if s.opts.EnableCORS {
 		h = cors.CombinedCORSHandler(h)
diff --git a/api/server/options.go b/api/server/options.go
index 6fd61672..9aa14579 100644
--- a/api/server/options.go
+++ b/api/server/options.go
@@ -10,15 +10,14 @@ import (
 type Option func(o *Options)
 
 type Options struct {
-	EnableACME    bool
-	EnableCORS    bool
-	ACMEProvider  acme.Provider
-	EnableTLS     bool
-	ACMEHosts     []string
-	TLSConfig     *tls.Config
-	Resolver      resolver.Resolver
-	Namespace     string
-	ServicePrefix string
+	EnableACME        bool
+	EnableCORS        bool
+	ACMEProvider      acme.Provider
+	EnableTLS         bool
+	ACMEHosts         []string
+	TLSConfig         *tls.Config
+	Resolver          resolver.Resolver
+	NamespaceResolver resolver.NamespaceResolver
 }
 
 func EnableCORS(b bool) Option {
@@ -57,20 +56,14 @@ func TLSConfig(t *tls.Config) Option {
 	}
 }
 
-func ServicePrefix(n string) Option {
-	return func(o *Options) {
-		o.ServicePrefix = n
-	}
-}
-
-func Namespace(n string) Option {
-	return func(o *Options) {
-		o.Namespace = n
-	}
-}
-
 func Resolver(r resolver.Resolver) Option {
 	return func(o *Options) {
 		o.Resolver = r
 	}
 }
+
+func NamespaceResolver(r resolver.NamespaceResolver) Option {
+	return func(o *Options) {
+		o.NamespaceResolver = r
+	}
+}
diff --git a/auth/service/service.go b/auth/service/service.go
index 57d6082e..cb1740c7 100644
--- a/auth/service/service.go
+++ b/auth/service/service.go
@@ -158,6 +158,11 @@ func (s *svc) Revoke(role string, res *auth.Resource) error {
 
 // Verify an account has access to a resource
 func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error {
+	// set the namespace on the resource
+	if len(res.Namespace) == 0 {
+		res.Namespace = s.Options().Namespace
+	}
+
 	queries := [][]string{
 		{res.Namespace, res.Type, res.Name, res.Endpoint}, // check for specific role, e.g. service.foo.ListFoo:admin (role is checked in accessForRule)
 		{res.Namespace, res.Type, res.Name, "*"},          // check for wildcard endpoint, e.g. service.foo*
@@ -205,16 +210,15 @@ func (s *svc) Verify(acc *auth.Account, res *auth.Resource) error {
 func (s *svc) Inspect(token string) (*auth.Account, error) {
 	// try to decode JWT locally and fall back to srv if an error occurs
 	if len(strings.Split(token, ".")) == 3 && s.jwt != nil {
-		if acc, err := s.jwt.Inspect(token); err == nil {
-			return acc, nil
-		}
+		return s.jwt.Inspect(token)
 	}
 
+	// the token is not a JWT or we do not have the keys to decode it,
+	// fall back to the auth service
 	rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{Token: token})
 	if err != nil {
 		return nil, err
 	}
-
 	return serializeAccount(rsp.Account), nil
 }
 
diff --git a/go.sum b/go.sum
index b7d9c90b..4de561e2 100644
--- a/go.sum
+++ b/go.sum
@@ -399,6 +399,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
@@ -432,6 +433,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -529,6 +531,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY=
 golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -600,6 +603,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/util/wrapper/wrapper.go b/util/wrapper/wrapper.go
index 100f2f2b..9a7fe54f 100644
--- a/util/wrapper/wrapper.go
+++ b/util/wrapper/wrapper.go
@@ -9,7 +9,6 @@ import (
 	"github.com/micro/go-micro/v2/debug/stats"
 	"github.com/micro/go-micro/v2/debug/trace"
 	"github.com/micro/go-micro/v2/errors"
-	"github.com/micro/go-micro/v2/logger"
 	"github.com/micro/go-micro/v2/metadata"
 	"github.com/micro/go-micro/v2/server"
 )
@@ -155,11 +154,6 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper {
 				return h(ctx, req, rsp)
 			}
 
-			// Check for auth service endpoints which should be excluded from auth
-			if strings.HasPrefix(req.Endpoint(), "Auth.") {
-				return h(ctx, req, rsp)
-			}
-
 			// Extract the token if present. Note: if noop is being used
 			// then the token can be blank without erroring
 			var token string
@@ -172,33 +166,17 @@ func AuthHandler(fn func() auth.Auth) server.HandlerWrapper {
 				token = header[len(auth.BearerScheme):]
 			}
 
-			// Get the namespace for the request
-			namespace, ok := metadata.Get(ctx, auth.NamespaceKey)
-			if !ok {
-				logger.Debugf("Missing request namespace")
-				namespace = auth.DefaultNamespace
-			}
-
 			// Inspect the token and get the account
 			account, err := a.Inspect(token)
 			if err != nil {
-				account = &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 account.Namespace != namespace {
-				logger.Debugf("Cross namespace request forbidden: account %v (%v) requested access to %v %v in the %v namespace",
-					account.ID, account.Namespace, req.Service(), req.Endpoint(), namespace)
-				// return errors.Forbidden(req.Service(), "cross namespace request")
+				account = &auth.Account{}
 			}
 
 			// construct the resource
 			res := &auth.Resource{
-				Type:      "service",
-				Name:      req.Service(),
-				Endpoint:  req.Endpoint(),
-				Namespace: namespace,
+				Type:     "service",
+				Name:     req.Service(),
+				Endpoint: req.Endpoint(),
 			}
 
 			// Verify the caller has access to the resource

From 3735b0e52917ef7af6886cc3738a4eebde522d67 Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 16:27:01 +0100
Subject: [PATCH 13/15] Remove global namespace option

---
 config/cmd/cmd.go | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/config/cmd/cmd.go b/config/cmd/cmd.go
index 3eb6fb5e..34e417dd 100644
--- a/config/cmd/cmd.go
+++ b/config/cmd/cmd.go
@@ -314,12 +314,6 @@ var (
 			EnvVars: []string{"MICRO_CONFIG"},
 			Usage:   "The source of the config to be used to get configuration",
 		},
-		&cli.StringFlag{
-			Name:    "namespace",
-			EnvVars: []string{"MICRO_NAMESPACE"},
-			Usage:   "The namespace the service belongs to",
-			Value:   auth.DefaultNamespace,
-		},
 	}
 
 	DefaultBrokers = map[string]func(...broker.Option) broker.Broker{
@@ -684,9 +678,6 @@ func (c *cmd) Before(ctx *cli.Context) error {
 			ctx.String("auth_id"), ctx.String("auth_secret"),
 		))
 	}
-	if len(ctx.String("namespace")) > 0 {
-		authOpts = append(authOpts, auth.Namespace(ctx.String("namespace")))
-	}
 
 	if len(ctx.String("auth_public_key")) > 0 {
 		authOpts = append(authOpts, auth.PublicKey(ctx.String("auth_public_key")))

From 67cd59d7bcd9c42c0c6b11e51049c0bbb57f27c4 Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 16:27:59 +0100
Subject: [PATCH 14/15] Rename namespace from Resolver.Endpoint

---
 api/resolver/resolver.go | 2 --
 1 file changed, 2 deletions(-)

diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go
index daa7078d..99a10802 100644
--- a/api/resolver/resolver.go
+++ b/api/resolver/resolver.go
@@ -33,8 +33,6 @@ type Endpoint struct {
 	Method string
 	// HTTP Path e.g /greeter.
 	Path string
-	// Namespace, g.g. go.micro
-	Namespace string
 }
 
 type Options struct {

From e907d24e3bfafca5b53a0f7acb935c152d6016f7 Mon Sep 17 00:00:00 2001
From: Ben Toogood <ben@micro.mu>
Date: Tue, 7 Apr 2020 19:29:26 +0100
Subject: [PATCH 15/15] API Wrappers

---
 api/resolver/resolver.go |   6 --
 api/server/auth/auth.go  | 117 ---------------------------------------
 api/server/http/http.go  |   7 ++-
 api/server/options.go    |  31 ++++++-----
 4 files changed, 22 insertions(+), 139 deletions(-)
 delete mode 100644 api/server/auth/auth.go

diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go
index 99a10802..12854b19 100644
--- a/api/resolver/resolver.go
+++ b/api/resolver/resolver.go
@@ -11,12 +11,6 @@ var (
 	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
 type Resolver interface {
 	Resolve(r *http.Request) (*Endpoint, error)
diff --git a/api/server/auth/auth.go b/api/server/auth/auth.go
deleted file mode 100644
index deffa3dc..00000000
--- a/api/server/auth/auth.go
+++ /dev/null
@@ -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)
-}
diff --git a/api/server/http/http.go b/api/server/http/http.go
index c35d24b7..6615232d 100644
--- a/api/server/http/http.go
+++ b/api/server/http/http.go
@@ -10,7 +10,6 @@ import (
 
 	"github.com/gorilla/handlers"
 	"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/logger"
 )
@@ -53,7 +52,11 @@ func (s *httpServer) Init(opts ...server.Option) error {
 
 func (s *httpServer) Handle(path string, handler http.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 {
 		h = cors.CombinedCORSHandler(h)
diff --git a/api/server/options.go b/api/server/options.go
index 9aa14579..6d87f543 100644
--- a/api/server/options.go
+++ b/api/server/options.go
@@ -2,6 +2,7 @@ package server
 
 import (
 	"crypto/tls"
+	"net/http"
 
 	"github.com/micro/go-micro/v2/api/resolver"
 	"github.com/micro/go-micro/v2/api/server/acme"
@@ -10,14 +11,22 @@ import (
 type Option func(o *Options)
 
 type Options struct {
-	EnableACME        bool
-	EnableCORS        bool
-	ACMEProvider      acme.Provider
-	EnableTLS         bool
-	ACMEHosts         []string
-	TLSConfig         *tls.Config
-	Resolver          resolver.Resolver
-	NamespaceResolver resolver.NamespaceResolver
+	EnableACME   bool
+	EnableCORS   bool
+	ACMEProvider acme.Provider
+	EnableTLS    bool
+	ACMEHosts    []string
+	TLSConfig    *tls.Config
+	Resolver     resolver.Resolver
+	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 {
@@ -61,9 +70,3 @@ func Resolver(r resolver.Resolver) Option {
 		o.Resolver = r
 	}
 }
-
-func NamespaceResolver(r resolver.NamespaceResolver) Option {
-	return func(o *Options) {
-		o.NamespaceResolver = r
-	}
-}