-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathserver.go
316 lines (292 loc) · 11.9 KB
/
server.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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
package goauth
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"sync"
"time"
"github.com/KaiserWerk/goauth2/usercode"
"github.com/KaiserWerk/goauth2/assets"
"github.com/KaiserWerk/goauth2/storage"
"github.com/KaiserWerk/goauth2/token"
"github.com/KaiserWerk/goauth2/types"
)
type ErrorType string
const (
InvalidRequest ErrorType = "invalid_request"
UnauthorizedClient ErrorType = "unauthorized_client"
AccessDenied ErrorType = "access_denied"
UnsupportedResponseType ErrorType = "unsupported_response_type"
InvalidScope ErrorType = "invalid_scope"
ServerError ErrorType = "server_error"
TemporarilyUnavailable ErrorType = "temporarily_unavailable"
)
type (
// Storage contains the storage implementations required for operations.
Storage struct {
// DeviceCodeRequestStorage stores requests for the Device Code Grant. Must be set for Device Code Grant.
DeviceCodeRequestStorage storage.DeviceCodeStorage
// AuthorizationCodeRequestStorage stores requests for the Authorization Code Grant.
AuthorizationCodeRequestStorage storage.AuthorizationCodeRequestStorage
// SessionStorage stores active user sessions. Required for all redirect-based grant flows.
SessionStorage storage.SessionStorage
// UserStorage stores user information and credentials. Required for all flows but the Client Credentials Grant flow.
UserStorage storage.UserStorage
// ClientStorage stores client information. Required for all grant flows.
ClientStorage storage.ClientStorage
// TokenStorage stores tokens, refresh tokens and related information. Required for all grant flows.
TokenStorage storage.TokenStorage
}
// Templates contains the HTML templates displayed for the user.
Templates struct {
// Login represents the login HTML template for redirect based flows.
Login []byte
// AuthorizationCode represents the authorization page shown to the user when authorizing using the Authorization Code Grant.
AuthorizationCode []byte
// ImplicitGrant represents the authorization page shown to the user when authorizing using the Implicit Grant.
ImplicitGrant []byte
// DeviceCode represents the authorization page shown to the user when authorizing using the Device Code Grant.
DeviceCode []byte
//Though PKCE is based on the Authorization Code Grant, you can still choose a different template.
PKCE []byte
//Though OIDC is based on the Authorization Code Grant, you can still choose a different template.
OIDC []byte
}
// Flags contains feature flags for the Authorization Code Grant to enable/disable particular features.
Flags struct {
// PKCE = Proof Key for Code Exchange. Used on top of the Authorization Code Grant.
PKCE bool
// OIDC = OpenID Connect. If you set this to true, PKCE is enabled regardless of value.
OIDC bool
}
// Policies represents constraints and requirements for proper operation.
Policies struct {
// DeviceCodeLength sets the length in bytes a generated device code for the Device Code Grant should have.
DeviceCodeLength int
// UserCodeLength sets the length in bytes a generated user code for the Device Code Grant should have.
//
// Deprecated: use the new UserCodeGenerator field instead.
UserCodeLength int
// AccessTokenLength sets the length in bytes of a generated access token.
AccessTokenLength int
// RefreshTokenLength sets the length in bytes of a generated refresh token.
RefreshTokenLength int
// ClientSecretLength sets the length in bytes of a generated client secret for newly created client.
ClientSecretLength int
// IDTokenLength relates to OpenID Connect and sets the length in bytes of a generated ID token.
IDTokenLength int
// SessionLifetime sets the maximum lifetime of a user session.
SessionLifetime time.Duration
// SessionLifetime sets the maximum lifetime of an access token.
AccessTokenLifetime time.Duration
// RefreshTokenLifetime sets the maximum lifetime of a refresh token.
RefreshTokenLifetime time.Duration
}
// Session contains session and cookie settings.
Session struct {
// CookieName represents the name of the cookie to be set for storing the session ID.
CookieName string
// HTTPOnly specifies whether the session cookie has the HTTPOnly flag set.
HTTPOnly bool
// Secure specifies whether the session cookie has the Secure flag set (only for HTTPS).
Secure bool
}
// URLs contains paths and/or URLs to the endpoints/routes defined by the caller.
// If you only use Client Credentials Grant + Resource Owner Password Credentials Grant, no URLs need to be set, since
// these grant flows are not redirect-based.
URLs struct {
// Login is the target URL for the user login page, e.g. /user_login.
Login string
// Logout is the target URL for the user logout page, e.g. /user_logout.
Logout string
// DeviceCode is the target URL for the user Device Code user authorization page.
DeviceCode string
// AuthorizationCode is the target URL for the Authorization Code user authorization page.
AuthorizationCode string
// Implicit is the target URL for the Implicit Grant user authorization page.
Implicit string
}
// A Server handles all HTTP requests relevant to the OAuth2 authorization processes. If a Server's exported fields
// are modified after first use, the behavior is undefined.
Server struct {
// PublicBaseURL is the public facing URL containing scheme, hostname and port, if required.
// it is used to construct redirect URLs.
PublicBaseURL string
// Storage contains the necessary storage implementations.
Storage Storage
// Template contains HTML templates as byte slices used for displaying to the user, e.g. login form.
Template Templates
// Flags are feature flags meant to enable certain features.
Flags Flags
// Policies can restrict how certain values have to be restricted, e.g. the length of certain strings or the
// validitdy durations.
Policies Policies
// Session contains session and cookie configuration values.
Session Session
// URLs contain paths and URLs for internal redirects.
URLs URLs
// TokenGenerator is a source used to generate tokens.
TokenGenerator token.TokenGenerator
// UserCodeGenerator is a source used to generate user codes for the device flow
UserCodeGenerator usercode.Generator
ErrorRedirect func(http.ResponseWriter, *http.Request, string, ErrorType, string, string)
ErrorResponse func(http.ResponseWriter, int, ErrorType, string) error
grantTypes []types.GrantType
m *sync.RWMutex
}
)
// NewDefaultServer returns a *Server with set default values:
//
// • PublicBaseURL: is set to 'http://localhost' without a port. It is required for redirect-based authorization flows.
//
// • Storage: each store uses a corresponding in-memory implementation, e.g. MemoryClientStorage.
//
// • Templates: the default templates from this library are used. They are not overly pretty, but they get their job done.
//
// • Flags: all flags remain at their default value.
//
// • Policies: sensible lengths and lifetime which ensure a certain degree of security.
//
// • TokenGenerator: uses a ready-to-use in-memory implementation, namely DefaultTokenGenerator.
//
// • DefaultUserCodeGenerator: uses a ready-to-use in-memory implementation, namely DefaultUserCodeGenerator.
//
// • grantTypes: all implemented grant types are listed here.
//
// You should probably alter the PublicBaseURL and add at least one Client and one User.
func NewDefaultServer() *Server {
return &Server{
PublicBaseURL: "http://localhost",
Storage: Storage{
DeviceCodeRequestStorage: storage.NewMemoryDeviceCodeRequestStorage(),
AuthorizationCodeRequestStorage: storage.NewMemoryAuthorizationCodeRequestStorage(),
SessionStorage: storage.NewMemorySessionStorage(),
UserStorage: storage.NewMemoryUserStorage(),
ClientStorage: storage.NewMemoryClientStorage(),
TokenStorage: storage.NewMemoryTokenStorage(),
},
Template: Templates{
Login: assets.LoginPageTemplate,
AuthorizationCode: assets.AuthorizationCodeTemplate,
ImplicitGrant: assets.ImplicitGrantTemplate,
DeviceCode: assets.DeviceCodeTemplate,
},
Flags: Flags{},
Policies: Policies{
DeviceCodeLength: 70,
UserCodeLength: 6,
AccessTokenLength: 120,
RefreshTokenLength: 80,
ClientSecretLength: 75,
IDTokenLength: 255,
SessionLifetime: 30 * 24 * time.Hour,
AccessTokenLifetime: 1 * time.Hour,
RefreshTokenLifetime: 24 * time.Hour,
},
Session: Session{
CookieName: "GOAUTH_SID",
HTTPOnly: true,
Secure: false,
},
URLs: URLs{
Login: "/user_login",
Logout: "/user_logout",
DeviceCode: "/device",
AuthorizationCode: "/authorize",
Implicit: "/implicit",
},
TokenGenerator: token.DefaultTokenGenerator,
UserCodeGenerator: usercode.DefaultUserCodeGenerator,
ErrorRedirect: ErrorRedirectResponse,
ErrorResponse: ErrorJSONResponse,
grantTypes: []types.GrantType{
types.AuthorizationCode,
types.DeviceCode,
types.Implicit,
types.ClientCredentials,
types.ResourceOwnerPasswordCredentials,
},
m: new(sync.RWMutex),
}
}
// NewEmptyServer returns a *Server with just the base setup.
func NewEmptyServer() *Server {
return &Server{
ErrorRedirect: ErrorRedirectResponse,
ErrorResponse: ErrorJSONResponse,
grantTypes: make([]types.GrantType, 0, 5),
m: new(sync.RWMutex),
}
}
// Cleanup should be executed when the Server is not required anymore, typically at app shutdown.
// Cleanup frees resources used by the Server.
func (s *Server) Cleanup() {
_ = s.Storage.ClientStorage.Close()
_ = s.Storage.TokenStorage.Close()
_ = s.Storage.UserStorage.Close()
_ = s.Storage.SessionStorage.Close()
_ = s.Storage.AuthorizationCodeRequestStorage.Close()
_ = s.Storage.DeviceCodeRequestStorage.Close()
}
// AddGrantType adds the given grant type to the current list of enabled grant types for the server s.
// A grant type not listed might not be available, depending on the caller's usage.
// You can use this call to change the availability of a given grant type while the Server is in use.
func (s *Server) AddGrantType(gt types.GrantType) {
s.m.Lock()
defer s.m.Unlock()
for _, t := range s.grantTypes {
if gt == t {
return
}
}
s.grantTypes = append(s.grantTypes, gt)
}
// RemoveGrantType removes the given grant type from the current list of enabled grant types for the server s.
// You can use this call to change the availability of a given grant type while the Server is in use.
func (s *Server) RemoveGrantType(gt types.GrantType) {
s.m.Lock()
defer s.m.Unlock()
for i, t := range s.grantTypes {
if gt == t {
s.grantTypes[i] = s.grantTypes[len(s.grantTypes)-1]
s.grantTypes = s.grantTypes[:len(s.grantTypes)-1]
}
}
}
// ResetGrantTypes empties the internal list of enabled grant types.
func (s *Server) ResetGrantTypes() {
s.m.Lock()
s.grantTypes = make([]types.GrantType, 0, 5)
s.m.Unlock()
}
func (s *Server) HasGrantType(gt types.GrantType) bool {
s.m.RLock()
defer s.m.RUnlock()
for _, t := range s.grantTypes {
if gt == t {
return true
}
}
return false
}
func ErrorRedirectResponse(w http.ResponseWriter, r *http.Request, redirectUrl string, errType ErrorType, errMsg, state string) {
values := url.Values{}
values.Add("error", string(errType))
values.Add("error_description", errMsg)
if state != "" {
values.Add("state", state)
}
target := fmt.Sprintf("%s#%s", redirectUrl, values.Encode())
http.Redirect(w, r, target, http.StatusSeeOther)
}
func ErrorJSONResponse(w http.ResponseWriter, code int, errType ErrorType, errMsg string) error {
w.WriteHeader(code)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Pragma", "no-cache")
d := map[string]string{"error": string(errType), "error_description": errMsg}
e := json.NewEncoder(w)
e.SetIndent("", "\t")
return e.Encode(d)
}