forked from roblillack/mars
-
Notifications
You must be signed in to change notification settings - Fork 0
/
session.go
186 lines (160 loc) · 5.09 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
package mars
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
// A signed cookie (and thus limited to 4kb in size).
// Restriction: Keys may not have a colon in them.
type Session map[string]string
const (
SESSION_ID_KEY = "_ID"
TIMESTAMP_KEY = "_TS"
)
// expireAfterDuration is the time to live, in seconds, of a session cookie.
// It may be specified in config as "session.expires". Values greater than 0
// set a persistent cookie with a time to live as specified, and the value 0
// sets a session cookie.
var expireAfterDuration time.Duration
func init() {
// Set expireAfterDuration, default to 30 days if no value in config
OnAppStart(func() {
var err error
if expiresString, ok := Config.String("session.expires"); !ok {
expireAfterDuration = 30 * 24 * time.Hour
} else if expiresString == "session" {
expireAfterDuration = 0
} else if expireAfterDuration, err = time.ParseDuration(expiresString); err != nil {
panic(fmt.Errorf("session.expires invalid: %s", err))
}
})
}
// Id retrieves from the cookie or creates a time-based UUID identifying this
// session.
func (s Session) Id() string {
if sessionIdStr, ok := s[SESSION_ID_KEY]; ok {
return sessionIdStr
}
buffer := make([]byte, 32)
if _, err := rand.Read(buffer); err != nil {
panic(err)
}
s[SESSION_ID_KEY] = hex.EncodeToString(buffer)
return s[SESSION_ID_KEY]
}
// getExpiration return a time.Time with the session's expiration date.
// If previous session has set to "session", remain it
func (s Session) getExpiration() time.Time {
if expireAfterDuration == 0 || s[TIMESTAMP_KEY] == "session" {
// Expire after closing browser
return time.Time{}
}
return time.Now().Add(expireAfterDuration)
}
// Cookie returns an http.Cookie containing the signed session.
func (s Session) Cookie() *http.Cookie {
var sessionValue string
ts := s.getExpiration()
s[TIMESTAMP_KEY] = getSessionExpirationCookie(ts)
for key, value := range s {
if strings.ContainsAny(key, ":\x00") {
panic("Session keys may not have colons or null bytes")
}
if strings.Contains(value, "\x00") {
panic("Session values may not have null bytes")
}
sessionValue += "\x00" + key + ":" + value + "\x00"
}
sessionData := url.QueryEscape(sessionValue)
return &http.Cookie{
Name: CookiePrefix + "_SESSION",
Value: Sign(sessionData) + "-" + sessionData,
Domain: CookieDomain,
Path: "/",
HttpOnly: CookieHttpOnly,
Secure: CookieSecure,
Expires: ts.UTC(),
}
}
// sessionTimeoutExpiredOrMissing returns a boolean of whether the session
// cookie is either not present or present but beyond its time to live; i.e.,
// whether there is not a valid session.
func sessionTimeoutExpiredOrMissing(session Session) bool {
if exp, present := session[TIMESTAMP_KEY]; !present {
return true
} else if exp == "session" {
return false
} else if expInt, _ := strconv.Atoi(exp); int64(expInt) < time.Now().Unix() {
return true
}
return false
}
// GetSessionFromCookie returns a Session struct pulled from the signed
// session cookie.
func GetSessionFromCookie(cookie *http.Cookie) Session {
session := make(Session)
// Separate the data from the signature.
hyphen := strings.Index(cookie.Value, "-")
if hyphen == -1 || hyphen >= len(cookie.Value)-1 {
return session
}
sig, data := cookie.Value[:hyphen], cookie.Value[hyphen+1:]
// Verify the signature.
if !Verify(data, sig) {
INFO.Println("Session cookie signature failed")
return session
}
parseKeyValueCookie(data, func(key, val string) {
session[key] = val
})
if sessionTimeoutExpiredOrMissing(session) {
session = make(Session)
}
return session
}
// SessionFilter is a Mars Filter that retrieves and sets the session cookie.
// Within Mars, it is available as a Session attribute on Controller instances.
// The name of the Session cookie is set as CookiePrefix + "_SESSION".
func SessionFilter(c *Controller, fc []Filter) {
c.Session = restoreSession(c.Request.Request)
sessionWasEmpty := len(c.Session) == 0
// Make session vars available in templates as {{.session.xyz}}
c.RenderArgs["session"] = c.Session
fc[0](c, fc[1:])
// Store the signed session if it could have changed.
if len(c.Session) > 0 || !sessionWasEmpty {
c.SetCookie(c.Session.Cookie())
}
}
// restoreSession returns either the current session, retrieved from the
// session cookie, or a new session.
func restoreSession(req *http.Request) Session {
cookie, err := req.Cookie(CookiePrefix + "_SESSION")
if err != nil {
return make(Session)
} else {
return GetSessionFromCookie(cookie)
}
}
// getSessionExpirationCookie retrieves the cookie's time to live as a
// string of either the number of seconds, for a persistent cookie, or
// "session".
func getSessionExpirationCookie(t time.Time) string {
if t.IsZero() {
return "session"
}
return strconv.FormatInt(t.Unix(), 10)
}
// SetNoExpiration sets session to expire when browser session ends
func (s Session) SetNoExpiration() {
s[TIMESTAMP_KEY] = "session"
}
// SetDefaultExpiration sets session to expire after default duration
func (s Session) SetDefaultExpiration() {
delete(s, TIMESTAMP_KEY)
}