Răsfoiți Sursa

custom session store using postgresql and gorm

sunguroku 5 ani în urmă
părinte
comite
f2cfac48f8
2 a modificat fișierele cu 191 adăugiri și 1 ștergeri
  1. 1 1
      internal/auth/authExample.go
  2. 190 0
      internal/auth/sessionstore.go

+ 1 - 1
internal/auth/auth.go → internal/auth/authExample.go

@@ -1,4 +1,4 @@
-package main
+package sessionstore
 
 import (
 	"fmt"

+ 190 - 0
internal/auth/sessionstore.go

@@ -0,0 +1,190 @@
+// Package sessionstore is a postgresql backend implementation of gorilla/sessions Session interface, based on
+// antonlindstrom/pgstore. Key change is to use GORM instead of typical sql driver using queries.
+// All queries are in queries/session, alias'd as "sq".
+package sessionstore
+
+import (
+	"database/sql"
+	"encoding/base32"
+	"net/http"
+	"strings"
+	"time"
+
+	"gorm.io/gorm"
+
+	"github.com/gorilla/securecookie"
+	"github.com/gorilla/sessions"
+	"github.com/pkg/errors"
+	"github.com/porter-dev/porter/internal/models"
+	sq "github.com/porter-dev/porter/internal/queries/session"
+)
+
+// structs
+
+// PGStore is not exported.
+type PGStore struct {
+	Codecs  []securecookie.Codec
+	Options *sessions.Options
+	Path    string
+	DbPool  *gorm.DB
+}
+
+// Helpers
+
+// MaxLength restricts the maximum length of new sessions to l.
+// If l is 0 there is no limit to the size of a session, use with caution.
+// The default for a new PGStore is 4096. PostgreSQL allows for max
+// value sizes of up to 1GB (http://www.postgresql.org/docs/current/interactive/datatype-character.html)
+func (db *PGStore) MaxLength(l int) {
+	for _, c := range db.Codecs {
+		if codec, ok := c.(*securecookie.SecureCookie); ok {
+			codec.MaxLength(l)
+		}
+	}
+}
+
+// MaxAge sets the maximum age for the store and the underlying cookie
+// implementation. Individual sessions can be deleted by setting Options.MaxAge
+// = -1 for that session.
+func (db *PGStore) MaxAge(age int) {
+	db.Options.MaxAge = age
+
+	// Set the maxAge for each securecookie instance.
+	for _, codec := range db.Codecs {
+		if sc, ok := codec.(*securecookie.SecureCookie); ok {
+			sc.MaxAge(age)
+		}
+	}
+}
+
+// load fetches a session by ID from the database and decodes its content
+// into session.Values.
+func (db *PGStore) load(session *sessions.Session) error {
+	res, err := sq.SelectSession(db.DbPool, &models.Session{Key: session.ID})
+
+	if err != nil {
+		return err
+	}
+
+	return securecookie.DecodeMulti(session.Name(), string(res.Data), &session.Values, db.Codecs...)
+}
+
+// save writes encoded session.Values to a database record.
+// writes to http_sessions table by default.
+func (db *PGStore) save(session *sessions.Session) error {
+	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, db.Codecs...)
+	if err != nil {
+		return err
+	}
+
+	exOn := session.Values["expires_on"]
+
+	var expiresOn time.Time
+
+	if exOn == nil {
+		expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
+	} else {
+		expiresOn = exOn.(time.Time)
+		if expiresOn.Sub(time.Now().Add(time.Second*time.Duration(session.Options.MaxAge))) < 0 {
+			expiresOn = time.Now().Add(time.Second * time.Duration(session.Options.MaxAge))
+		}
+	}
+
+	s := models.Session{
+		Key:       session.ID,
+		Data:      []byte(encoded),
+		ExpiresAt: expiresOn,
+	}
+
+	if session.IsNew {
+		_, createErr := sq.CreateSession(db.DbPool, &s)
+		return createErr
+	}
+
+	_, updateErr := sq.UpdateSession(db.DbPool, &s)
+	return updateErr
+}
+
+// Implementation of the interface (Get, New, Save)
+
+// NewStore takes an initialized db and session key pairs to create a session-store in postgres db.
+func NewStore(db *gorm.DB, keyPairs ...[]byte) (*PGStore, error) {
+	dbStore := &PGStore{
+		Codecs: securecookie.CodecsFromPairs(keyPairs...),
+		Options: &sessions.Options{
+			Path:   "/",
+			MaxAge: 86400 * 30,
+		},
+		DbPool: db,
+	}
+
+	return dbStore, nil
+}
+
+// Get Fetches a session for a given name after it has been added to the
+// registry.
+func (db *PGStore) Get(r *http.Request, name string) (*sessions.Session, error) {
+	return sessions.GetRegistry(r).Get(db, name)
+}
+
+// New returns a new session for the given name without adding it to the registry.
+func (db *PGStore) New(r *http.Request, name string) (*sessions.Session, error) {
+	session := sessions.NewSession(db, name)
+	if session == nil {
+		return nil, nil
+	}
+
+	opts := *db.Options
+	session.Options = &(opts)
+	session.IsNew = true
+
+	var err error
+	if c, errCookie := r.Cookie(name); errCookie == nil {
+		err = securecookie.DecodeMulti(name, c.Value, &session.ID, db.Codecs...)
+		if err == nil {
+			err = db.load(session)
+			if err == nil {
+				session.IsNew = false
+			} else if errors.Cause(err) == sql.ErrNoRows {
+				err = nil
+			}
+		}
+	}
+
+	db.MaxAge(db.Options.MaxAge)
+
+	return session, err
+}
+
+// Save saves the given session into the database and deletes cookies if needed
+func (db *PGStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
+	// Set delete if max-age is < 0
+	if session.Options.MaxAge < 0 {
+		if _, err := sq.DeleteSession(db.DbPool, &models.Session{Key: session.ID}); err != nil {
+			return err
+		}
+		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
+		return nil
+	}
+
+	if session.ID == "" {
+		// Generate a random session ID key suitable for storage in the DB
+		session.ID = strings.TrimRight(
+			base32.StdEncoding.EncodeToString(
+				securecookie.GenerateRandomKey(32),
+			), "=")
+	}
+
+	if err := db.save(session); err != nil {
+		return err
+	}
+
+	// Keep the session ID key in a cookie so it can be looked up in DB later.
+	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, db.Codecs...)
+	if err != nil {
+		return err
+	}
+
+	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
+	return nil
+}