📕 yarn is a Self-Hosted, Twitter™-like Decentralised micro-Blogging platform. No ads, no tracking, your content, your data! https://yarn.social/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
yarn/internal/session/session.go

121 lines
2.8 KiB

// Copyright 2020-present Yarn.social
// SPDX-License-Identifier: AGPL-3.0-or-later
package session
import (
"encoding/json"
"time"
sync "github.com/sasha-s/go-deadlock"
)
// Map ...
type Map map[string]string
// Session ...
type Session struct {
sync.RWMutex
store Store
ID string `json:"id"`
Data Map `json:"data"`
CreatedAt time.Time `json:"created"`
ExpiresAt time.Time `json:"expires"`
// trulyNeededCallback is invoked exactly once if session data were added
// and thus this session is required for sure. This way the session cookie
// can be created only when absolutely necessary. That is, when the user
// successfully logged in or a captcha is needed, etc.
//
// Upon first invocation this callback is then cleared.
//
// Invocing and resetting this callback is not thread-safe but there is no
// reason for it to be. A newly created session for a request will not be
// used by any other request in parallel.
trulyNeededCallback func()
// trulyRemovableCallback is invoked exactly once if the last session data
// was removed and thus this session is not required anymore for sure. This
// way the session cookie can be removed if no longer needed. That is, when
// the user successfully logged out out an anonymous session does not need
// the captcha anymore.
//
// Upon first invocation this callback is then cleared.
//
// Invocing and restting this callback is not thread-safe but there is
// probably no reason for it to be. It's very unlikely that several
// requests for the same session would remove a session cookie.
trulyRemovableCallback func()
}
func NewSession(store Store) *Session {
return &Session{store: store}
}
func LoadSession(data []byte, sess *Session) error {
if err := json.Unmarshal(data, &sess); err != nil {
return err
}
if sess.Data == nil {
sess.Data = make(Map)
}
return nil
}
func (s *Session) Expired() bool {
s.RLock()
defer s.RUnlock()
return s.ExpiresAt.Before(time.Now())
}
func (s *Session) Set(key, val string) error {
s.Lock()
s.Data[key] = val
s.Unlock()
if s.trulyNeededCallback != nil {
s.trulyNeededCallback()
s.trulyNeededCallback = nil
}
return s.store.SyncSession(s)
}
func (s *Session) Get(key string) (val string, ok bool) {
s.RLock()
defer s.RUnlock()
val, ok = s.Data[key]
return
}
func (s *Session) Has(key string) bool {
s.RLock()
defer s.RUnlock()
_, ok := s.Data[key]
return ok
}
func (s *Session) Del(key string) error {
s.Lock()
delete(s.Data, key)
s.Unlock()
if len(s.Data) == 0 {
if s.trulyRemovableCallback != nil {
s.trulyRemovableCallback()
s.trulyRemovableCallback = nil
}
return s.store.DelSession(s.ID)
}
return s.store.SyncSession(s)
}
func (s *Session) Bytes() ([]byte, error) {
s.RLock()
defer s.RUnlock()
data, err := json.Marshal(s)
if err != nil {
return nil, err
}
return data, nil
}