-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathgate.go
245 lines (222 loc) · 10.2 KB
/
gate.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package g8
import (
"context"
"net/http"
"strings"
)
const (
// AuthorizationHeader is the header in which g8 looks for the authorization bearer token
AuthorizationHeader = "Authorization"
// DefaultUnauthorizedResponseBody is the default response body returned if a request was sent with a missing or invalid token
DefaultUnauthorizedResponseBody = "token is missing or invalid"
// DefaultTooManyRequestsResponseBody is the default response body returned if a request exceeded the allowed rate limit
DefaultTooManyRequestsResponseBody = "too many requests"
// TokenContextKey is the key used to store the client's token in the context.
TokenContextKey = "g8.token"
// DataContextKey is the key used to store the client's data in the context.
DataContextKey = "g8.data"
)
// Gate is lock to the front door of your API, letting only those you allow through.
type Gate struct {
authorizationService *AuthorizationService
unauthorizedResponseBody []byte
customTokenExtractorFunc func(request *http.Request) string
rateLimiter *RateLimiter
tooManyRequestsResponseBody []byte
}
// Deprecated: use New instead.
func NewGate(authorizationService *AuthorizationService) *Gate {
return &Gate{
authorizationService: authorizationService,
unauthorizedResponseBody: []byte(DefaultUnauthorizedResponseBody),
tooManyRequestsResponseBody: []byte(DefaultTooManyRequestsResponseBody),
}
}
// New creates a new Gate.
func New() *Gate {
return &Gate{
unauthorizedResponseBody: []byte(DefaultUnauthorizedResponseBody),
tooManyRequestsResponseBody: []byte(DefaultTooManyRequestsResponseBody),
}
}
// WithAuthorizationService sets the authorization service to use.
//
// If there is no authorization service, Gate will not enforce authorization.
func (gate *Gate) WithAuthorizationService(authorizationService *AuthorizationService) *Gate {
gate.authorizationService = authorizationService
return gate
}
// WithCustomUnauthorizedResponseBody sets a custom response body when Gate determines that a request must be blocked
func (gate *Gate) WithCustomUnauthorizedResponseBody(unauthorizedResponseBody []byte) *Gate {
gate.unauthorizedResponseBody = unauthorizedResponseBody
return gate
}
// WithCustomTokenExtractor allows the specification of a custom function to extract a token from a request.
// If a custom token extractor is not specified, the token will be extracted from the Authorization header.
//
// For instance, if you're using a session cookie, you can extract the token from the cookie like so:
//
// authorizationService := g8.NewAuthorizationService()
// customTokenExtractorFunc := func(request *http.Request) string {
// sessionCookie, err := request.Cookie("session")
// if err != nil {
// return ""
// }
// return sessionCookie.Value
// }
// gate := g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc)
//
// You would normally use this with a client provider that matches whatever need you have.
// For example, if you're using a session cookie, your client provider would retrieve the user from the session ID
// extracted by this custom token extractor.
//
// Note that for the sake of convenience, the token extracted from the request is passed the protected handlers request
// context under the key TokenContextKey. This is especially useful if the token is in fact a session ID.
func (gate *Gate) WithCustomTokenExtractor(customTokenExtractorFunc func(request *http.Request) string) *Gate {
gate.customTokenExtractorFunc = customTokenExtractorFunc
return gate
}
// WithRateLimit adds rate limiting to the Gate
//
// If you just want to use a gate for rate limiting purposes:
//
// gate := g8.New().WithRateLimit(50)
func (gate *Gate) WithRateLimit(maximumRequestsPerSecond int) *Gate {
gate.rateLimiter = NewRateLimiter(maximumRequestsPerSecond)
return gate
}
// Protect secures a handler, requiring requests going through to have a valid Authorization Bearer token.
// Unlike ProtectWithPermissions, Protect will allow access to any registered tokens, regardless of their permissions
// or lack thereof.
//
// Example:
//
// gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithToken("token"))
// router := http.NewServeMux()
// // Without protection
// router.Handle("/handle", yourHandler)
// // With protection
// router.Handle("/handle", gate.Protect(yourHandler))
//
// The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (gate *Gate) Protect(handler http.Handler) http.Handler {
return gate.ProtectWithPermissions(handler, nil)
}
// ProtectWithPermissions secures a handler, requiring requests going through to have a valid Authorization Bearer token
// as well as a slice of permissions that must be met.
//
// Example:
//
// gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClient(g8.NewClient("token").WithPermission("ADMIN")))
// router := http.NewServeMux()
// // Without protection
// router.Handle("/handle", yourHandler)
// // With protection
// router.Handle("/handle", gate.ProtectWithPermissions(yourHandler, []string{"admin"}))
//
// The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (gate *Gate) ProtectWithPermissions(handler http.Handler, permissions []string) http.Handler {
return gate.ProtectFuncWithPermissions(func(writer http.ResponseWriter, request *http.Request) {
handler.ServeHTTP(writer, request)
}, permissions)
}
// ProtectWithPermission does the same thing as ProtectWithPermissions, but for a single permission instead of a
// slice of permissions
//
// See ProtectWithPermissions for further documentation
func (gate *Gate) ProtectWithPermission(handler http.Handler, permission string) http.Handler {
return gate.ProtectFuncWithPermissions(func(writer http.ResponseWriter, request *http.Request) {
handler.ServeHTTP(writer, request)
}, []string{permission})
}
// ProtectFunc secures a handlerFunc, requiring requests going through to have a valid Authorization Bearer token.
// Unlike ProtectFuncWithPermissions, ProtectFunc will allow access to any registered tokens, regardless of their
// permissions or lack thereof.
//
// Example:
//
// gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithToken("token"))
// router := http.NewServeMux()
// // Without protection
// router.HandleFunc("/handle", yourHandlerFunc)
// // With protection
// router.HandleFunc("/handle", gate.ProtectFunc(yourHandlerFunc))
//
// The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (gate *Gate) ProtectFunc(handlerFunc http.HandlerFunc) http.HandlerFunc {
return gate.ProtectFuncWithPermissions(handlerFunc, nil)
}
// ProtectFuncWithPermissions secures a handler, requiring requests going through to have a valid Authorization Bearer
// token as well as a slice of permissions that must be met.
//
// Example:
//
// gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClient(g8.NewClient("token").WithPermission("admin")))
// router := http.NewServeMux()
// // Without protection
// router.HandleFunc("/handle", yourHandlerFunc)
// // With protection
// router.HandleFunc("/handle", gate.ProtectFuncWithPermissions(yourHandlerFunc, []string{"admin"}))
//
// The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (gate *Gate) ProtectFuncWithPermissions(handlerFunc http.HandlerFunc, permissions []string) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
if gate.rateLimiter != nil {
if !gate.rateLimiter.Try() {
writer.WriteHeader(http.StatusTooManyRequests)
_, _ = writer.Write(gate.tooManyRequestsResponseBody)
return
}
}
if gate.authorizationService != nil {
token := gate.ExtractTokenFromRequest(request)
if client, authorized := gate.authorizationService.Authorize(token, permissions); !authorized {
writer.WriteHeader(http.StatusUnauthorized)
_, _ = writer.Write(gate.unauthorizedResponseBody)
return
} else {
request = request.WithContext(context.WithValue(request.Context(), TokenContextKey, token))
if client != nil && client.Data != nil {
request = request.WithContext(context.WithValue(request.Context(), DataContextKey, client.Data))
}
}
}
handlerFunc(writer, request)
}
}
// ProtectFuncWithPermission does the same thing as ProtectFuncWithPermissions, but for a single permission instead of a
// slice of permissions
//
// See ProtectFuncWithPermissions for further documentation
func (gate *Gate) ProtectFuncWithPermission(handlerFunc http.HandlerFunc, permission string) http.HandlerFunc {
return gate.ProtectFuncWithPermissions(handlerFunc, []string{permission})
}
// ExtractTokenFromRequest extracts a token from a request.
//
// By default, it extracts the bearer token from the AuthorizationHeader, but if a customTokenExtractorFunc is defined,
// it will use that instead.
//
// Note that this method is internally used by Protect, ProtectWithPermission, ProtectFunc and
// ProtectFuncWithPermissions, but it is exposed in case you need to use it directly.
func (gate *Gate) ExtractTokenFromRequest(request *http.Request) string {
if gate.customTokenExtractorFunc != nil {
// A custom token extractor function is defined, so we'll use it instead of the default token extraction logic
return gate.customTokenExtractorFunc(request)
}
return strings.TrimPrefix(request.Header.Get(AuthorizationHeader), "Bearer ")
}
// PermissionMiddleware is a middleware that behaves like ProtectWithPermission, but it is meant to be used
// as a middleware for libraries that support such a feature.
//
// For instance, if you are using github.com/gorilla/mux, you can use PermissionMiddleware like so:
//
// router := mux.NewRouter()
// router.Use(gate.PermissionMiddleware("admin"))
// router.Handle("/admin/handle", adminHandler)
//
// If you do not want to protect a router with a specific permission, you can use Gate.Protect instead.
func (gate *Gate) PermissionMiddleware(permissions ...string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return gate.ProtectWithPermissions(next, permissions)
}
}