-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsessions.go
174 lines (149 loc) · 5.99 KB
/
sessions.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
package sessions
import (
"net/http"
"strings"
"time"
"github.com/get-ion/ion/context"
)
// A Sessions manager should be responsible to Start a sesion, based
// on a Context, which should return
// a compatible Session interface, type. If the external session manager
// doesn't qualifies, then the user should code the rest of the functions with empty implementation.
//
// Sessions should be responsible to Destroy a session based
// on the Context.
type Sessions struct {
config Config
provider *provider
}
// New returns a new fast, feature-rich sessions manager
// it can be adapted to an ion station
func New(cfg Config) *Sessions {
return &Sessions{
config: cfg.Validate(),
provider: newProvider(),
}
}
// UseDatabase adds a session database to the manager's provider,
// a session db doesn't have write access
func (s *Sessions) UseDatabase(db Database) {
s.provider.RegisterDatabase(db)
}
// Start should start the session for the particular request.
func (s *Sessions) Start(ctx context.Context) *Session {
cookieValue := GetCookie(ctx, s.config.Cookie)
if cookieValue == "" { // cookie doesn't exists, let's generate a session and add set a cookie
sid := s.config.SessionIDGenerator()
sess := s.provider.Init(sid, s.config.Expires)
cookie := &http.Cookie{}
// The RFC makes no mention of encoding url value, so here I think to encode both sessionid key and the value using the safe(to put and to use as cookie) url-encoding
cookie.Name = s.config.Cookie
cookie.Value = sid
cookie.Path = "/"
if !s.config.DisableSubdomainPersistence {
requestDomain := ctx.Request().URL.Host
if portIdx := strings.IndexByte(requestDomain, ':'); portIdx > 0 {
requestDomain = requestDomain[0:portIdx]
}
if IsValidCookieDomain(requestDomain) {
// RFC2109, we allow level 1 subdomains, but no further
// if we have localhost.com , we want the localhost.cos.
// so if we have something like: mysubdomain.localhost.com we want the localhost here
// if we have mysubsubdomain.mysubdomain.localhost.com we want the .mysubdomain.localhost.com here
// slow things here, especially the 'replace' but this is a good and understable( I hope) way to get the be able to set cookies from subdomains & domain with 1-level limit
if dotIdx := strings.LastIndexByte(requestDomain, '.'); dotIdx > 0 {
// is mysubdomain.localhost.com || mysubsubdomain.mysubdomain.localhost.com
s := requestDomain[0:dotIdx] // set mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
if secondDotIdx := strings.LastIndexByte(s, '.'); secondDotIdx > 0 {
//is mysubdomain.localhost || mysubsubdomain.mysubdomain.localhost
s = s[secondDotIdx+1:] // set to localhost || mysubdomain.localhost
}
// replace the s with the requestDomain before the domain's siffux
subdomainSuff := strings.LastIndexByte(requestDomain, '.')
if subdomainSuff > len(s) { // if it is actual exists as subdomain suffix
requestDomain = strings.Replace(requestDomain, requestDomain[0:subdomainSuff], s, 1) // set to localhost.com || mysubdomain.localhost.com
}
}
// finally set the .localhost.com (for(1-level) || .mysubdomain.localhost.com (for 2-level subdomain allow)
cookie.Domain = "." + requestDomain // . to allow persistence
}
}
cookie.HttpOnly = true
// MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds
if s.config.Expires >= 0 {
if s.config.Expires == 0 { // unlimited life
cookie.Expires = CookieExpireUnlimited
} else { // > 0
cookie.Expires = time.Now().Add(s.config.Expires)
}
cookie.MaxAge = int(cookie.Expires.Sub(time.Now()).Seconds())
}
// set the cookie to secure if this is a tls wrapped request
// and the configuration allows it.
if ctx.Request().TLS != nil && s.config.CookieSecureTLS {
cookie.Secure = true
}
// encode the session id cookie client value right before send it.
cookie.Value = s.encodeCookieValue(cookie.Value)
AddCookie(ctx, cookie)
return sess
}
cookieValue = s.decodeCookieValue(cookieValue)
sess := s.provider.Read(cookieValue, s.config.Expires)
return sess
}
// Destroy remove the session data and remove the associated cookie.
func (s *Sessions) Destroy(ctx context.Context) {
cookieValue := GetCookie(ctx, s.config.Cookie)
// decode the client's cookie value in order to find the server's session id
// to destroy the session data.
cookieValue = s.decodeCookieValue(cookieValue)
if cookieValue == "" { // nothing to destroy
return
}
RemoveCookie(ctx, s.config.Cookie)
s.provider.Destroy(cookieValue)
}
// DestroyByID removes the session entry
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
//
// It's safe to use it even if you are not sure if a session with that id exists.
//
// Note: the sid should be the original one (i.e: fetched by a store )
// it's not decoded.
func (s *Sessions) DestroyByID(sid string) {
s.provider.Destroy(sid)
}
// DestroyAll removes all sessions
// from the server-side memory (and database if registered).
// Client's session cookie will still exist but it will be reseted on the next request.
func (s *Sessions) DestroyAll() {
s.provider.DestroyAll()
}
// let's keep these funcs simple, we can do it with two lines but we may add more things in the future.
func (s *Sessions) decodeCookieValue(cookieValue string) string {
var cookieValueDecoded *string
if decode := s.config.Decode; decode != nil {
err := decode(s.config.Cookie, cookieValue, &cookieValueDecoded)
if err == nil {
cookieValue = *cookieValueDecoded
} else {
cookieValue = ""
}
}
return cookieValue
}
func (s *Sessions) encodeCookieValue(cookieValue string) string {
if encode := s.config.Encode; encode != nil {
newVal, err := encode(s.config.Cookie, cookieValue)
if err == nil {
cookieValue = newVal
} else {
cookieValue = ""
}
}
return cookieValue
}