sessions

package module
v0.1.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 24, 2025 License: Apache-2.0 Imports: 15 Imported by: 0

README

Very opinionated gin session middleware with DynamoDB backend

Go Reference

There are already several excellent DynamoDB store plugins for github.com/gin-contrib/sessions (well, mostly from github.com/gorilla/sessions). This module (named sessions) does something a bit different: you must bring your own struct that uses dynamodbav struct tags to model the DynamoDB table that contains session data. When handling a request, you can either work directly with a pointer to this struct, or use a type-safe sessions.Session-compatible implementation that can return an error or panic if you attempt to set a field with the wrong type.

I created this module because I love how easy it is to use the middleware to manage sessions, but I already have my own DynamoDB table for session data. If you're starting new, the various DynamoDB store plugins will abstract away the need to define the DynamoDB schema so you don't have to care about it at all. But if you already have your own table, this module is for you.

Usage

Get with:

go get github.com/nguyengg/go-aws-commons/gin-sessions-dynamodb
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	sessions "github.com/nguyengg/go-aws-commons/gin-sessions-dynamodb"
	"github.com/nguyengg/go-aws-commons/gin-sessions-dynamodb/groups"
	"github.com/nguyengg/go-aws-commons/opaque-token/hmac"
)

type Session struct {
	Id   string `dynamodbav:"sessionId,hashkey" tableName:"session"`
	User *User  `dynamodbav:"user"`
}

type User struct {
	Sub    string   `dynamodbav:"sub"`
	Groups []string `dynamodbav:"groups,stringset"`
}

func main() {
	r := gin.Default()

	// this example also enables CSRF generation and validation using secret provided by the AWS Secrets Lambda extension.
	hasher := hmac.New(hmac.WithKeyFromLambdaExtensionSecrets("csrf-secret"))
	r.Use(
		// if you don't explicitly provide a client, `config.LoadDefaultConfig` is used similar to this example.
		sessions.Sessions[Session]("sid", sessions.WithCSRF(hasher, sessions.DefaultCSRFCookieName)),
		// the Sessions middleware must act before the CSRF middleware.
		sessions.RequireCSRF(hasher))

	r.GET("/", func(c *gin.Context) {
		// this is type-safe way to interaction with my session struct.
		var mySession *Session = sessions.Get[Session](c)
		mySession.User = &User{Sub: "henry", Groups: []string{"poweruser"}}

		// because I enabled CSRF with WithCSRF, saving the session will write the CSRF cookie as well.
		if err := sessions.Save(c); err != nil {
			_ = c.AbortWithError(http.StatusBadGateway, err)
			return
		}

		// alternatively, I can use the sessions.Session interface "compatible" with gin and gorilla.
		s := sessions.Default(c)
		s.Set("user", "henry")
		if err := s.Save(); err != nil {
			_ = c.AbortWithError(http.StatusBadGateway, err)
			return
		}
	})

	// the module also provides a basic middleware to verify user from the session is authorised based on group
	// membership.
	r.GET("/protected/resource",
		groups.MustHave(func(c *gin.Context) (bool, groups.Groups) {
			user := sessions.Get[Session](c).User
			if user == nil {
				return false, nil
			}

			return true, user.Groups
		}, groups.OneOf("canReadResource", "canWriteResource")))
}

Documentation

Index

Constants

View Source
const (
	DefaultCSRFCookieName = "__Host-csrf"
	DefaultCSRFHeaderName = "X-Csrf-Token"
	DefaultCSRFFormName   = "csrf_token"
)
View Source
const (
	// DefaultKey is the gin context key for Session instance.
	DefaultKey = "github.com/nguyengg/go-aws-commons/gin-sessions-dynamodb"
)

Variables

View Source
var (
	ErrNoCSRFCookie         = errors.New("no CSRF cookie")
	ErrNoCSRFHeader         = errors.New("no CSRF header")
	ErrNoCSRFForm           = errors.New("no CSRF form parameter")
	ErrMismatchDoubleSubmit = errors.New("no CSRF form parameter")
)

Functions

func DefaultNewSessionId

func DefaultNewSessionId() string

DefaultNewSessionId creates a new UUID and returns its raw-URL-encoded content.

func DoubleSubmit added in v0.1.7

func DoubleSubmit(source CSRFSource, more ...CSRFSource) func(*CSRFOptions)

DoubleSubmit validates that all of the given CSRF sources must be available AND identical.

Useful if you use double-submit cookie pattern. This method replaces the existing [CSRFOptions.Sources].

func Get

func Get[T interface{}](c *gin.Context) *T

Get returns the pointer to the session struct attached to the request.

There are two ways to interact with the session middleware; this is the more type-safe way.

Usage:

type MySession struct {
	Id string `dynamodbav:"sessionId,hashkey" tableName:"session"`
}

r := gin.Default()
r.Use(Sessions[MySession]("sid"))
r.GET("/", func (c *gin.Context) {
	var s *MySession = Get[MySession](c)
})

func GetCSRF added in v0.1.7

func GetCSRF(c *gin.Context) string

GetCSRF returns the CSRF token associated with the given session.

The returned value is the expected CSRF token generated from the session's Id. If WithCSRF was not set up, this method always returns an empty string.

func New

func New[T interface{}](c *gin.Context) *T

New always create a new session and return the pointer thereto.

Usage:

type MySession struct {
	Id string `dynamodbav:"sessionId,hashkey" tableName:"session"`
}

