github.com/decred/politeia@v1.4.0/politeiawww/legacy/sessions/sessions.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package sessions
     6  
     7  import (
     8  	"errors"
     9  	"net/http"
    10  	"time"
    11  
    12  	www "github.com/decred/politeia/politeiawww/api/www/v1"
    13  	"github.com/decred/politeia/politeiawww/legacy/user"
    14  	"github.com/google/uuid"
    15  	"github.com/gorilla/sessions"
    16  )
    17  
    18  const (
    19  	// SessionMaxAge is the max age for a session in seconds.
    20  	SessionMaxAge = 86400 // One day
    21  
    22  	// Session value keys. A user session contains a map that is used
    23  	// for application specific values. The following is a list of the
    24  	// keys for the politeiawww specific values.
    25  	sessionValueUserID    = "user_id"
    26  	sessionValueCreatedAt = "created_at"
    27  )
    28  
    29  var (
    30  	// ErrSessionNotFound is emitted when a session is not found in the
    31  	// session store.
    32  	ErrSessionNotFound = errors.New("session not found")
    33  )
    34  
    35  // Sessions manages politeiawww sessions.
    36  type Sessions struct {
    37  	store  sessions.Store
    38  	userdb user.Database
    39  }
    40  
    41  func sessionIsExpired(session *sessions.Session) bool {
    42  	createdAt := session.Values[sessionValueCreatedAt].(int64)
    43  	expiresAt := createdAt + int64(session.Options.MaxAge)
    44  	return time.Now().Unix() > expiresAt
    45  }
    46  
    47  // GetSession returns the Session for the session ID from the given http
    48  // request cookie. If no session exists then a new session object is returned.
    49  // Access IsNew on the session to check if it is an existing session or a new
    50  // one. The new session will not have any sessions values set, such as user_id,
    51  // and will not have been saved to the session store yet.
    52  func (s *Sessions) GetSession(r *http.Request) (*sessions.Session, error) {
    53  	log.Tracef("GetSession")
    54  
    55  	return s.store.Get(r, www.CookieSession)
    56  }
    57  
    58  // GetSessionUserID returns the user ID of the user for the given session. A
    59  // ErrSessionNotFound error is returned if a user session does not exist or
    60  // has expired.
    61  func (s *Sessions) GetSessionUserID(w http.ResponseWriter, r *http.Request) (string, error) {
    62  	log.Tracef("GetSessionUserID")
    63  
    64  	session, err := s.GetSession(r)
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  	if session.IsNew {
    69  		// If the session is new it means the request did not contain a
    70  		// valid session. This could be because it was expired or it
    71  		// did not exist.
    72  		log.Debugf("Session not found for user")
    73  		return "", ErrSessionNotFound
    74  	}
    75  
    76  	// Delete the session if its expired. Setting the MaxAge to <= 0
    77  	// and saving the session will trigger a deletion. The previous
    78  	// GetSession call should already filter out expired sessions so
    79  	// this is really just a sanity check.
    80  	if sessionIsExpired(session) {
    81  		log.Debug("Session is expired")
    82  		session.Options.MaxAge = -1
    83  		s.store.Save(r, w, session)
    84  		return "", ErrSessionNotFound
    85  	}
    86  
    87  	return session.Values[sessionValueUserID].(string), nil
    88  }
    89  
    90  // GetSessionUser returns the User for the given session. A errSessionFound
    91  // error is returned if a user session does not exist or has expired.
    92  func (s *Sessions) GetSessionUser(w http.ResponseWriter, r *http.Request) (*user.User, error) {
    93  	log.Tracef("GetSessionUser")
    94  
    95  	uid, err := s.GetSessionUserID(w, r)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	pid, err := uuid.Parse(uid)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	user, err := s.userdb.UserGetById(pid)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	if user.Deactivated {
   111  		log.Debugf("User has been deactivated")
   112  		err := s.DelSession(w, r)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  		return nil, ErrSessionNotFound
   117  	}
   118  
   119  	log.Debugf("Session found for user %v", user.ID)
   120  
   121  	return user, nil
   122  }
   123  
   124  // DelSession removes the given session from the session store.
   125  func (s *Sessions) DelSession(w http.ResponseWriter, r *http.Request) error {
   126  	log.Tracef("DelSession")
   127  
   128  	session, err := s.GetSession(r)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	if session.IsNew {
   133  		return ErrSessionNotFound
   134  	}
   135  
   136  	log.Debugf("Deleting user session %v", session.Values[sessionValueUserID])
   137  
   138  	// Saving the session with a negative MaxAge will cause it to be
   139  	// deleted.
   140  	session.Options.MaxAge = -1
   141  	return s.store.Save(r, w, session)
   142  }
   143  
   144  // NewSession creates a new session, adds it to the given http response
   145  // session cookie, and saves it to the session store. If the http request
   146  // already contains a session cookie then the session values will be updated
   147  // and the session will be updated in the session store.
   148  func (s *Sessions) NewSession(w http.ResponseWriter, r *http.Request, userID string) error {
   149  	log.Tracef("NewSession: %v", userID)
   150  
   151  	// Init session
   152  	session, err := s.GetSession(r)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	// Update session with politeiawww specific values
   158  	session.Values[sessionValueCreatedAt] = time.Now().Unix()
   159  	session.Values[sessionValueUserID] = userID
   160  
   161  	log.Debugf("Session created for user %v", userID)
   162  
   163  	// Update session in the store and update the response cookie
   164  	return s.store.Save(r, w, session)
   165  }
   166  
   167  // New returns a new Sessions context.
   168  func New(userdb user.Database, keyPairs ...[]byte) *Sessions {
   169  	return &Sessions{
   170  		store:  newSessionStore(userdb, keyPairs...),
   171  		userdb: userdb,
   172  	}
   173  }