mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
This is based on https://code.forgejo.org/go-chi/session/pulls/80. The remainder of this message is largely copied from there: For interoperability with reverse proxies and CDNs, setting a session cookie for no good reason (login is a good reason) is a PITA, because it makes caching of content for anonymous (not logged-in) users very hard, requiring all kinds of special casing and error prone workarounds. In particular in an age of exploitative AI bot crawling, being able to serve content for anonymous users from a fast, efficient page cache is an important option. This patch lays a foundation by using an option added to go-chi/session to not create session cookies always, but rather only when the respective session is non-empty. Test cases are included there and omitted here.
202 lines
4.9 KiB
Go
202 lines
4.9 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package session
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
"forgejo.org/modules/json"
|
|
"forgejo.org/modules/log"
|
|
|
|
"code.forgejo.org/go-chi/session"
|
|
memcache "code.forgejo.org/go-chi/session/memcache"
|
|
mysql "code.forgejo.org/go-chi/session/mysql"
|
|
postgres "code.forgejo.org/go-chi/session/postgres"
|
|
)
|
|
|
|
// VirtualSessionProvider represents a shadowed session provider implementation.
|
|
type VirtualSessionProvider struct {
|
|
lock sync.RWMutex
|
|
provider session.Provider
|
|
}
|
|
|
|
// Init initializes the cookie session provider with given root path.
|
|
func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
|
|
var opts session.Options
|
|
if err := json.Unmarshal([]byte(config), &opts); err != nil {
|
|
return err
|
|
}
|
|
// Note that these options are unprepared so we can't just use NewManager here.
|
|
// Nor can we access the provider map in session.
|
|
// So we will just have to do this by hand.
|
|
// This is only slightly more wrong than modules/setting/session.go:23
|
|
switch opts.Provider {
|
|
case "memory":
|
|
o.provider = &session.MemProvider{}
|
|
case "couchbase":
|
|
log.Warn("Couchbase as session provider is no longer supported, falling back to file as session provider")
|
|
fallthrough
|
|
case "file":
|
|
o.provider = &session.FileProvider{}
|
|
case "redis":
|
|
o.provider = &RedisProvider{}
|
|
case "db":
|
|
o.provider = &DBProvider{}
|
|
case "mysql":
|
|
o.provider = &mysql.MysqlProvider{}
|
|
case "postgres":
|
|
o.provider = &postgres.PostgresProvider{}
|
|
case "memcache":
|
|
o.provider = &memcache.MemcacheProvider{}
|
|
default:
|
|
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
|
|
}
|
|
return o.provider.Init(gclifetime, opts.ProviderConfig)
|
|
}
|
|
|
|
// Read returns raw session store by session ID.
|
|
func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
|
|
o.lock.RLock()
|
|
defer o.lock.RUnlock()
|
|
if o.provider.Exist(sid) {
|
|
return o.provider.Read(sid)
|
|
}
|
|
kv := make(map[any]any)
|
|
return NewVirtualStore(o, sid, kv), nil
|
|
}
|
|
|
|
// Exist returns true if session with given ID exists.
|
|
func (o *VirtualSessionProvider) Exist(sid string) bool {
|
|
return true
|
|
}
|
|
|
|
// Destroy deletes a session by session ID.
|
|
func (o *VirtualSessionProvider) Destroy(sid string) error {
|
|
o.lock.Lock()
|
|
defer o.lock.Unlock()
|
|
return o.provider.Destroy(sid)
|
|
}
|
|
|
|
// Regenerate regenerates a session store from old session ID to new one.
|
|
func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
|
o.lock.Lock()
|
|
defer o.lock.Unlock()
|
|
return o.provider.Regenerate(oldsid, sid)
|
|
}
|
|
|
|
// Count counts and returns number of sessions.
|
|
func (o *VirtualSessionProvider) Count() int {
|
|
o.lock.RLock()
|
|
defer o.lock.RUnlock()
|
|
return o.provider.Count()
|
|
}
|
|
|
|
// GC calls GC to clean expired sessions.
|
|
func (o *VirtualSessionProvider) GC() {
|
|
o.provider.GC()
|
|
}
|
|
|
|
func init() {
|
|
session.Register("VirtualSession", &VirtualSessionProvider{})
|
|
}
|
|
|
|
// VirtualStore represents a virtual session store implementation.
|
|
type VirtualStore struct {
|
|
p *VirtualSessionProvider
|
|
sid string
|
|
lock sync.RWMutex
|
|
data map[any]any
|
|
released bool
|
|
}
|
|
|
|
// NewVirtualStore creates and returns a virtual session store.
|
|
func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[any]any) *VirtualStore {
|
|
return &VirtualStore{
|
|
p: p,
|
|
sid: sid,
|
|
data: kv,
|
|
}
|
|
}
|
|
|
|
// Set sets value to given key in session.
|
|
func (s *VirtualStore) Set(key, val any) error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
s.data[key] = val
|
|
return nil
|
|
}
|
|
|
|
// Get gets value by given key in session.
|
|
func (s *VirtualStore) Get(key any) any {
|
|
s.lock.RLock()
|
|
defer s.lock.RUnlock()
|
|
|
|
return s.data[key]
|
|
}
|
|
|
|
// Delete delete a key from session.
|
|
func (s *VirtualStore) Delete(key any) error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
delete(s.data, key)
|
|
return nil
|
|
}
|
|
|
|
// ID returns current session ID.
|
|
func (s *VirtualStore) ID() string {
|
|
return s.sid
|
|
}
|
|
|
|
// Release releases resource and save data to provider.
|
|
func (s *VirtualStore) Release() error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
// Now need to lock the provider
|
|
s.p.lock.Lock()
|
|
defer s.p.lock.Unlock()
|
|
if len(s.data) > 0 {
|
|
// Now ensure that we don't exist!
|
|
realProvider := s.p.provider
|
|
|
|
if !s.released && realProvider.Exist(s.sid) {
|
|
// This is an error!
|
|
return fmt.Errorf("new sid '%s' already exists", s.sid)
|
|
}
|
|
realStore, err := realProvider.Read(s.sid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := realStore.Flush(); err != nil {
|
|
return err
|
|
}
|
|
for key, value := range s.data {
|
|
if err := realStore.Set(key, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = realStore.Release()
|
|
if err == nil {
|
|
s.released = true
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Flush deletes all session data.
|
|
func (s *VirtualStore) Flush() error {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
s.data = make(map[any]any)
|
|
return nil
|
|
}
|
|
|
|
// True if no keys have been set
|
|
func (s *VirtualStore) Empty() bool {
|
|
return len(s.data) == 0
|
|
}
|