This repository has been archived by the owner on Oct 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 63
/
cookie.go
181 lines (154 loc) · 5.48 KB
/
cookie.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
package auth
import (
"context"
"crypto/sha256"
"encoding/hex"
"math/rand"
"net/http"
"net/url"
"time"
"github.com/flyteorg/flyteadmin/auth/interfaces"
"github.com/flyteorg/flytestdlib/errors"
"github.com/flyteorg/flytestdlib/logger"
"github.com/gorilla/securecookie"
)
const (
// #nosec
accessTokenCookieName = "flyte_at"
// #nosec
idTokenCookieName = "flyte_idt"
// #nosec
refreshTokenCookieName = "flyte_rt"
// #nosec
csrfStateCookieName = "flyte_csrf_state"
// #nosec
redirectURLCookieName = "flyte_redirect_location"
// #nosec
idTokenExtra = "id_token"
// #nosec
authCodeCookieName = "flyte_auth_code"
// #nosec
userInfoCookieName = "flyte_user_info"
)
const (
ErrSecureCookie errors.ErrorCode = "SECURE_COOKIE_ERROR"
// #nosec
ErrInvalidCsrfToken errors.ErrorCode = "CSRF_TOKEN_VALIDATION_FAILED"
)
var AllowedChars = []rune("abcdefghijklmnopqrstuvwxyz1234567890")
func HashCsrfState(csrf string) string {
shaBytes := sha256.Sum256([]byte(csrf))
hash := hex.EncodeToString(shaBytes[:])
return hash
}
func NewSecureCookie(cookieName, value string, hashKey, blockKey []byte) (http.Cookie, error) {
var s = securecookie.New(hashKey, blockKey)
encoded, err := s.Encode(cookieName, value)
if err == nil {
return http.Cookie{
Name: cookieName,
Value: encoded,
}, nil
}
return http.Cookie{}, errors.Wrapf(ErrSecureCookie, err, "Error creating secure cookie")
}
func retrieveSecureCookie(ctx context.Context, request *http.Request, cookieName string, hashKey, blockKey []byte) (string, error) {
cookie, err := request.Cookie(cookieName)
if err != nil {
logger.Infof(ctx, "Could not detect existing cookie [%v]. Error: %v", cookieName, err)
return "", errors.Wrapf(ErrTokenNil, err, "Failure to retrieve cookie [%v]", cookieName)
}
if cookie == nil {
logger.Infof(ctx, "Retrieved empty cookie [%v].", cookieName)
return "", errors.Errorf(ErrTokenNil, "Retrieved empty cookie [%v]", cookieName)
}
logger.Debugf(ctx, "Existing [%v] cookie found", cookieName)
token, err := ReadSecureCookie(ctx, *cookie, hashKey, blockKey)
if err != nil {
logger.Errorf(ctx, "Error reading existing secure cookie [%v]. Error: %s", cookieName, err)
return "", errors.Errorf(ErrTokenNil, "Error reading existing secure cookie [%v]. Error: %s", cookieName, err)
}
if len(token) == 0 {
logger.Errorf(ctx, "Read empty token from secure cookie [%v].", cookieName)
return "", errors.Errorf(ErrTokenNil, "Read empty token from secure cookie [%v].", cookieName)
}
return token, nil
}
func ReadSecureCookie(ctx context.Context, cookie http.Cookie, hashKey, blockKey []byte) (string, error) {
var s = securecookie.New(hashKey, blockKey)
var value string
var err error
if err = s.Decode(cookie.Name, cookie.Value, &value); err == nil {
return value, nil
}
logger.Errorf(ctx, "Error reading secure cookie %s %s", cookie.Name, err)
return "", errors.Wrapf(ErrSecureCookie, err, "Error reading secure cookie %s", cookie.Name)
}
func NewCsrfToken(seed int64) string {
rand.Seed(seed)
csrfToken := [10]rune{}
for i := 0; i < len(csrfToken); i++ {
/* #nosec */
csrfToken[i] = AllowedChars[rand.Intn(len(AllowedChars))]
}
return string(csrfToken[:])
}
func NewCsrfCookie() http.Cookie {
csrfStateToken := NewCsrfToken(time.Now().UnixNano())
return http.Cookie{
Name: csrfStateCookieName,
Value: csrfStateToken,
SameSite: http.SameSiteLaxMode,
HttpOnly: true,
}
}
func VerifyCsrfCookie(ctx context.Context, request *http.Request) error {
csrfState := request.FormValue(CsrfFormKey)
if csrfState == "" {
return errors.Errorf(ErrInvalidCsrfToken, "Empty state in callback, %s", request.Form)
}
csrfCookie, err := request.Cookie(csrfStateCookieName)
if csrfCookie == nil || err != nil {
return errors.Errorf(ErrInvalidCsrfToken, "Could not find csrf cookie %s", err)
}
if HashCsrfState(csrfCookie.Value) != csrfState {
return errors.Errorf(ErrInvalidCsrfToken, "CSRF token does not match state %s, %s vs %s", csrfCookie.Value,
HashCsrfState(csrfCookie.Value), csrfState)
}
return nil
}
// This function takes in a string and returns a cookie that's used to keep track of where to send the user after
// the OAuth2 login flow is complete.
func NewRedirectCookie(ctx context.Context, redirectURL string) *http.Cookie {
urlObj, err := url.Parse(redirectURL)
if err != nil || urlObj == nil {
logger.Errorf(ctx, "Error creating redirect cookie %s %s", urlObj, err)
return nil
}
if urlObj.EscapedPath() == "" {
logger.Errorf(ctx, "Error parsing URL, redirect %s resolved to empty string", redirectURL)
return nil
}
return &http.Cookie{
Name: redirectURLCookieName,
Value: urlObj.String(),
SameSite: http.SameSiteLaxMode,
HttpOnly: true,
}
}
// At the end of the OAuth flow, the server needs to send the user somewhere. This should have been stored as a cookie
// during the initial /login call. If that cookie is missing from the request, it will default to the one configured
// in this package's Config object.
func getAuthFlowEndRedirect(ctx context.Context, authCtx interfaces.AuthenticationContext, request *http.Request) string {
queryParams := request.URL.Query()
// Use the redirect URL specified in the request if one is available.
if redirectURL := queryParams.Get(RedirectURLParameter); len(redirectURL) > 0 {
return redirectURL
}
cookie, err := request.Cookie(redirectURLCookieName)
if err != nil {
logger.Debugf(ctx, "Could not detect end-of-flow redirect url cookie")
return authCtx.Options().UserAuth.RedirectURL.String()
}
return cookie.Value
}