├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── goreleaser.yml │ ├── lint.yml │ ├── trivy-scan.yml │ ├── codeql.yml │ └── testing.yml ├── tester ├── tester_options_samesite_go1.10.go ├── tester_options_samesite_go1.11.go └── tester.go ├── mongo ├── mongomgo │ ├── mongomgo.go │ └── mongomgo_test.go └── mongodriver │ ├── mongodriver.go │ └── mongodriver_test.go ├── postgres ├── postgres.go └── postgres_test.go ├── gorm ├── gorm.go └── gorm_test.go ├── _example ├── cookie │ └── main.go ├── memstore │ └── main.go ├── redis │ └── main.go ├── filesystem │ └── main.go ├── memcached │ ├── ascii │ │ └── ascii.go │ └── binary │ │ └── binary.go ├── gorm │ └── main.go ├── mongo │ ├── mongomgo │ │ └── main.go │ └── mongodriver │ │ └── main.go └── postgres │ └── main.go ├── .goreleaser.yaml ├── session_options_go1.10.go ├── filesystem ├── filesystem_test.go └── filesystem.go ├── cookie ├── cookie_test.go └── cookie.go ├── memstore ├── memstore_test.go └── memstore.go ├── .golangci.yml ├── memcached ├── memcached.go └── memcached_test.go ├── LICENSE ├── session_options_go1.11.go ├── redis ├── redis_test.go └── redis.go ├── go.mod ├── sessions.go ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | vendor/* 3 | !/vendor/vendor.json 4 | /gorm/test.db 5 | .idea 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /tester/tester_options_samesite_go1.10.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.11 2 | 3 | package tester 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func testOptionSameSitego(t *testing.T, r *gin.Engine) { 12 | // not supported 13 | } 14 | -------------------------------------------------------------------------------- /mongo/mongomgo/mongomgo.go: -------------------------------------------------------------------------------- 1 | package mongomgo 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/globalsign/mgo" 6 | "github.com/kidstuff/mongostore" 7 | ) 8 | 9 | var _ sessions.Store = (*store)(nil) 10 | 11 | func NewStore(c *mgo.Collection, maxAge int, ensureTTL bool, keyPairs ...[]byte) sessions.Store { 12 | return &store{mongostore.NewMongoStore(c, maxAge, ensureTTL, keyPairs...)} 13 | } 14 | 15 | type store struct { 16 | *mongostore.MongoStore 17 | } 18 | 19 | func (c *store) Options(options sessions.Options) { 20 | c.MongoStore.Options = options.ToGorillaOptions() 21 | } 22 | -------------------------------------------------------------------------------- /mongo/mongodriver/mongodriver.go: -------------------------------------------------------------------------------- 1 | package mongodriver 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | 6 | "github.com/laziness-coders/mongostore" 7 | "go.mongodb.org/mongo-driver/mongo" 8 | ) 9 | 10 | var _ sessions.Store = (*store)(nil) 11 | 12 | func NewStore(c *mongo.Collection, maxAge int, ensureTTL bool, keyPairs ...[]byte) sessions.Store { 13 | return &store{mongostore.NewMongoStore(c, maxAge, ensureTTL, keyPairs...)} 14 | } 15 | 16 | type store struct { 17 | *mongostore.MongoStore 18 | } 19 | 20 | func (c *store) Options(options sessions.Options) { 21 | c.MongoStore.Options = options.ToGorillaOptions() 22 | } 23 | -------------------------------------------------------------------------------- /postgres/postgres.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/antonlindstrom/pgstore" 7 | "github.com/gin-contrib/sessions" 8 | ) 9 | 10 | type Store interface { 11 | sessions.Store 12 | } 13 | 14 | type store struct { 15 | *pgstore.PGStore 16 | } 17 | 18 | var _ Store = new(store) 19 | 20 | func NewStore(db *sql.DB, keyPairs ...[]byte) (Store, error) { 21 | p, err := pgstore.NewPGStoreFromPool(db, keyPairs...) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &store{p}, nil 27 | } 28 | 29 | func (s *store) Options(options sessions.Options) { 30 | s.PGStore.Options = options.ToGorillaOptions() 31 | } 32 | -------------------------------------------------------------------------------- /gorm/gorm.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-contrib/sessions" 7 | "github.com/wader/gormstore/v2" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type Store interface { 12 | sessions.Store 13 | } 14 | 15 | func NewStore(d *gorm.DB, expiredSessionCleanup bool, keyPairs ...[]byte) Store { 16 | s := gormstore.New(d, keyPairs...) 17 | if expiredSessionCleanup { 18 | quit := make(chan struct{}) 19 | go s.PeriodicCleanup(1*time.Hour, quit) 20 | } 21 | return &store{s} 22 | } 23 | 24 | type store struct { 25 | *gormstore.Store 26 | } 27 | 28 | func (s *store) Options(options sessions.Options) { 29 | s.SessionOpts = options.ToGorillaOptions() 30 | } 31 | -------------------------------------------------------------------------------- /_example/cookie/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/gin-contrib/sessions/cookie" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func main() { 10 | r := gin.Default() 11 | store := cookie.NewStore([]byte("secret")) 12 | r.Use(sessions.Sessions("mysession", store)) 13 | 14 | r.GET("/incr", func(c *gin.Context) { 15 | session := sessions.Default(c) 16 | var count int 17 | v := session.Get("count") 18 | if v == nil { 19 | count = 0 20 | } else { 21 | count = v.(int) 22 | count++ 23 | } 24 | session.Set("count", count) 25 | session.Save() 26 | c.JSON(200, gin.H{"count": count}) 27 | }) 28 | r.Run(":8000") 29 | } 30 | -------------------------------------------------------------------------------- /_example/memstore/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/gin-contrib/sessions/memstore" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func main() { 10 | r := gin.Default() 11 | store := memstore.NewStore([]byte("secret")) 12 | r.Use(sessions.Sessions("mysession", store)) 13 | 14 | r.GET("/incr", func(c *gin.Context) { 15 | session := sessions.Default(c) 16 | var count int 17 | v := session.Get("count") 18 | if v == nil { 19 | count = 0 20 | } else { 21 | count = v.(int) 22 | count++ 23 | } 24 | session.Set("count", count) 25 | session.Save() 26 | c.JSON(200, gin.H{"count": count}) 27 | }) 28 | r.Run(":8000") 29 | } 30 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - skip: true 3 | 4 | changelog: 5 | use: github 6 | groups: 7 | - title: Features 8 | regexp: "^.*feat[(\\w)]*:+.*$" 9 | order: 0 10 | - title: "Bug fixes" 11 | regexp: "^.*fix[(\\w)]*:+.*$" 12 | order: 1 13 | - title: "Enhancements" 14 | regexp: "^.*chore[(\\w)]*:+.*$" 15 | order: 2 16 | - title: "Refactor" 17 | regexp: "^.*refactor[(\\w)]*:+.*$" 18 | order: 3 19 | - title: "Build process updates" 20 | regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ 21 | order: 4 22 | - title: "Documentation updates" 23 | regexp: ^.*?docs?(\(.+\))??!?:.+$ 24 | order: 4 25 | - title: Others 26 | order: 999 27 | -------------------------------------------------------------------------------- /_example/redis/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/gin-contrib/sessions/redis" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func main() { 10 | r := gin.Default() 11 | store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret")) 12 | r.Use(sessions.Sessions("mysession", store)) 13 | 14 | r.GET("/incr", func(c *gin.Context) { 15 | session := sessions.Default(c) 16 | var count int 17 | v := session.Get("count") 18 | if v == nil { 19 | count = 0 20 | } else { 21 | count = v.(int) 22 | count++ 23 | } 24 | session.Set("count", count) 25 | session.Save() 26 | c.JSON(200, gin.H{"count": count}) 27 | }) 28 | r.Run(":8000") 29 | } 30 | -------------------------------------------------------------------------------- /_example/filesystem/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | sessions "github.com/geschke/gin-contrib-sessions" 5 | "github.com/geschke/gin-contrib-sessions/filesystem" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func main() { 10 | sessionPath := "/tmp/" 11 | r := gin.Default() 12 | store := filesystem.NewStore(sessionPath, []byte("secret")) 13 | r.Use(sessions.Sessions("mysession", store)) 14 | 15 | r.GET("/incr", func(c *gin.Context) { 16 | session := sessions.Default(c) 17 | var count int 18 | v := session.Get("count") 19 | if v == nil { 20 | count = 0 21 | } else { 22 | count = v.(int) 23 | count++ 24 | } 25 | session.Set("count", count) 26 | session.Save() 27 | c.JSON(200, gin.H{"count": count}) 28 | }) 29 | r.Run(":8000") 30 | } 31 | -------------------------------------------------------------------------------- /_example/memcached/ascii/ascii.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/bradfitz/gomemcache/memcache" 5 | "github.com/gin-contrib/sessions" 6 | "github.com/gin-contrib/sessions/memcached" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | r := gin.Default() 12 | store := memcached.NewStore(memcache.New("localhost:11211"), "", []byte("secret")) 13 | r.Use(sessions.Sessions("mysession", store)) 14 | 15 | r.GET("/incr", func(c *gin.Context) { 16 | session := sessions.Default(c) 17 | var count int 18 | v := session.Get("count") 19 | if v == nil { 20 | count = 0 21 | } else { 22 | count = v.(int) 23 | count++ 24 | } 25 | session.Set("count", count) 26 | session.Save() 27 | c.JSON(200, gin.H{"count": count}) 28 | }) 29 | r.Run(":8000") 30 | } 31 | -------------------------------------------------------------------------------- /_example/memcached/binary/binary.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/gin-contrib/sessions/memcached" 6 | "github.com/gin-gonic/gin" 7 | "github.com/memcachier/mc" 8 | ) 9 | 10 | func main() { 11 | r := gin.Default() 12 | client := mc.NewMC("localhost:11211", "username", "password") 13 | store := memcached.NewMemcacheStore(client, "", []byte("secret")) 14 | r.Use(sessions.Sessions("mysession", store)) 15 | 16 | r.GET("/incr", func(c *gin.Context) { 17 | session := sessions.Default(c) 18 | var count int 19 | v := session.Get("count") 20 | if v == nil { 21 | count = 0 22 | } else { 23 | count = v.(int) 24 | count++ 25 | } 26 | session.Set("count", count) 27 | session.Save() 28 | c.JSON(200, gin.H{"count": count}) 29 | }) 30 | r.Run(":8000") 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: Goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v6 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup go 21 | uses: actions/setup-go@v6 22 | with: 23 | go-version-file: go.mod 24 | check-latest: true 25 | - name: Run GoReleaser 26 | uses: goreleaser/goreleaser-action@v6 27 | with: 28 | # either 'goreleaser' (default) or 'goreleaser-pro' 29 | distribution: goreleaser 30 | version: latest 31 | args: release --clean 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Run CI Lint 2 | on: push 3 | 4 | jobs: 5 | test: 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest] 9 | go: [1.24, 1.25] 10 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 11 | runs-on: ${{ matrix.os }} 12 | 13 | env: 14 | GO111MODULE: on 15 | TESTTAGS: ${{ matrix.test-tags }} 16 | GOPROXY: https://proxy.golang.org 17 | steps: 18 | - name: Set up Go ${{ matrix.go }} 19 | uses: actions/setup-go@v6 20 | with: 21 | go-version: ${{ matrix.go }} 22 | 23 | - name: Checkout Code 24 | uses: actions/checkout@v6 25 | with: 26 | ref: ${{ github.ref }} 27 | 28 | - name: golangci-lint 29 | uses: golangci/golangci-lint-action@v9 30 | with: 31 | version: v2.6 32 | args: --verbose 33 | -------------------------------------------------------------------------------- /session_options_go1.10.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.11 2 | 3 | package sessions 4 | 5 | import ( 6 | gsessions "github.com/gorilla/sessions" 7 | ) 8 | 9 | // Options stores configuration for a session or session store. 10 | // Fields are a subset of http.Cookie fields. 11 | type Options struct { 12 | Path string 13 | Domain string 14 | // MaxAge=0 means no 'Max-Age' attribute specified. 15 | // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'. 16 | // MaxAge>0 means Max-Age attribute present and given in seconds. 17 | MaxAge int 18 | Secure bool 19 | HttpOnly bool 20 | } 21 | 22 | func (options Options) ToGorillaOptions() *gsessions.Options { 23 | return &gsessions.Options{ 24 | Path: options.Path, 25 | Domain: options.Domain, 26 | MaxAge: options.MaxAge, 27 | Secure: options.Secure, 28 | HttpOnly: options.HttpOnly, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /_example/gorm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | gormsessions "github.com/gin-contrib/sessions/gorm" 6 | "github.com/gin-gonic/gin" 7 | "gorm.io/driver/sqlite" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func main() { 12 | db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) 13 | if err != nil { 14 | panic(err) 15 | } 16 | store := gormsessions.NewStore(db, true, []byte("secret")) 17 | 18 | r := gin.Default() 19 | r.Use(sessions.Sessions("mysession", store)) 20 | 21 | r.GET("/incr", func(c *gin.Context) { 22 | session := sessions.Default(c) 23 | var count int 24 | v := session.Get("count") 25 | if v == nil { 26 | count = 0 27 | } else { 28 | count = v.(int) 29 | count++ 30 | } 31 | session.Set("count", count) 32 | session.Save() 33 | c.JSON(200, gin.H{"count": count}) 34 | }) 35 | r.Run(":8000") 36 | } 37 | -------------------------------------------------------------------------------- /_example/mongo/mongomgo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/gin-contrib/sessions/mongo/mongomgo" 6 | "github.com/gin-gonic/gin" 7 | "github.com/globalsign/mgo" 8 | ) 9 | 10 | func main() { 11 | r := gin.Default() 12 | session, err := mgo.Dial("localhost:27017/test") 13 | if err != nil { 14 | // handle err 15 | } 16 | 17 | c := session.DB("").C("sessions") 18 | store := mongomgo.NewStore(c, 3600, true, []byte("secret")) 19 | r.Use(sessions.Sessions("mysession", store)) 20 | 21 | r.GET("/incr", func(c *gin.Context) { 22 | session := sessions.Default(c) 23 | var count int 24 | v := session.Get("count") 25 | if v == nil { 26 | count = 0 27 | } else { 28 | count = v.(int) 29 | count++ 30 | } 31 | session.Set("count", count) 32 | session.Save() 33 | c.JSON(200, gin.H{"count": count}) 34 | }) 35 | r.Run(":8000") 36 | } 37 | -------------------------------------------------------------------------------- /_example/postgres/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/gin-contrib/sessions" 6 | "github.com/gin-contrib/sessions/postgres" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | r := gin.Default() 12 | db, err := sql.Open("postgres", "postgresql://username:password@localhost:5432/database") 13 | if err != nil { 14 | // handle err 15 | } 16 | 17 | store, err := postgres.NewStore(db, []byte("secret")) 18 | if err != nil { 19 | // handle err 20 | } 21 | 22 | r.Use(sessions.Sessions("mysession", store)) 23 | 24 | r.GET("/incr", func(c *gin.Context) { 25 | session := sessions.Default(c) 26 | var count int 27 | v := session.Get("count") 28 | if v == nil { 29 | count = 0 30 | } else { 31 | count = v.(int) 32 | count++ 33 | } 34 | session.Set("count", count) 35 | session.Save() 36 | c.JSON(200, gin.H{"count": count}) 37 | }) 38 | r.Run(":8000") 39 | } 40 | -------------------------------------------------------------------------------- /filesystem/filesystem_test.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/gin-contrib/sessions" 8 | "github.com/gin-contrib/sessions/tester" 9 | ) 10 | 11 | var sessionPath = os.TempDir() 12 | 13 | var newStore = func(_ *testing.T) sessions.Store { 14 | store := NewStore(sessionPath, []byte("secret")) 15 | return store 16 | } 17 | 18 | func TestFilesystem_SessionGetSet(t *testing.T) { 19 | tester.GetSet(t, newStore) 20 | } 21 | 22 | func TestFilesystem_SessionDeleteKey(t *testing.T) { 23 | tester.DeleteKey(t, newStore) 24 | } 25 | 26 | func TestFilesystem_SessionFlashes(t *testing.T) { 27 | tester.Flashes(t, newStore) 28 | } 29 | 30 | func TestFilesystem_SessionClear(t *testing.T) { 31 | tester.Clear(t, newStore) 32 | } 33 | 34 | func TestFilesystem_SessionOptions(t *testing.T) { 35 | tester.Options(t, newStore) 36 | } 37 | 38 | func TestFilesystem_SessionMany(t *testing.T) { 39 | tester.Many(t, newStore) 40 | } 41 | -------------------------------------------------------------------------------- /cookie/cookie_test.go: -------------------------------------------------------------------------------- 1 | package cookie 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gin-contrib/sessions" 7 | "github.com/gin-contrib/sessions/tester" 8 | ) 9 | 10 | var newStore = func(_ *testing.T) sessions.Store { 11 | store := NewStore([]byte("secret")) 12 | return store 13 | } 14 | 15 | func TestCookie_SessionGetSet(t *testing.T) { 16 | tester.GetSet(t, newStore) 17 | } 18 | 19 | func TestCookie_SessionDeleteKey(t *testing.T) { 20 | tester.DeleteKey(t, newStore) 21 | } 22 | 23 | func TestCookie_SessionFlashes(t *testing.T) { 24 | tester.Flashes(t, newStore) 25 | } 26 | 27 | func TestCookie_SessionClear(t *testing.T) { 28 | tester.Clear(t, newStore) 29 | } 30 | 31 | func TestCookie_SessionOptions(t *testing.T) { 32 | tester.Options(t, newStore) 33 | } 34 | 35 | func TestCookie_SessionMany(t *testing.T) { 36 | tester.Many(t, newStore) 37 | } 38 | 39 | func TestCookie_SessionManyStores(t *testing.T) { 40 | tester.ManyStores(t, newStore) 41 | } 42 | -------------------------------------------------------------------------------- /memstore/memstore_test.go: -------------------------------------------------------------------------------- 1 | package memstore 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gin-contrib/sessions" 7 | "github.com/gin-contrib/sessions/tester" 8 | ) 9 | 10 | var newStore = func(_ *testing.T) sessions.Store { 11 | store := NewStore([]byte("secret")) 12 | return store 13 | } 14 | 15 | func TestCookie_SessionGetSet(t *testing.T) { 16 | tester.GetSet(t, newStore) 17 | } 18 | 19 | func TestCookie_SessionDeleteKey(t *testing.T) { 20 | tester.DeleteKey(t, newStore) 21 | } 22 | 23 | func TestCookie_SessionFlashes(t *testing.T) { 24 | tester.Flashes(t, newStore) 25 | } 26 | 27 | func TestCookie_SessionClear(t *testing.T) { 28 | tester.Clear(t, newStore) 29 | } 30 | 31 | func TestCookie_SessionOptions(t *testing.T) { 32 | tester.Options(t, newStore) 33 | } 34 | 35 | func TestCookie_SessionMany(t *testing.T) { 36 | tester.Many(t, newStore) 37 | } 38 | 39 | func TestCookie_SessionManyStores(t *testing.T) { 40 | tester.ManyStores(t, newStore) 41 | } 42 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - bodyclose 6 | - dogsled 7 | - dupl 8 | - errcheck 9 | - exhaustive 10 | - gochecknoinits 11 | - goconst 12 | - gocritic 13 | - gocyclo 14 | - goprintffuncname 15 | - gosec 16 | - govet 17 | - ineffassign 18 | - lll 19 | - misspell 20 | - nakedret 21 | - noctx 22 | - nolintlint 23 | - rowserrcheck 24 | - staticcheck 25 | - unconvert 26 | - unparam 27 | - unused 28 | - whitespace 29 | exclusions: 30 | generated: lax 31 | presets: 32 | - comments 33 | - common-false-positives 34 | - legacy 35 | - std-error-handling 36 | paths: 37 | - third_party$ 38 | - builtin$ 39 | - examples$ 40 | formatters: 41 | enable: 42 | - gofmt 43 | - gofumpt 44 | - goimports 45 | exclusions: 46 | generated: lax 47 | paths: 48 | - third_party$ 49 | - builtin$ 50 | - examples$ 51 | -------------------------------------------------------------------------------- /gorm/gorm_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.13 2 | 3 | package gorm 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/gin-contrib/sessions" 9 | "github.com/gin-contrib/sessions/tester" 10 | "gorm.io/driver/sqlite" 11 | "gorm.io/gorm" 12 | ) 13 | 14 | var newStore = func(_ *testing.T) sessions.Store { 15 | db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) 16 | if err != nil { 17 | panic(err) 18 | } 19 | return NewStore(db, true, []byte("secret")) 20 | } 21 | 22 | func TestGorm_SessionGetSet(t *testing.T) { 23 | tester.GetSet(t, newStore) 24 | } 25 | 26 | func TestGorm_SessionDeleteKey(t *testing.T) { 27 | tester.DeleteKey(t, newStore) 28 | } 29 | 30 | func TestGorm_SessionFlashes(t *testing.T) { 31 | tester.Flashes(t, newStore) 32 | } 33 | 34 | func TestGorm_SessionClear(t *testing.T) { 35 | tester.Clear(t, newStore) 36 | } 37 | 38 | func TestGorm_SessionOptions(t *testing.T) { 39 | tester.Options(t, newStore) 40 | } 41 | 42 | func TestGorm_SessionMany(t *testing.T) { 43 | tester.Many(t, newStore) 44 | } 45 | -------------------------------------------------------------------------------- /tester/tester_options_samesite_go1.11.go: -------------------------------------------------------------------------------- 1 | //go:build go1.11 2 | 3 | package tester 4 | 5 | import ( 6 | "context" 7 | "net/http" 8 | "net/http/httptest" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/gin-contrib/sessions" 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | func testOptionSameSitego(t *testing.T, r *gin.Engine) { 17 | r.GET("/sameSite", func(c *gin.Context) { 18 | session := sessions.Default(c) 19 | session.Set("key", ok) 20 | session.Options(sessions.Options{ 21 | SameSite: http.SameSiteStrictMode, 22 | }) 23 | _ = session.Save() 24 | c.String(200, ok) 25 | }) 26 | 27 | res3 := httptest.NewRecorder() 28 | req3, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/sameSite", nil) 29 | r.ServeHTTP(res3, req3) 30 | 31 | s := strings.Split(res3.Header().Get("Set-Cookie"), "; ") 32 | if len(s) < 2 { 33 | t.Fatal("No SameSite=Strict found in options") 34 | } 35 | if s[1] != "SameSite=Strict" { 36 | t.Fatal("Error writing samesite with options:", s[1]) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /memstore/memstore.go: -------------------------------------------------------------------------------- 1 | package memstore 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/quasoft/memstore" 6 | ) 7 | 8 | type Store interface { 9 | sessions.Store 10 | } 11 | 12 | // Keys are defined in pairs to allow key rotation, but the common case is to set a single 13 | // authentication key and optionally an encryption key. 14 | // 15 | // The first key in a pair is used for authentication and the second for encryption. The 16 | // encryption key can be set to nil or omitted in the last pair, but the authentication key 17 | // is required in all pairs. 18 | // 19 | // It is recommended to use an authentication key with 32 or 64 bytes. The encryption key, 20 | // if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes. 21 | func NewStore(keyPairs ...[]byte) Store { 22 | return &store{memstore.NewMemStore(keyPairs...)} 23 | } 24 | 25 | type store struct { 26 | *memstore.MemStore 27 | } 28 | 29 | func (c *store) Options(options sessions.Options) { 30 | c.MemStore.Options = options.ToGorillaOptions() 31 | } 32 | -------------------------------------------------------------------------------- /cookie/cookie.go: -------------------------------------------------------------------------------- 1 | package cookie 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | gsessions "github.com/gorilla/sessions" 6 | ) 7 | 8 | type Store interface { 9 | sessions.Store 10 | } 11 | 12 | // Keys are defined in pairs to allow key rotation, but the common case is to set a single 13 | // authentication key and optionally an encryption key. 14 | // 15 | // The first key in a pair is used for authentication and the second for encryption. The 16 | // encryption key can be set to nil or omitted in the last pair, but the authentication key 17 | // is required in all pairs. 18 | // 19 | // It is recommended to use an authentication key with 32 or 64 bytes. The encryption key, 20 | // if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes. 21 | func NewStore(keyPairs ...[]byte) Store { 22 | return &store{gsessions.NewCookieStore(keyPairs...)} 23 | } 24 | 25 | type store struct { 26 | *gsessions.CookieStore 27 | } 28 | 29 | func (c *store) Options(options sessions.Options) { 30 | c.CookieStore.Options = options.ToGorillaOptions() 31 | } 32 | -------------------------------------------------------------------------------- /filesystem/filesystem.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | gsessions "github.com/gorilla/sessions" 6 | ) 7 | 8 | type Store interface { 9 | sessions.Store 10 | } 11 | 12 | // Keys are defined in pairs to allow key rotation, but the common case is to set a single 13 | // authentication key and optionally an encryption key. 14 | // 15 | // The first key in a pair is used for authentication and the second for encryption. The 16 | // encryption key can be set to nil or omitted in the last pair, but the authentication key 17 | // is required in all pairs. 18 | // 19 | // It is recommended to use an authentication key with 32 or 64 bytes. The encryption key, 20 | // if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes. 21 | func NewStore(path string, keyPairs ...[]byte) Store { 22 | return &store{gsessions.NewFilesystemStore(path, keyPairs...)} 23 | } 24 | 25 | type store struct { 26 | *gsessions.FilesystemStore 27 | } 28 | 29 | func (c *store) Options(options sessions.Options) { 30 | c.FilesystemStore.Options = options.ToGorillaOptions() 31 | } 32 | -------------------------------------------------------------------------------- /memcached/memcached.go: -------------------------------------------------------------------------------- 1 | package memcached 2 | 3 | import ( 4 | "github.com/bradfitz/gomemcache/memcache" 5 | gsm "github.com/bradleypeabody/gorilla-sessions-memcache" 6 | "github.com/gin-contrib/sessions" 7 | ) 8 | 9 | type Store interface { 10 | sessions.Store 11 | } 12 | 13 | // client: memcache client (github.com/bradfitz/gomemcache/memcache) 14 | // keyPrefix: prefix for the keys we store. 15 | func NewStore( 16 | client *memcache.Client, keyPrefix string, keyPairs ...[]byte, 17 | ) Store { 18 | memcacherClient := gsm.NewGoMemcacher(client) 19 | return NewMemcacheStore(memcacherClient, keyPrefix, keyPairs...) 20 | } 21 | 22 | // client: memcache client which implements the gsm.Memcacher interface 23 | // keyPrefix: prefix for the keys we store. 24 | func NewMemcacheStore( 25 | client gsm.Memcacher, keyPrefix string, keyPairs ...[]byte, 26 | ) Store { 27 | return &store{gsm.NewMemcacherStore(client, keyPrefix, keyPairs...)} 28 | } 29 | 30 | type store struct { 31 | *gsm.MemcacheStore 32 | } 33 | 34 | func (c *store) Options(options sessions.Options) { 35 | c.MemcacheStore.Options = options.ToGorillaOptions() 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Gin-Gonic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_example/mongo/mongodriver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/gin-contrib/sessions" 6 | "github.com/gin-contrib/sessions/mongo/mongodriver" 7 | "github.com/gin-gonic/gin" 8 | "go.mongodb.org/mongo-driver/mongo" 9 | "go.mongodb.org/mongo-driver/mongo/options" 10 | ) 11 | 12 | func main() { 13 | r := gin.Default() 14 | mongoOptions := options.Client().ApplyURI("mongodb://localhost:27017") 15 | client, err := mongo.NewClient(mongoOptions) 16 | if err != nil { 17 | // handle err 18 | } 19 | 20 | if err := client.Connect(context.Background()); err != nil { 21 | // handle err 22 | } 23 | 24 | c := client.Database("test").Collection("sessions") 25 | store := mongodriver.NewStore(c, 3600, true, []byte("secret")) 26 | r.Use(sessions.Sessions("mysession", store)) 27 | 28 | r.GET("/incr", func(c *gin.Context) { 29 | session := sessions.Default(c) 30 | var count int 31 | v := session.Get("count") 32 | if v == nil { 33 | count = 0 34 | } else { 35 | count = v.(int) 36 | count++ 37 | } 38 | session.Set("count", count) 39 | session.Save() 40 | c.JSON(200, gin.H{"count": count}) 41 | }) 42 | r.Run(":8000") 43 | } 44 | -------------------------------------------------------------------------------- /session_options_go1.11.go: -------------------------------------------------------------------------------- 1 | //go:build go1.11 2 | 3 | package sessions 4 | 5 | import ( 6 | "net/http" 7 | 8 | gsessions "github.com/gorilla/sessions" 9 | ) 10 | 11 | // Options stores configuration for a session or session store. 12 | // Fields are a subset of http.Cookie fields. 13 | type Options struct { 14 | Path string 15 | Domain string 16 | // MaxAge=0 means no 'Max-Age' attribute specified. 17 | // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'. 18 | // MaxAge>0 means Max-Age attribute present and given in seconds. 19 | MaxAge int 20 | Secure bool 21 | HttpOnly bool 22 | // rfc-draft to preventing CSRF: https://tools.ietf.org/html/draft-west-first-party-cookies-07 23 | // refer: https://godoc.org/net/http 24 | // https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/ 25 | SameSite http.SameSite 26 | } 27 | 28 | func (options Options) ToGorillaOptions() *gsessions.Options { 29 | return &gsessions.Options{ 30 | Path: options.Path, 31 | Domain: options.Domain, 32 | MaxAge: options.MaxAge, 33 | Secure: options.Secure, 34 | HttpOnly: options.HttpOnly, 35 | SameSite: options.SameSite, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mongo/mongomgo/mongomgo_test.go: -------------------------------------------------------------------------------- 1 | package mongomgo 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gin-contrib/sessions" 7 | "github.com/gin-contrib/sessions/tester" 8 | "github.com/globalsign/mgo" 9 | ) 10 | 11 | const mongoTestServer = "localhost:27017" 12 | 13 | var newStore = func(_ *testing.T) sessions.Store { 14 | session, err := mgo.Dial(mongoTestServer) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | c := session.DB("test").C("sessions") 20 | return NewStore(c, 3600, true, []byte("secret")) 21 | } 22 | 23 | func TestMongoMGOMGO_SessionGetSet(t *testing.T) { 24 | tester.GetSet(t, newStore) 25 | } 26 | 27 | func TestMongoMGO_SessionDeleteKey(t *testing.T) { 28 | tester.DeleteKey(t, newStore) 29 | } 30 | 31 | func TestMongoMGO_SessionFlashes(t *testing.T) { 32 | tester.Flashes(t, newStore) 33 | } 34 | 35 | func TestMongoMGO_SessionClear(t *testing.T) { 36 | tester.Clear(t, newStore) 37 | } 38 | 39 | func TestMongoMGO_SessionOptions(t *testing.T) { 40 | tester.Options(t, newStore) 41 | } 42 | 43 | func TestMongoMGO_SessionMany(t *testing.T) { 44 | tester.Many(t, newStore) 45 | } 46 | 47 | func TestMongo_SessionManyStores(t *testing.T) { 48 | tester.ManyStores(t, newStore) 49 | } 50 | -------------------------------------------------------------------------------- /postgres/postgres_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | "github.com/gin-contrib/sessions" 8 | "github.com/gin-contrib/sessions/tester" 9 | ) 10 | 11 | const postgresTestServer = "postgres://testuser:testpw@localhost:5432/testdb?sslmode=disable" 12 | 13 | var newStore = func(_ *testing.T) sessions.Store { 14 | db, err := sql.Open("postgres", postgresTestServer) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | store, err := NewStore(db, []byte("secret")) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | return store 25 | } 26 | 27 | func TestPostgres_SessionGetSet(t *testing.T) { 28 | tester.GetSet(t, newStore) 29 | } 30 | 31 | func TestPostgres_SessionDeleteKey(t *testing.T) { 32 | tester.DeleteKey(t, newStore) 33 | } 34 | 35 | func TestPostgres_SessionFlashes(t *testing.T) { 36 | tester.Flashes(t, newStore) 37 | } 38 | 39 | func TestPostgres_SessionClear(t *testing.T) { 40 | tester.Clear(t, newStore) 41 | } 42 | 43 | func TestPostgres_SessionOptions(t *testing.T) { 44 | tester.Options(t, newStore) 45 | } 46 | 47 | func TestPostgres_SessionMany(t *testing.T) { 48 | tester.Many(t, newStore) 49 | } 50 | 51 | func TestPostgres_SessionManyStores(t *testing.T) { 52 | tester.ManyStores(t, newStore) 53 | } 54 | -------------------------------------------------------------------------------- /mongo/mongodriver/mongodriver_test.go: -------------------------------------------------------------------------------- 1 | package mongodriver 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/gin-contrib/sessions" 9 | "github.com/gin-contrib/sessions/tester" 10 | "go.mongodb.org/mongo-driver/mongo" 11 | "go.mongodb.org/mongo-driver/mongo/options" 12 | ) 13 | 14 | const mongoTestServer = "mongodb://localhost:27017" 15 | 16 | var newStore = func(_ *testing.T) sessions.Store { 17 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 18 | defer cancel() 19 | client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoTestServer)) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | c := client.Database("test").Collection("sessions") 25 | return NewStore(c, 3600, true, []byte("secret")) 26 | } 27 | 28 | func TestMongoDriver_SessionGetSet(t *testing.T) { 29 | tester.GetSet(t, newStore) 30 | } 31 | 32 | func TestMongoDriver_SessionDeleteKey(t *testing.T) { 33 | tester.DeleteKey(t, newStore) 34 | } 35 | 36 | func TestMongoDriver_SessionFlashes(t *testing.T) { 37 | tester.Flashes(t, newStore) 38 | } 39 | 40 | func TestMongoDriver_SessionClear(t *testing.T) { 41 | tester.Clear(t, newStore) 42 | } 43 | 44 | func TestMongoDriver_SessionOptions(t *testing.T) { 45 | tester.Options(t, newStore) 46 | } 47 | 48 | func TestMongoDriver_SessionMany(t *testing.T) { 49 | tester.Many(t, newStore) 50 | } 51 | -------------------------------------------------------------------------------- /redis/redis_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gin-contrib/sessions" 7 | "github.com/gin-contrib/sessions/tester" 8 | ) 9 | 10 | const redisTestServer = "localhost:6379" 11 | 12 | var newRedisStore = func(_ *testing.T) sessions.Store { 13 | store, err := NewStore(10, "tcp", redisTestServer, "", "", []byte("secret")) 14 | if err != nil { 15 | panic(err) 16 | } 17 | return store 18 | } 19 | 20 | func TestRedis_SessionGetSet(t *testing.T) { 21 | tester.GetSet(t, newRedisStore) 22 | } 23 | 24 | func TestRedis_SessionDeleteKey(t *testing.T) { 25 | tester.DeleteKey(t, newRedisStore) 26 | } 27 | 28 | func TestRedis_SessionFlashes(t *testing.T) { 29 | tester.Flashes(t, newRedisStore) 30 | } 31 | 32 | func TestRedis_SessionClear(t *testing.T) { 33 | tester.Clear(t, newRedisStore) 34 | } 35 | 36 | func TestRedis_SessionOptions(t *testing.T) { 37 | tester.Options(t, newRedisStore) 38 | } 39 | 40 | func TestRedis_SessionMany(t *testing.T) { 41 | tester.Many(t, newRedisStore) 42 | } 43 | 44 | func TestRedis_SessionManyStores(t *testing.T) { 45 | tester.ManyStores(t, newRedisStore) 46 | } 47 | 48 | func TestGetRedisStore(t *testing.T) { 49 | t.Run("unmatched type", func(t *testing.T) { 50 | type store struct{ Store } 51 | rediStore, err := GetRedisStore(store{}) 52 | if err == nil || rediStore != nil { 53 | t.Fail() 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/trivy-scan.yml: -------------------------------------------------------------------------------- 1 | name: Trivy Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | schedule: 11 | # Run daily at 00:00 UTC 12 | - cron: '0 0 * * *' 13 | workflow_dispatch: # Allow manual trigger 14 | 15 | permissions: 16 | contents: read 17 | security-events: write # Required for uploading SARIF results 18 | 19 | jobs: 20 | trivy-scan: 21 | name: Trivy Security Scan 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v6 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Run Trivy vulnerability scanner (source code) 30 | uses: aquasecurity/trivy-action@0.33.1 31 | with: 32 | scan-type: 'fs' 33 | scan-ref: '.' 34 | scanners: 'vuln,secret,misconfig' 35 | format: 'sarif' 36 | output: 'trivy-results.sarif' 37 | severity: 'CRITICAL,HIGH,MEDIUM' 38 | ignore-unfixed: true 39 | 40 | - name: Upload Trivy results to GitHub Security tab 41 | uses: github/codeql-action/upload-sarif@v4 42 | if: always() 43 | with: 44 | sarif_file: 'trivy-results.sarif' 45 | 46 | - name: Run Trivy scanner (table output for logs) 47 | uses: aquasecurity/trivy-action@0.33.1 48 | if: always() 49 | with: 50 | scan-type: 'fs' 51 | scan-ref: '.' 52 | scanners: 'vuln,secret,misconfig' 53 | format: 'table' 54 | severity: 'CRITICAL,HIGH,MEDIUM' 55 | ignore-unfixed: true 56 | exit-code: '1' 57 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [master] 20 | schedule: 21 | - cron: "41 23 * * 6" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["go"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v6 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v4 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v4 55 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Run Testing 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | # Label of the container job 12 | runner-job: 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | go: [1.24, 1.25] 17 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - name: Start Redis 22 | uses: supercharge/redis-github-action@1.8.0 23 | with: 24 | redis-version: 4 25 | 26 | - name: Start MongoDB 4.2 27 | uses: supercharge/mongodb-github-action@1.12.0 28 | with: 29 | mongodb-version: 4.2 30 | 31 | - name: Start PostgreSQL 14 32 | uses: harmon758/postgresql-action@v1 33 | with: 34 | postgresql db: testdb 35 | postgresql user: testuser 36 | postgresql password: testpw 37 | postgresql version: "14" 38 | 39 | - uses: niden/actions-memcached@v7 40 | 41 | - name: Set up Go ${{ matrix.go }} 42 | uses: actions/setup-go@v6 43 | with: 44 | go-version: ${{ matrix.go }} 45 | 46 | - name: Checkout Code 47 | uses: actions/checkout@v6 48 | with: 49 | ref: ${{ github.ref }} 50 | 51 | - name: Run Tests 52 | run: | 53 | go test -v -covermode=atomic -coverprofile=coverage.out ./... 54 | 55 | - name: Upload coverage to Codecov 56 | uses: codecov/codecov-action@v5 57 | 58 | vulnerability-scanning: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v6 62 | with: 63 | fetch-depth: 0 64 | 65 | - name: Run Trivy vulnerability scanner in repo mode 66 | uses: aquasecurity/trivy-action@0.28.0 67 | with: 68 | scan-type: "fs" 69 | ignore-unfixed: true 70 | format: "sarif" 71 | output: "trivy-results.sarif" 72 | exit-code: "1" 73 | severity: "CRITICAL,HIGH" 74 | -------------------------------------------------------------------------------- /memcached/memcached_test.go: -------------------------------------------------------------------------------- 1 | package memcached 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bradfitz/gomemcache/memcache" 7 | "github.com/gin-contrib/sessions" 8 | "github.com/gin-contrib/sessions/tester" 9 | "github.com/memcachier/mc" 10 | ) 11 | 12 | const memcachedTestServer = "localhost:11211" 13 | 14 | var newStore = func(_ *testing.T) sessions.Store { 15 | store := NewStore( 16 | memcache.New(memcachedTestServer), "", []byte("secret")) 17 | return store 18 | } 19 | 20 | func TestMemcached_SessionGetSet(t *testing.T) { 21 | tester.GetSet(t, newStore) 22 | } 23 | 24 | func TestMemcached_SessionDeleteKey(t *testing.T) { 25 | tester.DeleteKey(t, newStore) 26 | } 27 | 28 | func TestMemcached_SessionFlashes(t *testing.T) { 29 | tester.Flashes(t, newStore) 30 | } 31 | 32 | func TestMemcached_SessionClear(t *testing.T) { 33 | tester.Clear(t, newStore) 34 | } 35 | 36 | func TestMemcached_SessionOptions(t *testing.T) { 37 | tester.Options(t, newStore) 38 | } 39 | 40 | func TestMemcached_SessionMany(t *testing.T) { 41 | tester.Many(t, newStore) 42 | } 43 | 44 | func TestMemcached_SessionManyStores(t *testing.T) { 45 | tester.ManyStores(t, newStore) 46 | } 47 | 48 | var newBinaryStore = func(_ *testing.T) sessions.Store { 49 | store := NewMemcacheStore( 50 | mc.NewMC(memcachedTestServer, "", ""), "", []byte("secret")) 51 | return store 52 | } 53 | 54 | func TestBinaryMemcached_SessionGetSet(t *testing.T) { 55 | tester.GetSet(t, newBinaryStore) 56 | } 57 | 58 | func TestBinaryMemcached_SessionDeleteKey(t *testing.T) { 59 | tester.DeleteKey(t, newBinaryStore) 60 | } 61 | 62 | func TestBinaryMemcached_SessionFlashes(t *testing.T) { 63 | tester.Flashes(t, newBinaryStore) 64 | } 65 | 66 | func TestBinaryMemcached_SessionClear(t *testing.T) { 67 | tester.Clear(t, newBinaryStore) 68 | } 69 | 70 | func TestBinaryMemcached_SessionOptions(t *testing.T) { 71 | tester.Options(t, newBinaryStore) 72 | } 73 | 74 | func TestBinaryMemcached_SessionMany(t *testing.T) { 75 | tester.Many(t, newBinaryStore) 76 | } 77 | 78 | func TestBinaryMemcached_SessionManyStores(t *testing.T) { 79 | tester.ManyStores(t, newBinaryStore) 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gin-contrib/sessions 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a 7 | github.com/boj/redistore v1.4.1 8 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf 9 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304 10 | github.com/gin-gonic/gin v1.10.1 11 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 12 | github.com/gomodule/redigo v1.9.2 13 | github.com/gorilla/context v1.1.2 14 | github.com/gorilla/sessions v1.4.0 15 | github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b 16 | github.com/laziness-coders/mongostore v0.0.14 17 | github.com/memcachier/mc v2.0.1+incompatible 18 | github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b 19 | github.com/wader/gormstore/v2 v2.0.3 20 | go.mongodb.org/mongo-driver v1.17.3 21 | gorm.io/driver/sqlite v1.5.7 22 | gorm.io/gorm v1.25.12 23 | ) 24 | 25 | require ( 26 | github.com/bytedance/sonic v1.13.2 // indirect 27 | github.com/bytedance/sonic/loader v0.2.4 // indirect 28 | github.com/cloudwego/base64x v0.1.5 // indirect 29 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 30 | github.com/gin-contrib/sse v1.0.0 // indirect 31 | github.com/go-playground/locales v0.14.1 // indirect 32 | github.com/go-playground/universal-translator v0.18.1 // indirect 33 | github.com/go-playground/validator/v10 v10.26.0 // indirect 34 | github.com/goccy/go-json v0.10.5 // indirect 35 | github.com/golang/snappy v1.0.0 // indirect 36 | github.com/gorilla/securecookie v1.1.2 // indirect 37 | github.com/jinzhu/inflection v1.0.0 // indirect 38 | github.com/jinzhu/now v1.1.5 // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/klauspost/compress v1.18.0 // indirect 41 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 42 | github.com/leodido/go-urn v1.4.0 // indirect 43 | github.com/lib/pq v1.10.9 // indirect 44 | github.com/mattn/go-isatty v0.0.20 // indirect 45 | github.com/mattn/go-sqlite3 v1.14.22 // indirect 46 | github.com/memcachier/mc/v3 v3.0.3 // indirect 47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 48 | github.com/modern-go/reflect2 v1.0.2 // indirect 49 | github.com/montanaflynn/stats v0.7.1 // indirect 50 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 51 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 52 | github.com/ugorji/go/codec v1.2.12 // indirect 53 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 54 | github.com/xdg-go/scram v1.1.2 // indirect 55 | github.com/xdg-go/stringprep v1.0.4 // indirect 56 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect 57 | golang.org/x/arch v0.16.0 // indirect 58 | golang.org/x/crypto v0.45.0 // indirect 59 | golang.org/x/net v0.47.0 // indirect 60 | golang.org/x/sync v0.18.0 // indirect 61 | golang.org/x/sys v0.38.0 // indirect 62 | golang.org/x/text v0.31.0 // indirect 63 | google.golang.org/protobuf v1.36.6 // indirect 64 | gopkg.in/yaml.v3 v3.0.1 // indirect 65 | ) 66 | -------------------------------------------------------------------------------- /redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/gin-contrib/sessions" 7 | 8 | "github.com/boj/redistore" 9 | "github.com/gomodule/redigo/redis" 10 | ) 11 | 12 | type Store interface { 13 | sessions.Store 14 | } 15 | 16 | // size: maximum number of idle connections. 17 | // network: tcp or udp 18 | // address: host:port 19 | // password: redis-password 20 | // Keys are defined in pairs to allow key rotation, but the common case is to set a single 21 | // authentication key and optionally an encryption key. 22 | // 23 | // The first key in a pair is used for authentication and the second for encryption. The 24 | // encryption key can be set to nil or omitted in the last pair, but the authentication key 25 | // is required in all pairs. 26 | // 27 | // It is recommended to use an authentication key with 32 or 64 bytes. The encryption key, 28 | // if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes. 29 | func NewStore(size int, network, address, username, password string, keyPairs ...[]byte) (Store, error) { 30 | s, err := redistore.NewRediStore(size, network, address, username, password, keyPairs...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return &store{s}, nil 35 | } 36 | 37 | // NewStoreWithDB creates a new Redis-based session store with the specified parameters. 38 | // 39 | // Parameters: 40 | // - size: The maximum number of idle connections in the pool. 41 | // - network: The network type (e.g., "tcp"). 42 | // - address: The address of the Redis server (e.g., "localhost:6379"). 43 | // - password: The password for the Redis server (if any). 44 | // - DB: The Redis database to be selected after connecting. 45 | // - keyPairs: A variadic list of byte slices used for authentication and encryption. 46 | // 47 | // Returns: 48 | // - Store: The created session store. 49 | // - error: An error if the store could not be created. 50 | func NewStoreWithDB(size int, network, address, username, password, db string, keyPairs ...[]byte) (Store, error) { 51 | s, err := redistore.NewRediStoreWithDB(size, network, address, username, password, db, keyPairs...) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return &store{s}, nil 56 | } 57 | 58 | // NewStoreWithPool creates a new session store using a Redis connection pool. 59 | // It takes a redis.Pool and an optional variadic list of key pairs for 60 | // authentication and encryption of session data. 61 | // 62 | // Parameters: 63 | // - pool: A redis.Pool object that manages a pool of Redis connections. 64 | // - keyPairs: Optional variadic list of byte slices used for authentication 65 | // and encryption of session data. 66 | // 67 | // Returns: 68 | // - Store: A new session store backed by Redis. 69 | // - error: An error if the store could not be created. 70 | func NewStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) (Store, error) { 71 | s, err := redistore.NewRediStoreWithPool(pool, keyPairs...) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &store{s}, nil 76 | } 77 | 78 | type store struct { 79 | *redistore.RediStore 80 | } 81 | 82 | // GetRedisStore retrieves the Redis store from the provided Store interface. 83 | // It returns an error if the provided Store is not of the expected type. 84 | // 85 | // Parameters: 86 | // - s: The Store interface from which to retrieve the Redis store. 87 | // 88 | // Returns: 89 | // - err: An error if the provided Store is not of the expected type. 90 | // - rediStore: The retrieved Redis store, or nil if there was an error. 91 | func GetRedisStore(s Store) (rediStore *redistore.RediStore, err error) { 92 | realStore, ok := s.(*store) 93 | if !ok { 94 | err = errors.New("unable to get the redis store: Store isn't *store") 95 | return nil, err 96 | } 97 | 98 | rediStore = realStore.RediStore 99 | return rediStore, nil 100 | } 101 | 102 | // SetKeyPrefix sets a key prefix for the given Redis store. 103 | // It retrieves the Redis store from the provided Store interface and sets the key prefix. 104 | // If there is an error retrieving the Redis store, it returns the error. 105 | // 106 | // Parameters: 107 | // - s: The Store interface from which the Redis store will be retrieved. 108 | // - prefix: The key prefix to be set for the Redis store. 109 | // 110 | // Returns: 111 | // - error: An error if there is an issue retrieving the Redis store, otherwise nil. 112 | func SetKeyPrefix(s Store, prefix string) error { 113 | rediStore, err := GetRedisStore(s) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | rediStore.SetKeyPrefix(prefix) 119 | return nil 120 | } 121 | 122 | func (c *store) Options(options sessions.Options) { 123 | c.RediStore.Options = options.ToGorillaOptions() 124 | } 125 | -------------------------------------------------------------------------------- /sessions.go: -------------------------------------------------------------------------------- 1 | package sessions 2 | 3 | import ( 4 | "log/slog" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/gorilla/context" 9 | "github.com/gorilla/sessions" 10 | ) 11 | 12 | const ( 13 | DefaultKey = "github.com/gin-contrib/sessions" 14 | errorFormat = "[sessions] ERROR!" 15 | ) 16 | 17 | type Store interface { 18 | sessions.Store 19 | Options(Options) 20 | } 21 | 22 | // Wraps thinly gorilla-session methods. 23 | // Session stores the values and optional configuration for a session. 24 | type Session interface { 25 | // ID of the session, generated by stores. It should not be used for user data. 26 | ID() string 27 | // Get returns the session value associated to the given key. 28 | Get(key interface{}) interface{} 29 | // Set sets the session value associated to the given key. 30 | Set(key interface{}, val interface{}) 31 | // Delete removes the session value associated to the given key. 32 | Delete(key interface{}) 33 | // Clear deletes all values in the session. 34 | Clear() 35 | // AddFlash adds a flash message to the session. 36 | // A single variadic argument is accepted, and it is optional: it defines the flash key. 37 | // If not defined "_flash" is used by default. 38 | AddFlash(value interface{}, vars ...string) 39 | // Flashes returns a slice of flash messages from the session. 40 | // A single variadic argument is accepted, and it is optional: it defines the flash key. 41 | // If not defined "_flash" is used by default. 42 | Flashes(vars ...string) []interface{} 43 | // Options sets configuration for a session. 44 | Options(Options) 45 | // Save saves all sessions used during the current request. 46 | Save() error 47 | } 48 | 49 | // SessionStore named session stores allow multiple sessions with different store types 50 | type SessionStore struct { 51 | Name string 52 | Store Store 53 | } 54 | 55 | func Sessions(name string, store Store) gin.HandlerFunc { 56 | return func(c *gin.Context) { 57 | s := &session{name, c.Request, store, nil, false, c.Writer} 58 | c.Set(DefaultKey, s) 59 | defer context.Clear(c.Request) 60 | c.Next() 61 | } 62 | } 63 | 64 | func SessionsMany(names []string, store Store) gin.HandlerFunc { 65 | return func(c *gin.Context) { 66 | sessions := make(map[string]Session, len(names)) 67 | for _, name := range names { 68 | sessions[name] = &session{name, c.Request, store, nil, false, c.Writer} 69 | } 70 | c.Set(DefaultKey, sessions) 71 | defer context.Clear(c.Request) 72 | c.Next() 73 | } 74 | } 75 | 76 | func SessionsManyStores(sessionStores []SessionStore) gin.HandlerFunc { 77 | return func(c *gin.Context) { 78 | sessions := make(map[string]Session, len(sessionStores)) 79 | for _, sessionStore := range sessionStores { 80 | sessions[sessionStore.Name] = &session{sessionStore.Name, c.Request, sessionStore.Store, nil, false, c.Writer} 81 | } 82 | c.Set(DefaultKey, sessions) 83 | defer context.Clear(c.Request) 84 | c.Next() 85 | } 86 | } 87 | 88 | type session struct { 89 | name string 90 | request *http.Request 91 | store Store 92 | session *sessions.Session 93 | written bool 94 | writer http.ResponseWriter 95 | } 96 | 97 | func (s *session) ID() string { 98 | return s.Session().ID 99 | } 100 | 101 | func (s *session) Get(key interface{}) interface{} { 102 | return s.Session().Values[key] 103 | } 104 | 105 | func (s *session) Set(key interface{}, val interface{}) { 106 | s.Session().Values[key] = val 107 | s.written = true 108 | } 109 | 110 | func (s *session) Delete(key interface{}) { 111 | delete(s.Session().Values, key) 112 | s.written = true 113 | } 114 | 115 | func (s *session) Clear() { 116 | for key := range s.Session().Values { 117 | s.Delete(key) 118 | } 119 | } 120 | 121 | func (s *session) AddFlash(value interface{}, vars ...string) { 122 | s.Session().AddFlash(value, vars...) 123 | s.written = true 124 | } 125 | 126 | func (s *session) Flashes(vars ...string) []interface{} { 127 | s.written = true 128 | return s.Session().Flashes(vars...) 129 | } 130 | 131 | func (s *session) Options(options Options) { 132 | s.written = true 133 | s.Session().Options = options.ToGorillaOptions() 134 | } 135 | 136 | func (s *session) Save() error { 137 | if s.Written() { 138 | e := s.Session().Save(s.request, s.writer) 139 | if e == nil { 140 | s.written = false 141 | } 142 | return e 143 | } 144 | return nil 145 | } 146 | 147 | func (s *session) Session() *sessions.Session { 148 | if s.session == nil { 149 | var err error 150 | s.session, err = s.store.Get(s.request, s.name) 151 | if err != nil { 152 | slog.Error(errorFormat, 153 | "err", err, 154 | ) 155 | } 156 | } 157 | return s.session 158 | } 159 | 160 | func (s *session) Written() bool { 161 | return s.written 162 | } 163 | 164 | // shortcut to get session 165 | func Default(c *gin.Context) Session { 166 | return c.MustGet(DefaultKey).(Session) 167 | } 168 | 169 | // shortcut to get session with given name 170 | func DefaultMany(c *gin.Context, name string) Session { 171 | return c.MustGet(DefaultKey).(map[string]Session)[name] 172 | } 173 | -------------------------------------------------------------------------------- /tester/tester.go: -------------------------------------------------------------------------------- 1 | // Package tester is a package to test each packages of session stores, such as 2 | // cookie, redis, memcached, mongo, memstore. You can use this to test your own session 3 | // stores. 4 | package tester 5 | 6 | import ( 7 | "context" 8 | "net/http" 9 | "net/http/httptest" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/gin-contrib/sessions" 14 | "github.com/gin-gonic/gin" 15 | ) 16 | 17 | type storeFactory func(*testing.T) sessions.Store 18 | 19 | const ( 20 | sessionName = "mysession" 21 | ok = "ok" 22 | ) 23 | 24 | func GetSet(t *testing.T, newStore storeFactory) { 25 | r := gin.Default() 26 | r.Use(sessions.Sessions(sessionName, newStore(t))) 27 | 28 | r.GET("/set", func(c *gin.Context) { 29 | session := sessions.Default(c) 30 | session.Set("key", ok) 31 | _ = session.Save() 32 | c.String(http.StatusOK, ok) 33 | }) 34 | 35 | r.GET("/get", func(c *gin.Context) { 36 | session := sessions.Default(c) 37 | if session.Get("key") != ok { 38 | t.Error("Session writing failed") 39 | } 40 | _ = session.Save() 41 | c.String(http.StatusOK, ok) 42 | }) 43 | 44 | res1 := httptest.NewRecorder() 45 | req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/set", nil) 46 | r.ServeHTTP(res1, req1) 47 | 48 | res2 := httptest.NewRecorder() 49 | req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/get", nil) 50 | copyCookies(req2, res1) 51 | r.ServeHTTP(res2, req2) 52 | } 53 | 54 | func DeleteKey(t *testing.T, newStore storeFactory) { 55 | r := gin.Default() 56 | r.Use(sessions.Sessions(sessionName, newStore(t))) 57 | 58 | r.GET("/set", func(c *gin.Context) { 59 | session := sessions.Default(c) 60 | session.Set("key", ok) 61 | _ = session.Save() 62 | c.String(http.StatusOK, ok) 63 | }) 64 | 65 | r.GET("/delete", func(c *gin.Context) { 66 | session := sessions.Default(c) 67 | session.Delete("key") 68 | _ = session.Save() 69 | c.String(http.StatusOK, ok) 70 | }) 71 | 72 | r.GET("/get", func(c *gin.Context) { 73 | session := sessions.Default(c) 74 | if session.Get("key") != nil { 75 | t.Error("Session deleting failed") 76 | } 77 | _ = session.Save() 78 | c.String(http.StatusOK, ok) 79 | }) 80 | 81 | res1 := httptest.NewRecorder() 82 | req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/set", nil) 83 | r.ServeHTTP(res1, req1) 84 | 85 | res2 := httptest.NewRecorder() 86 | req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/delete", nil) 87 | copyCookies(req2, res1) 88 | r.ServeHTTP(res2, req2) 89 | 90 | res3 := httptest.NewRecorder() 91 | req3, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/get", nil) 92 | copyCookies(req3, res2) 93 | r.ServeHTTP(res3, req3) 94 | } 95 | 96 | func Flashes(t *testing.T, newStore storeFactory) { 97 | r := gin.Default() 98 | store := newStore(t) 99 | r.Use(sessions.Sessions(sessionName, store)) 100 | 101 | r.GET("/set", func(c *gin.Context) { 102 | session := sessions.Default(c) 103 | session.AddFlash(ok) 104 | _ = session.Save() 105 | c.String(http.StatusOK, ok) 106 | }) 107 | 108 | r.GET("/flash", func(c *gin.Context) { 109 | session := sessions.Default(c) 110 | l := len(session.Flashes()) 111 | if l != 1 { 112 | t.Error("Flashes count does not equal 1. Equals ", l) 113 | } 114 | _ = session.Save() 115 | c.String(http.StatusOK, ok) 116 | }) 117 | 118 | r.GET("/check", func(c *gin.Context) { 119 | session := sessions.Default(c) 120 | l := len(session.Flashes()) 121 | if l != 0 { 122 | t.Error("flashes count is not 0 after reading. Equals ", l) 123 | } 124 | _ = session.Save() 125 | c.String(http.StatusOK, ok) 126 | }) 127 | 128 | res1 := httptest.NewRecorder() 129 | req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/set", nil) 130 | r.ServeHTTP(res1, req1) 131 | 132 | res2 := httptest.NewRecorder() 133 | req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/flash", nil) 134 | copyCookies(req2, res1) 135 | r.ServeHTTP(res2, req2) 136 | 137 | res3 := httptest.NewRecorder() 138 | req3, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/check", nil) 139 | copyCookies(req3, res2) 140 | r.ServeHTTP(res3, req3) 141 | } 142 | 143 | func Clear(t *testing.T, newStore storeFactory) { 144 | data := map[string]string{ 145 | "key": "val", 146 | "foo": "bar", 147 | } 148 | r := gin.Default() 149 | store := newStore(t) 150 | r.Use(sessions.Sessions(sessionName, store)) 151 | 152 | r.GET("/set", func(c *gin.Context) { 153 | session := sessions.Default(c) 154 | for k, v := range data { 155 | session.Set(k, v) 156 | } 157 | session.Clear() 158 | _ = session.Save() 159 | c.String(http.StatusOK, ok) 160 | }) 161 | 162 | r.GET("/check", func(c *gin.Context) { 163 | session := sessions.Default(c) 164 | for k, v := range data { 165 | if session.Get(k) == v { 166 | t.Fatal("Session clear failed") 167 | } 168 | } 169 | _ = session.Save() 170 | c.String(http.StatusOK, ok) 171 | }) 172 | 173 | res1 := httptest.NewRecorder() 174 | req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/set", nil) 175 | r.ServeHTTP(res1, req1) 176 | 177 | res2 := httptest.NewRecorder() 178 | req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/check", nil) 179 | copyCookies(req2, res1) 180 | r.ServeHTTP(res2, req2) 181 | } 182 | 183 | func Options(t *testing.T, newStore storeFactory) { 184 | r := gin.Default() 185 | store := newStore(t) 186 | store.Options(sessions.Options{ 187 | Domain: "localhost", 188 | }) 189 | r.Use(sessions.Sessions(sessionName, store)) 190 | 191 | r.GET("/domain", func(c *gin.Context) { 192 | session := sessions.Default(c) 193 | session.Set("key", ok) 194 | session.Options(sessions.Options{ 195 | Path: "/foo/bar/bat", 196 | }) 197 | _ = session.Save() 198 | c.String(http.StatusOK, ok) 199 | }) 200 | r.GET("/path", func(c *gin.Context) { 201 | session := sessions.Default(c) 202 | session.Set("key", ok) 203 | _ = session.Save() 204 | c.String(http.StatusOK, ok) 205 | }) 206 | r.GET("/set", func(c *gin.Context) { 207 | session := sessions.Default(c) 208 | session.Set("key", ok) 209 | _ = session.Save() 210 | c.String(http.StatusOK, ok) 211 | }) 212 | r.GET("/expire", func(c *gin.Context) { 213 | session := sessions.Default(c) 214 | session.Options(sessions.Options{ 215 | MaxAge: -1, 216 | }) 217 | _ = session.Save() 218 | c.String(http.StatusOK, ok) 219 | }) 220 | r.GET("/check", func(c *gin.Context) { 221 | session := sessions.Default(c) 222 | val := session.Get("key") 223 | if val != nil { 224 | t.Fatal("Session expiration failed") 225 | } 226 | c.String(http.StatusOK, ok) 227 | }) 228 | 229 | testOptionSameSitego(t, r) 230 | 231 | res1 := httptest.NewRecorder() 232 | req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/domain", nil) 233 | r.ServeHTTP(res1, req1) 234 | 235 | res2 := httptest.NewRecorder() 236 | req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/path", nil) 237 | r.ServeHTTP(res2, req2) 238 | 239 | res3 := httptest.NewRecorder() 240 | req3, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/set", nil) 241 | r.ServeHTTP(res3, req3) 242 | 243 | res4 := httptest.NewRecorder() 244 | req4, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/expire", nil) 245 | r.ServeHTTP(res4, req4) 246 | 247 | res5 := httptest.NewRecorder() 248 | req5, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/check", nil) 249 | r.ServeHTTP(res5, req5) 250 | 251 | for _, c := range res1.Header().Values("Set-Cookie") { 252 | s := strings.Split(c, "; ") 253 | if len(s) < 2 { 254 | t.Fatal("No Path=/foo/bar/batt found in options") 255 | } 256 | if s[1] != "Path=/foo/bar/bat" { 257 | t.Fatal("Error writing path with options:", s[1]) 258 | } 259 | } 260 | 261 | for _, c := range res2.Header().Values("Set-Cookie") { 262 | s := strings.Split(c, "; ") 263 | if len(s) < 2 { 264 | t.Fatal("No Domain=localhost found in options") 265 | } 266 | 267 | if s[1] != "Domain=localhost" { 268 | t.Fatal("Error writing domain with options:", s[1]) 269 | } 270 | } 271 | } 272 | 273 | func Many(t *testing.T, newStore storeFactory) { 274 | r := gin.Default() 275 | sessionNames := []string{"a", "b"} 276 | 277 | r.Use(sessions.SessionsMany(sessionNames, newStore(t))) 278 | 279 | r.GET("/set", func(c *gin.Context) { 280 | sessionA := sessions.DefaultMany(c, "a") 281 | sessionA.Set("hello", "world") 282 | _ = sessionA.Save() 283 | 284 | sessionB := sessions.DefaultMany(c, "b") 285 | sessionB.Set("foo", "bar") 286 | _ = sessionB.Save() 287 | c.String(http.StatusOK, ok) 288 | }) 289 | 290 | r.GET("/get", func(c *gin.Context) { 291 | sessionA := sessions.DefaultMany(c, "a") 292 | if sessionA.Get("hello") != "world" { 293 | t.Error("Session writing failed") 294 | } 295 | _ = sessionA.Save() 296 | 297 | sessionB := sessions.DefaultMany(c, "b") 298 | if sessionB.Get("foo") != "bar" { 299 | t.Error("Session writing failed") 300 | } 301 | _ = sessionB.Save() 302 | c.String(http.StatusOK, ok) 303 | }) 304 | 305 | res1 := httptest.NewRecorder() 306 | req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/set", nil) 307 | r.ServeHTTP(res1, req1) 308 | 309 | res2 := httptest.NewRecorder() 310 | req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/get", nil) 311 | header := "" 312 | for _, x := range res1.Header()["Set-Cookie"] { 313 | header += strings.Split(x, ";")[0] + "; \n" 314 | } 315 | req2.Header.Set("Cookie", header) 316 | r.ServeHTTP(res2, req2) 317 | } 318 | 319 | func ManyStores(t *testing.T, newStore storeFactory) { 320 | r := gin.Default() 321 | 322 | store := newStore(t) 323 | sessionStores := []sessions.SessionStore{ 324 | {Name: "a", Store: store}, 325 | {Name: "b", Store: store}, 326 | } 327 | 328 | r.Use(sessions.SessionsManyStores(sessionStores)) 329 | 330 | r.GET("/set", func(c *gin.Context) { 331 | sessionA := sessions.DefaultMany(c, "a") 332 | sessionA.Set("hello", "world") 333 | _ = sessionA.Save() 334 | 335 | sessionB := sessions.DefaultMany(c, "b") 336 | sessionB.Set("foo", "bar") 337 | _ = sessionB.Save() 338 | c.String(http.StatusOK, ok) 339 | }) 340 | 341 | r.GET("/get", func(c *gin.Context) { 342 | sessionA := sessions.DefaultMany(c, "a") 343 | if sessionA.Get("hello") != "world" { 344 | t.Error("Session writing failed") 345 | } 346 | _ = sessionA.Save() 347 | 348 | sessionB := sessions.DefaultMany(c, "b") 349 | if sessionB.Get("foo") != "bar" { 350 | t.Error("Session writing failed") 351 | } 352 | _ = sessionB.Save() 353 | c.String(http.StatusOK, ok) 354 | }) 355 | 356 | res1 := httptest.NewRecorder() 357 | req1, _ := http.NewRequestWithContext(context.Background(), "GET", "/set", nil) 358 | r.ServeHTTP(res1, req1) 359 | 360 | res2 := httptest.NewRecorder() 361 | req2, _ := http.NewRequestWithContext(context.Background(), "GET", "/get", nil) 362 | header := "" 363 | for _, x := range res1.Header()["Set-Cookie"] { 364 | header += strings.Split(x, ";")[0] + "; \n" 365 | } 366 | req2.Header.Set("Cookie", header) 367 | r.ServeHTTP(res2, req2) 368 | } 369 | 370 | func copyCookies(req *http.Request, res *httptest.ResponseRecorder) { 371 | req.Header.Set("Cookie", strings.Join(res.Header().Values("Set-Cookie"), "; ")) 372 | } 373 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sessions 2 | 3 | [![Run CI Lint](https://github.com/gin-contrib/sessions/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/gin-contrib/sessions/actions/workflows/lint.yml) 4 | [![Run Testing](https://github.com/gin-contrib/sessions/actions/workflows/testing.yml/badge.svg?branch=master)](https://github.com/gin-contrib/sessions/actions/workflows/testing.yml) 5 | [![Trivy Security Scan](https://github.com/gin-contrib/sessions/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/gin-contrib/sessions/actions/workflows/trivy-scan.yml) 6 | [![codecov](https://codecov.io/gh/gin-contrib/sessions/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/sessions) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/sessions)](https://goreportcard.com/report/github.com/gin-contrib/sessions) 8 | [![GoDoc](https://godoc.org/github.com/gin-contrib/sessions?status.svg)](https://godoc.org/github.com/gin-contrib/sessions) 9 | 10 | Gin middleware for session management with multi-backend support: 11 | 12 | - [cookie-based](#cookie-based) 13 | - [Redis](#redis) 14 | - [memcached](#memcached) 15 | - [MongoDB](#mongodb) 16 | - [GORM](#gorm) 17 | - [memstore](#memstore) 18 | - [PostgreSQL](#postgresql) 19 | - [Filesystem](#Filesystem) 20 | 21 | ## Usage 22 | 23 | ### Start using it 24 | 25 | Download and install it: 26 | 27 | ```bash 28 | go get github.com/gin-contrib/sessions 29 | ``` 30 | 31 | Import it in your code: 32 | 33 | ```go 34 | import "github.com/gin-contrib/sessions" 35 | ``` 36 | 37 | ## Basic Examples 38 | 39 | ### single session 40 | 41 | ```go 42 | package main 43 | 44 | import ( 45 | "github.com/gin-contrib/sessions" 46 | "github.com/gin-contrib/sessions/cookie" 47 | "github.com/gin-gonic/gin" 48 | ) 49 | 50 | func main() { 51 | r := gin.Default() 52 | store := cookie.NewStore([]byte("secret")) 53 | r.Use(sessions.Sessions("mysession", store)) 54 | 55 | r.GET("/hello", func(c *gin.Context) { 56 | session := sessions.Default(c) 57 | 58 | if session.Get("hello") != "world" { 59 | session.Set("hello", "world") 60 | session.Save() 61 | } 62 | 63 | c.JSON(200, gin.H{"hello": session.Get("hello")}) 64 | }) 65 | r.Run(":8000") 66 | } 67 | ``` 68 | 69 | ### multiple sessions 70 | 71 | ```go 72 | package main 73 | 74 | import ( 75 | "github.com/gin-contrib/sessions" 76 | "github.com/gin-contrib/sessions/cookie" 77 | "github.com/gin-gonic/gin" 78 | ) 79 | 80 | func main() { 81 | r := gin.Default() 82 | store := cookie.NewStore([]byte("secret")) 83 | sessionNames := []string{"a", "b"} 84 | r.Use(sessions.SessionsMany(sessionNames, store)) 85 | 86 | r.GET("/hello", func(c *gin.Context) { 87 | sessionA := sessions.DefaultMany(c, "a") 88 | sessionB := sessions.DefaultMany(c, "b") 89 | 90 | if sessionA.Get("hello") != "world!" { 91 | sessionA.Set("hello", "world!") 92 | sessionA.Save() 93 | } 94 | 95 | if sessionB.Get("hello") != "world?" { 96 | sessionB.Set("hello", "world?") 97 | sessionB.Save() 98 | } 99 | 100 | c.JSON(200, gin.H{ 101 | "a": sessionA.Get("hello"), 102 | "b": sessionB.Get("hello"), 103 | }) 104 | }) 105 | r.Run(":8000") 106 | } 107 | ``` 108 | 109 | ### multiple sessions with different stores 110 | 111 | ```go 112 | package main 113 | 114 | import ( 115 | "github.com/gin-contrib/sessions" 116 | "github.com/gin-contrib/sessions/cookie" 117 | "github.com/gin-gonic/gin" 118 | ) 119 | 120 | func main() { 121 | r := gin.Default() 122 | cookieStore := cookie.NewStore([]byte("secret")) 123 | redisStore, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret")) 124 | sessionStores := []sessions.SessionStore{ 125 | { 126 | Name: "a", 127 | Store: cookieStore, 128 | }, 129 | { 130 | Name: "b", 131 | Store: redisStore, 132 | }, 133 | } 134 | r.Use(sessions.SessionsManyStores(sessionStores)) 135 | 136 | r.GET("/hello", func(c *gin.Context) { 137 | sessionA := sessions.DefaultMany(c, "a") 138 | sessionB := sessions.DefaultMany(c, "b") 139 | 140 | if sessionA.Get("hello") != "world!" { 141 | sessionA.Set("hello", "world!") 142 | sessionA.Save() 143 | } 144 | 145 | if sessionB.Get("hello") != "world?" { 146 | sessionB.Set("hello", "world?") 147 | sessionB.Save() 148 | } 149 | 150 | c.JSON(200, gin.H{ 151 | "a": sessionA.Get("hello"), 152 | "b": sessionB.Get("hello"), 153 | }) 154 | }) 155 | r.Run(":8000") 156 | } 157 | ``` 158 | 159 | ## Backend Examples 160 | 161 | ### cookie-based 162 | 163 | ```go 164 | package main 165 | 166 | import ( 167 | "github.com/gin-contrib/sessions" 168 | "github.com/gin-contrib/sessions/cookie" 169 | "github.com/gin-gonic/gin" 170 | ) 171 | 172 | func main() { 173 | r := gin.Default() 174 | store := cookie.NewStore([]byte("secret")) 175 | r.Use(sessions.Sessions("mysession", store)) 176 | 177 | r.GET("/incr", func(c *gin.Context) { 178 | session := sessions.Default(c) 179 | var count int 180 | v := session.Get("count") 181 | if v == nil { 182 | count = 0 183 | } else { 184 | count = v.(int) 185 | count++ 186 | } 187 | session.Set("count", count) 188 | session.Save() 189 | c.JSON(200, gin.H{"count": count}) 190 | }) 191 | r.Run(":8000") 192 | } 193 | ``` 194 | 195 | ### Redis 196 | 197 | ```go 198 | package main 199 | 200 | import ( 201 | "github.com/gin-contrib/sessions" 202 | "github.com/gin-contrib/sessions/redis" 203 | "github.com/gin-gonic/gin" 204 | ) 205 | 206 | func main() { 207 | r := gin.Default() 208 | store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret")) 209 | r.Use(sessions.Sessions("mysession", store)) 210 | 211 | r.GET("/incr", func(c *gin.Context) { 212 | session := sessions.Default(c) 213 | var count int 214 | v := session.Get("count") 215 | if v == nil { 216 | count = 0 217 | } else { 218 | count = v.(int) 219 | count++ 220 | } 221 | session.Set("count", count) 222 | session.Save() 223 | c.JSON(200, gin.H{"count": count}) 224 | }) 225 | r.Run(":8000") 226 | } 227 | ``` 228 | 229 | ### Memcached 230 | 231 | #### ASCII Protocol 232 | 233 | ```go 234 | package main 235 | 236 | import ( 237 | "github.com/bradfitz/gomemcache/memcache" 238 | "github.com/gin-contrib/sessions" 239 | "github.com/gin-contrib/sessions/memcached" 240 | "github.com/gin-gonic/gin" 241 | ) 242 | 243 | func main() { 244 | r := gin.Default() 245 | store := memcached.NewStore(memcache.New("localhost:11211"), "", []byte("secret")) 246 | r.Use(sessions.Sessions("mysession", store)) 247 | 248 | r.GET("/incr", func(c *gin.Context) { 249 | session := sessions.Default(c) 250 | var count int 251 | v := session.Get("count") 252 | if v == nil { 253 | count = 0 254 | } else { 255 | count = v.(int) 256 | count++ 257 | } 258 | session.Set("count", count) 259 | session.Save() 260 | c.JSON(200, gin.H{"count": count}) 261 | }) 262 | r.Run(":8000") 263 | } 264 | ``` 265 | 266 | #### Binary protocol (with optional SASL authentication) 267 | 268 | ```go 269 | package main 270 | 271 | import ( 272 | "github.com/gin-contrib/sessions" 273 | "github.com/gin-contrib/sessions/memcached" 274 | "github.com/gin-gonic/gin" 275 | "github.com/memcachier/mc" 276 | ) 277 | 278 | func main() { 279 | r := gin.Default() 280 | client := mc.NewMC("localhost:11211", "username", "password") 281 | store := memcached.NewMemcacheStore(client, "", []byte("secret")) 282 | r.Use(sessions.Sessions("mysession", store)) 283 | 284 | r.GET("/incr", func(c *gin.Context) { 285 | session := sessions.Default(c) 286 | var count int 287 | v := session.Get("count") 288 | if v == nil { 289 | count = 0 290 | } else { 291 | count = v.(int) 292 | count++ 293 | } 294 | session.Set("count", count) 295 | session.Save() 296 | c.JSON(200, gin.H{"count": count}) 297 | }) 298 | r.Run(":8000") 299 | } 300 | ``` 301 | 302 | ### MongoDB 303 | 304 | #### mgo 305 | 306 | ```go 307 | package main 308 | 309 | import ( 310 | "github.com/gin-contrib/sessions" 311 | "github.com/gin-contrib/sessions/mongo/mongomgo" 312 | "github.com/gin-gonic/gin" 313 | "github.com/globalsign/mgo" 314 | ) 315 | 316 | func main() { 317 | r := gin.Default() 318 | session, err := mgo.Dial("localhost:27017/test") 319 | if err != nil { 320 | // handle err 321 | } 322 | 323 | c := session.DB("").C("sessions") 324 | store := mongomgo.NewStore(c, 3600, true, []byte("secret")) 325 | r.Use(sessions.Sessions("mysession", store)) 326 | 327 | r.GET("/incr", func(c *gin.Context) { 328 | session := sessions.Default(c) 329 | var count int 330 | v := session.Get("count") 331 | if v == nil { 332 | count = 0 333 | } else { 334 | count = v.(int) 335 | count++ 336 | } 337 | session.Set("count", count) 338 | session.Save() 339 | c.JSON(200, gin.H{"count": count}) 340 | }) 341 | r.Run(":8000") 342 | } 343 | ``` 344 | 345 | #### mongo-driver 346 | 347 | ```go 348 | package main 349 | 350 | import ( 351 | "context" 352 | "github.com/gin-contrib/sessions" 353 | "github.com/gin-contrib/sessions/mongo/mongodriver" 354 | "github.com/gin-gonic/gin" 355 | "go.mongodb.org/mongo-driver/mongo" 356 | "go.mongodb.org/mongo-driver/mongo/options" 357 | ) 358 | 359 | func main() { 360 | r := gin.Default() 361 | mongoOptions := options.Client().ApplyURI("mongodb://localhost:27017") 362 | client, err := mongo.NewClient(mongoOptions) 363 | if err != nil { 364 | // handle err 365 | } 366 | 367 | if err := client.Connect(context.Background()); err != nil { 368 | // handle err 369 | } 370 | 371 | c := client.Database("test").Collection("sessions") 372 | store := mongodriver.NewStore(c, 3600, true, []byte("secret")) 373 | r.Use(sessions.Sessions("mysession", store)) 374 | 375 | r.GET("/incr", func(c *gin.Context) { 376 | session := sessions.Default(c) 377 | var count int 378 | v := session.Get("count") 379 | if v == nil { 380 | count = 0 381 | } else { 382 | count = v.(int) 383 | count++ 384 | } 385 | session.Set("count", count) 386 | session.Save() 387 | c.JSON(200, gin.H{"count": count}) 388 | }) 389 | r.Run(":8000") 390 | } 391 | ``` 392 | 393 | ### memstore 394 | 395 | ```go 396 | package main 397 | 398 | import ( 399 | "github.com/gin-contrib/sessions" 400 | "github.com/gin-contrib/sessions/memstore" 401 | "github.com/gin-gonic/gin" 402 | ) 403 | 404 | func main() { 405 | r := gin.Default() 406 | store := memstore.NewStore([]byte("secret")) 407 | r.Use(sessions.Sessions("mysession", store)) 408 | 409 | r.GET("/incr", func(c *gin.Context) { 410 | session := sessions.Default(c) 411 | var count int 412 | v := session.Get("count") 413 | if v == nil { 414 | count = 0 415 | } else { 416 | count = v.(int) 417 | count++ 418 | } 419 | session.Set("count", count) 420 | session.Save() 421 | c.JSON(200, gin.H{"count": count}) 422 | }) 423 | r.Run(":8000") 424 | } 425 | ``` 426 | 427 | ### GORM 428 | 429 | ```go 430 | package main 431 | 432 | import ( 433 | "github.com/gin-contrib/sessions" 434 | gormsessions "github.com/gin-contrib/sessions/gorm" 435 | "github.com/gin-gonic/gin" 436 | "gorm.io/driver/sqlite" 437 | "gorm.io/gorm" 438 | ) 439 | 440 | func main() { 441 | db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) 442 | if err != nil { 443 | panic(err) 444 | } 445 | store := gormsessions.NewStore(db, true, []byte("secret")) 446 | 447 | r := gin.Default() 448 | r.Use(sessions.Sessions("mysession", store)) 449 | 450 | r.GET("/incr", func(c *gin.Context) { 451 | session := sessions.Default(c) 452 | var count int 453 | v := session.Get("count") 454 | if v == nil { 455 | count = 0 456 | } else { 457 | count = v.(int) 458 | count++ 459 | } 460 | session.Set("count", count) 461 | session.Save() 462 | c.JSON(200, gin.H{"count": count}) 463 | }) 464 | r.Run(":8000") 465 | } 466 | ``` 467 | 468 | ### PostgreSQL 469 | 470 | ```go 471 | package main 472 | 473 | import ( 474 | "database/sql" 475 | "github.com/gin-contrib/sessions" 476 | "github.com/gin-contrib/sessions/postgres" 477 | "github.com/gin-gonic/gin" 478 | ) 479 | 480 | func main() { 481 | r := gin.Default() 482 | db, err := sql.Open("postgres", "postgresql://username:password@localhost:5432/database") 483 | if err != nil { 484 | // handle err 485 | } 486 | 487 | store, err := postgres.NewStore(db, []byte("secret")) 488 | if err != nil { 489 | // handle err 490 | } 491 | 492 | r.Use(sessions.Sessions("mysession", store)) 493 | 494 | r.GET("/incr", func(c *gin.Context) { 495 | session := sessions.Default(c) 496 | var count int 497 | v := session.Get("count") 498 | if v == nil { 499 | count = 0 500 | } else { 501 | count = v.(int) 502 | count++ 503 | } 504 | session.Set("count", count) 505 | session.Save() 506 | c.JSON(200, gin.H{"count": count}) 507 | }) 508 | r.Run(":8000") 509 | } 510 | ``` 511 | 512 | ### Filesystem 513 | 514 | ```go 515 | package main 516 | 517 | import ( 518 | "github.com/gin-contrib/sessions" 519 | "github.com/gin-contrib/sessions/filesystem" 520 | "github.com/gin-gonic/gin" 521 | ) 522 | 523 | func main() { 524 | r := gin.Default() 525 | 526 | var sessionPath = "/tmp/" // in case of empty string, the system's default tmp folder is used 527 | 528 | store := filesystem.NewStore(sessionPath,[]byte("secret")) 529 | r.Use(sessions.Sessions("mysession", store)) 530 | 531 | r.GET("/incr", func(c *gin.Context) { 532 | session := sessions.Default(c) 533 | var count int 534 | v := session.Get("count") 535 | if v == nil { 536 | count = 0 537 | } else { 538 | count = v.(int) 539 | count++ 540 | } 541 | session.Set("count", count) 542 | session.Save() 543 | c.JSON(200, gin.H{"count": count}) 544 | }) 545 | r.Run(":8000") 546 | } 547 | ``` 548 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 3 | github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a h1:dIdcLbck6W67B5JFMewU5Dba1yKZA3MsT67i4No/zh0= 4 | github.com/antonlindstrom/pgstore v0.0.0-20220421113606-e3a6e3fed12a/go.mod h1:Sdr/tmSOLEnncCuXS5TwZRxuk7deH1WXVY8cve3eVBM= 5 | github.com/boj/redistore v1.4.1 h1:lP9ZZWqKMq2RIqexlZX1w1ODSnegL+puxGIujkU5tIw= 6 | github.com/boj/redistore v1.4.1/go.mod h1:c0Tvw6aMjslog4jHIAcNv6EtJM849YoOAhMY7JBbWpI= 7 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= 8 | github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= 9 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304 h1:f/AUyZ4PoqHhBJnhMrrNtSNYH5RvLxr5UQ0qrOZ9jkE= 10 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20240916143655-c0e34fd2f304/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= 11 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 12 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 13 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 14 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 15 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 16 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 17 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 18 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 19 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 20 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 21 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 22 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 23 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 24 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 25 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 27 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 28 | github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= 29 | github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= 30 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 31 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 32 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= 33 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 34 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 35 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 36 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 37 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 38 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 39 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 40 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 41 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 42 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 43 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 44 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 45 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 46 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 47 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 48 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 49 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 50 | github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= 51 | github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 52 | github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s= 53 | github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= 54 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 55 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 56 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 57 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 58 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 59 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 60 | github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= 61 | github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= 62 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 63 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 64 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 65 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 66 | github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 67 | github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 68 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 69 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 70 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 71 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 72 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 73 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 74 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 75 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 76 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 77 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 78 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 79 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= 80 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= 81 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 82 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 83 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 84 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 85 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 86 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 87 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 88 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 89 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 90 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 91 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 92 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 93 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 94 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 95 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 96 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= 97 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 98 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 99 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 100 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 101 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 102 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 103 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 104 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= 105 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 106 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 107 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 108 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 109 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 110 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= 111 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= 112 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 113 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 114 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 115 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 116 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 117 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 118 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 119 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 120 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 121 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 122 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 123 | github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b h1:TLCm7HR+P9HM2NXaAJaIiHerOUMedtFJeAfaYwZ8YhY= 124 | github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= 125 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 126 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 127 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 128 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 129 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 130 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 131 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 132 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 133 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 134 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 135 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 136 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 137 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 138 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 139 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 140 | github.com/laziness-coders/mongostore v0.0.14 h1:4RrtOeTsGr3pBbImtpCZT7L4LB/kXfAzpCPXds69RgA= 141 | github.com/laziness-coders/mongostore v0.0.14/go.mod h1:Rh+yJax2Vxc2QY62clIM/kRnLk+TxivgSLHOXENXPtk= 142 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 143 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 144 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 145 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 146 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 147 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 148 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 149 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 150 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 151 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 152 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 153 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 154 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 155 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 156 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 157 | github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 158 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 159 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 160 | github.com/memcachier/mc v2.0.1+incompatible h1:s8EDz0xrJLP8goitwZOoq1vA/sm0fPS4X3KAF0nyhWQ= 161 | github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= 162 | github.com/memcachier/mc/v3 v3.0.3 h1:qii+lDiPKi36O4Xg+HVKwHu6Oq+Gt17b+uEiA0Drwv4= 163 | github.com/memcachier/mc/v3 v3.0.3/go.mod h1:GzjocBahcXPxt2cmqzknrgqCOmMxiSzhVKPOe90Tpug= 164 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 165 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 166 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 167 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 168 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 169 | github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= 170 | github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= 171 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 172 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 173 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 174 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 175 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 176 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 177 | github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc= 178 | github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= 179 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 180 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 181 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 182 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 183 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 184 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 185 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 186 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 187 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 188 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 189 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 190 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 191 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 192 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 193 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 194 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 195 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 196 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 197 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 198 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 199 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 200 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 201 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 202 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 203 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 204 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 205 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 206 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 207 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 208 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 209 | github.com/wader/gormstore/v2 v2.0.3 h1:/29GWPauY8xZkpLnB8hsp+dZfP3ivA9fiDw1YVNTp6U= 210 | github.com/wader/gormstore/v2 v2.0.3/go.mod h1:sr3N3a8F1+PBc3fHoKaphFqDXLRJ9Oe6Yow0HxKFbbg= 211 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 212 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 213 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 214 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 215 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 216 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 217 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= 218 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= 219 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 220 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 221 | go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= 222 | go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= 223 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 224 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 225 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 226 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 227 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 228 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 229 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 230 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 231 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 232 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 233 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 234 | golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= 235 | golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= 236 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 237 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 238 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 239 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 240 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 241 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 242 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 243 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 244 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 245 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 246 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 247 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 248 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 249 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 250 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 251 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 252 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 253 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 254 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 255 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 256 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 257 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 258 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 259 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 260 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 261 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 262 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 263 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 264 | golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= 265 | golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 266 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 267 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 268 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 269 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 270 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 271 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 272 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 273 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 274 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 275 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 276 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 277 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 278 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 279 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 280 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 281 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 282 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 283 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 284 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 285 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 286 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 287 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 288 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 289 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 290 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 291 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 292 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 293 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 294 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 295 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 296 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 297 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 298 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 299 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 300 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 301 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 302 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 303 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 304 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 305 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 306 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 307 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 308 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 309 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 310 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 311 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 312 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 313 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 314 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 315 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 316 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 317 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 318 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 319 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 320 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 321 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 322 | gorm.io/driver/mysql v1.4.0 h1:P+gpa0QGyNma39khn1vZMS/eXEJxTwHz4Q26NR4C8fw= 323 | gorm.io/driver/mysql v1.4.0/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= 324 | gorm.io/driver/postgres v1.4.1 h1:DutsKq2LK2Ag65q/+VygWth0/L4GAVOp+sCtg6WzZjs= 325 | gorm.io/driver/postgres v1.4.1/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= 326 | gorm.io/driver/sqlite v1.4.1/go.mod h1:AKZZCAoFfOWHF7Nd685Iq8Uywc0i9sWJlzpoE/INzsw= 327 | gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= 328 | gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= 329 | gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 330 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 331 | gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 332 | gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 333 | gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= 334 | gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= 335 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 336 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 337 | --------------------------------------------------------------------------------