-
Notifications
You must be signed in to change notification settings - Fork 172
/
Copy pathsession.go
187 lines (159 loc) · 5.74 KB
/
session.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
package scs
import (
"bytes"
"log"
"net/http"
"time"
"github.com/alexedwards/scs/v2/memstore"
)
// Session holds the configuration settings for your sessions.
type Session struct {
// IdleTimeout controls the maximum length of time a session can be inactive
// before it expires. For example, some applications may wish to set this so
// there is a timeout after 20 minutes of inactivity. By default IdleTimeout
// is not set and there is no inactivity timeout.
IdleTimeout time.Duration
// Lifetime controls the maximum length of time that a session is valid for
// before it expires. The lifetime is an 'absolute expiry' which is set when
// the session is first created and does not change. The default value is 24
// hours.
Lifetime time.Duration
// Store controls the session store where the session data is persisted.
Store Store
// Cookie contains the configuration settings for session cookies.
Cookie SessionCookie
// contextKey is the key used to set and retrieve the session data from a
// context.Context. It's automatically generated to ensure uniqueness.
contextKey contextKey
}
// SessionCookie contains the configuration settings for session cookies.
type SessionCookie struct {
// Name sets the name of the session cookie. It should not contain
// whitespace, commas, colons, semicolons, backslashes, the equals sign or
// control characters as per RFC6265. The default cookie name is "session".
// If your application uses two different sessions, you must make sure that
// the cookie name for each is unique.
Name string
// Domain sets the 'Domain' attribute on the session cookie. By default
// it will be set to the domain name that the cookie was issued from.
Domain string
// HttpOnly sets the 'HttpOnly' attribute on the session cookie. The
// default value is true.
HttpOnly bool
// Path sets the 'Path' attribute on the session cookie. The default value
// is "/". Passing the empty string "" will result in it being set to the
// path that the cookie was issued from.
Path string
// Persist sets whether the session cookie should be persistent or not
// (i.e. whether it should be retained after a user closes their browser).
// The default value is true, which means that the session cookie will not
// be destroyed when the user closes their browser and the appropriate
// 'Expires' and 'MaxAge' values will be added to the session cookie.
Persist bool
// SameSite controls the value of the 'SameSite' attribute on the session
// cookie. By default this is set to 'SameSite=Lax'. If you want no SameSite
// attribute or value in the session cookie then you should set this to 0.
SameSite http.SameSite
// Secure sets the 'Secure' attribute on the session cookie. The default
// value is false. It's recommended that you set this to true and serve all
// requests over HTTPS in production environments.
// See https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Session_Management_Cheat_Sheet.md#transport-layer-security.
Secure bool
}
// NewSession returns a new session manager with the default options. It is
// safe for concurrent use.
func NewSession() *Session {
s := &Session{
IdleTimeout: 0,
Lifetime: 24 * time.Hour,
Store: memstore.New(),
contextKey: generateContextKey(),
Cookie: SessionCookie{
Name: "session",
Domain: "",
HttpOnly: true,
Path: "/",
Persist: true,
Secure: false,
SameSite: http.SameSiteLaxMode,
},
}
return s
}
// LoadAndSave provides middleware which automatically loads and saves session
// data for the current request, and communicates the session token to and from
// the client in a cookie.
func (s *Session) LoadAndSave(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var token string
cookie, err := r.Cookie(s.Cookie.Name)
if err == nil {
token = cookie.Value
}
ctx, err := s.Load(r.Context(), token)
if err != nil {
log.Output(2, err.Error())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
sr := r.WithContext(ctx)
bw := &bufferedResponseWriter{ResponseWriter: w}
next.ServeHTTP(bw, sr)
switch s.Status(ctx) {
case Modified:
token, expiry, err := s.Commit(ctx)
if err != nil {
log.Output(2, err.Error())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
s.writeSessionCookie(w, token, expiry)
case Destroyed:
s.writeSessionCookie(w, "", time.Time{})
}
if bw.code != 0 {
w.WriteHeader(bw.code)
}
w.Write(bw.buf.Bytes())
})
}
func (s *Session) writeSessionCookie(w http.ResponseWriter, token string, expiry time.Time) {
cookie := &http.Cookie{
Name: s.Cookie.Name,
Value: token,
Path: s.Cookie.Path,
Domain: s.Cookie.Domain,
Secure: s.Cookie.Secure,
HttpOnly: s.Cookie.HttpOnly,
SameSite: s.Cookie.SameSite,
}
if expiry.IsZero() {
cookie.Expires = time.Unix(1, 0)
cookie.MaxAge = -1
} else if s.Cookie.Persist {
cookie.Expires = time.Unix(expiry.Unix()+1, 0) // Round up to the nearest second.
cookie.MaxAge = int(time.Until(expiry).Seconds() + 1) // Round up to the nearest second.
}
w.Header().Add("Set-Cookie", cookie.String())
addHeaderIfMissing(w, "Cache-Control", `no-cache="Set-Cookie"`)
addHeaderIfMissing(w, "Vary", "Cookie")
}
func addHeaderIfMissing(w http.ResponseWriter, key, value string) {
for _, h := range w.Header()[key] {
if h == value {
return
}
}
w.Header().Add(key, value)
}
type bufferedResponseWriter struct {
http.ResponseWriter
buf bytes.Buffer
code int
}
func (bw *bufferedResponseWriter) Write(b []byte) (int, error) {
return bw.buf.Write(b)
}
func (bw *bufferedResponseWriter) WriteHeader(code int) {
bw.code = code
}