8 "github.com/Sirupsen/logrus"
9 "github.com/gin-gonic/gin"
10 "github.com/googollee/go-socket.io"
11 uuid "github.com/satori/go.uuid"
12 "github.com/syncthing/syncthing/lib/sync"
15 const sessionCookieName = "xds-sid"
16 const sessionHeaderName = "XDS-SID"
18 const sessionMonitorTime = 10 // Time (in seconds) to schedule monitoring session tasks
20 const initSessionMaxAge = 10 // Initial session max age in seconds
21 const maxSessions = 100000 // Maximum number of sessions in sessMap map
23 const secureCookie = false // TODO: see https://github.com/astaxie/beego/blob/master/session/session.go#L218
25 // ClientSession contains the info of a user/client session
26 type ClientSession struct {
28 WSID string // only one WebSocket per client/session
30 IOSocket *socketio.Socket
37 // Sessions holds client sessions
38 type Sessions struct {
41 sessMap map[string]ClientSession
45 stop chan struct{} // signals intentional stop
48 // NewClientSessions .
49 func NewClientSessions(router *gin.Engine, log *logrus.Logger, cookieMaxAge string, sillyLog bool) *Sessions {
50 ckMaxAge, err := strconv.ParseInt(cookieMaxAge, 10, 0)
56 cookieMaxAge: ckMaxAge,
57 sessMap: make(map[string]ClientSession),
58 mutex: sync.NewMutex(),
60 LogLevelSilly: sillyLog,
61 stop: make(chan struct{}),
63 s.router.Use(s.Middleware())
65 // Start monitoring of sessions Map (use to manage expiration and cleanup)
71 // Stop sessions management
72 func (s *Sessions) Stop() {
76 // Middleware is used to managed session
77 func (s *Sessions) Middleware() gin.HandlerFunc {
78 return func(c *gin.Context) {
79 // FIXME Add CSRF management
84 // Allocate a new session key and put in cookie
85 sess = s.newSession("")
90 // Set session in cookie and in header
91 // Do not set Domain to localhost (http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain)
92 c.SetCookie(sessionCookieName, sess.ID, int(sess.MaxAge), "/", "",
94 c.Header(sessionHeaderName, sess.ID)
96 // Save session id in gin metadata
97 c.Set(sessionCookieName, sess.ID)
103 // Get returns the client session for a specific ID
104 func (s *Sessions) Get(c *gin.Context) *ClientSession {
107 // First get from gin metadata
108 v, exist := c.Get(sessionCookieName)
113 // Then look in cookie
114 if !exist || sid == "" {
115 sid, _ = c.Cookie(sessionCookieName)
118 // Then look in Header
120 sid = c.Request.Header.Get(sessionCookieName)
124 defer s.mutex.Unlock()
125 if key, ok := s.sessMap[sid]; ok {
126 // TODO: return a copy ???
133 // IOSocketGet Get socketio definition from sid
134 func (s *Sessions) IOSocketGet(sid string) *socketio.Socket {
136 defer s.mutex.Unlock()
137 sess, ok := s.sessMap[sid]
144 // UpdateIOSocket updates the IO Socket definition for of a session
145 func (s *Sessions) UpdateIOSocket(sid string, so *socketio.Socket) error {
147 defer s.mutex.Unlock()
148 if _, ok := s.sessMap[sid]; ok {
149 sess := s.sessMap[sid]
151 // Could be the case when socketio is closed/disconnected
154 sess.WSID = (*so).Id()
157 s.sessMap[sid] = sess
162 // nesSession Allocate a new client session
163 func (s *Sessions) newSession(prefix string) *ClientSession {
164 uuid := prefix + uuid.NewV4().String()
165 id := base64.URLEncoding.EncodeToString([]byte(uuid))
169 MaxAge: initSessionMaxAge,
171 expireAt: time.Now().Add(time.Duration(initSessionMaxAge) * time.Second),
175 defer s.mutex.Unlock()
177 s.sessMap[se.ID] = se
179 s.log.Debugf("NEW session (%d): %s", len(s.sessMap), id)
183 // refresh Move this session ID to the head of the list
184 func (s *Sessions) refresh(sid string) {
186 defer s.mutex.Unlock()
188 sess := s.sessMap[sid]
190 if sess.MaxAge < s.cookieMaxAge && sess.useCount > 1 {
191 sess.MaxAge = s.cookieMaxAge
192 sess.expireAt = time.Now().Add(time.Duration(sess.MaxAge) * time.Second)
195 // TODO - Add flood detection (like limit_req of nginx)
196 // (delayed request when to much requests in a short period of time)
198 s.sessMap[sid] = sess
201 func (s *Sessions) monitorSessMap() {
205 s.log.Debugln("Stop monitorSessMap")
207 case <-time.After(sessionMonitorTime * time.Second):
209 s.log.Debugf("Sessions Map size: %d", len(s.sessMap))
210 s.log.Debugf("Sessions Map : %v", s.sessMap)
213 if len(s.sessMap) > maxSessions {
214 s.log.Errorln("TOO MUCH sessions, cleanup old ones !")
218 for _, ss := range s.sessMap {
219 if ss.expireAt.Sub(time.Now()) < 0 {
220 s.log.Debugf("Delete expired session id: %s", ss.ID)
221 delete(s.sessMap, ss.ID)