/
wrapper.go
130 lines (114 loc) · 4 KB
/
wrapper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package auth
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/itzmanish/go-micro/v2/api/resolver"
"github.com/itzmanish/go-micro/v2/api/server"
"github.com/itzmanish/go-micro/v2/auth"
"github.com/itzmanish/go-micro/v2/logger"
"github.com/itzmanish/go-micro/v2/util/ctx"
inauth "github.com/itzmanish/micro/v2/internal/auth"
"github.com/itzmanish/micro/v2/internal/namespace"
)
// Wrapper wraps a handler and authenticates requests
func Wrapper(r resolver.Resolver, nr *namespace.Resolver) server.Wrapper {
return func(h http.Handler) http.Handler {
return authWrapper{
handler: h,
resolver: r,
nsResolver: nr,
auth: auth.DefaultAuth,
}
}
}
type authWrapper struct {
handler http.Handler
auth auth.Auth
resolver resolver.Resolver
nsResolver *namespace.Resolver
}
func (a authWrapper) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Determine the namespace and set it in the header
ns := req.Header.Get(namespace.NamespaceKey)
if len(ns) == 0 {
ns = a.nsResolver.Resolve(req)
req.Header.Set(namespace.NamespaceKey, ns)
}
// Set the metadata so we can access it in micro api / web
req = req.WithContext(ctx.FromRequest(req))
// 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, inauth.TokenCookieName+"=")
req.Header.Set("Authorization", auth.BearerScheme+token)
}
}
// Get the account using the token, some are unauthenticated, so the lack of an
// account doesn't necesserially mean a forbidden request
acc, _ := a.auth.Inspect(token)
// Ensure accounts only issued by the namesace are valid
if acc != nil && acc.Issuer != ns {
acc = nil
}
// Determine the name of the service being requested
endpoint, err := a.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 := a.nsResolver.ResolveWithType(req)
if len(endpoint.Name) > 0 {
resName = resName + "." + 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}
if err := a.auth.Verify(acc, res, auth.VerifyContext(req.Context())); err == nil {
// The account has the necessary permissions to access the resource
a.handler.ServeHTTP(w, req)
return
}
// The account is set, but they don't have enough permissions, hence
// we return a forbidden error.
if acc != nil {
http.Error(w, "Forbidden request", 403)
return
}
// If there is no auth login url set, 401
loginURL := a.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)
}