r := gin.Default()
r.Use(Sessions[MySession]("sid"))
r.GET("/", func (c *gin.Context) {
	var s *MySession = New[MySession](c)
})

func RequireCSRF added in v0.1.7

func RequireCSRF(hasher hmac.Hasher, optFns ...func(*CSRFOptions)) gin.HandlerFunc

RequireCSRF creates a gin middleware for validating CSRF tokens from several potential sources.

CSRF requires Sessions to have been set up to provide a valid session Id that will be used as the payload for verifying the CSRF token.

func Save

func Save(c *gin.Context) error

Save can be used to save the current session to DynamoDB.

If you are not using Default and only use the type-safe Get and New, Save can be used instead of Session.Save.

func Sessions

func Sessions[T interface{}](name string, optFns ...func(*Session)) gin.HandlerFunc

Sessions creates a gin middleware for managing sessions of struct type T.

The name argument is the name of the cookie that stores the session Id. Type T must have these struct tags:

// Hash key is required, and its type must be a string since only string session Ids are supported.
Field string `dynamodbav:"sessionId,hashkey" tableName:"my-table"`

See ddb.Table for more information on how the struct tags are parsed. If type T does not implement the required tags or the tags fail validation, the function will panic.

Use WithCSRF if you want Save to also create a new CSRF token if the session is new.

func SetOptions added in v0.1.4

func SetOptions(c *gin.Context, options Options)

SetOptions can be used to modify the cookie options for the current session.

If you are not using Default and only use the type-safe Get and New, SetOptions can be used instead of Session.Options.

func WithCSRF added in v0.1.7

func WithCSRF(hasher hmac.Hasher, name string) func(*Session)

WithCSRF attaches to the session middleware the ability to set CSRF cookie as well when a new session is created.

The cookie will use the same settings as Session.CookieOptions but with [Options.HttpOnly] set to false. The CSRF token will be saved to the context and can be retrieved using GetCSRF if it needs to be embedded in the response as hidden form input.

Types

type CSRFOptions added in v0.1.7

type CSRFOptions struct {
	// Sources contains the various optional ways to retrieve the CSRF token from a request.
	//
	// By default, this value is filled out with CSRFFromCookie(DefaultCSRFCookieName),
	// CSRFFromHeader(DefaultCSRFHeaderName), and CSRFFromForm(DefaultCSRFFormName), all base64.RawURLEncoding.
	Sources []CSRFSource

	// MethodFilter controls which HTTP methods receive CSRF validation.
	//
	// By default, only DELETE, PATCH, POST, and PUT are subject.
	MethodFilter func(string) bool

	// AbortHandler is invoked when the CSRF tokens are invalid.
	//
	// By default, the request chain is aborted with http.StatusForbidden.
	AbortHandler func(*gin.Context)
	// contains filtered or unexported fields
}

CSRFOptions customises the CSRF middleware.

type CSRFSource added in v0.1.7

type CSRFSource func(*gin.Context) ([]byte, error)

CSRFSource provides a way to retrieve CSRF token from request.

func CSRFFromCookie added in v0.1.7

func CSRFFromCookie(name string) CSRFSource

CSRFFromCookie retrieves the CSRF base64-raw-url-encoded token from cookie with the given name.

func CSRFFromForm added in v0.1.7

func CSRFFromForm(name string) CSRFSource

CSRFFromForm retrieves the CSRF base64-raw-url-encoded token from the POST form parameter with the given name.

func CSRFFromHeader added in v0.1.7

func CSRFFromHeader(name string) CSRFSource

CSRFFromHeader retrieves the CSRF base64-raw-url-encoded token from request header with the given name.

type Options

type Options = sessions.Options

Options stores configuration for a session or session store. Fields are a subset of http.Cookie fields.

This is a clone from "github.com/gin-contrib/sessions" and "github.com/gorilla/sessions" which are both named "sessions" to help you not having to name your import conflicts.

type Session

type Session struct {
	// Client is the DynamoDB client for saving session data.
	//
	// By default, `config.LoadDefaultConfig` will be used to provide an instance.
	Client ddb.ManagerAPIClient

	// ClientOptions is passed to every DynamoDB call.
	ClientOptions []func(*dynamodb.Options)

	// NewSessionId is used to create the Id for a new session.
	//
	// By default, DefaultNewSessionId is used.
	NewSessionId func() string

	// CookieOptions modifies the session cookie settings.
	CookieOptions sessions.Options
	// contains filtered or unexported fields
}

Session implements gin sessions.Session in a type-safe way.

func Default

func Default(c *gin.Context) *Session

Default returns the Session instance attached to the request.

There are two ways to interact with the session middleware; this is one of them by letting you interact with the Session wrapper.

func (*Session) AddFlash

func (s *Session) AddFlash(value interface{}, vars ...string)

func (*Session) Clear

func (s *Session) Clear()

Clear deletes all values in the session.

The hashkey (session Id) will not be deleted, and any fields not tagged with `dynamodbav` will also be ignored.

func (*Session) Delete

func (s *Session) Delete(key interface{})

func (*Session) Flashes

func (s *Session) Flashes(vars ...string) []interface{}

func (*Session) Get

func (s *Session) Get(key interface{}) interface{}

func (*Session) ID

func (s *Session) ID() string

func (*Session) Options

func (s *Session) Options(options Options)

func (*Session) Save

func (s *Session) Save() error

func (*Session) Set

func (s *Session) Set(key interface{}, val interface{})

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL