Config: Add option to change default session cache duration #808 #3943

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-04-17 08:26:35 +02:00
parent e25626cd0c
commit 0134c68d2c
9 changed files with 60 additions and 21 deletions

View File

@@ -211,6 +211,9 @@ func (c *Config) Propagate() {
places.UserAgent = c.UserAgent() places.UserAgent = c.UserAgent()
entity.GeoApi = c.GeoApi() entity.GeoApi = c.GeoApi()
// Set session cache duration.
entity.SessionCacheDuration = c.SessionCacheDuration()
// Set minimum password length. // Set minimum password length.
entity.PasswordLength = c.PasswordLength() entity.PasswordLength = c.PasswordLength()

View File

@@ -2,6 +2,7 @@ package config
import ( import (
"regexp" "regexp"
"time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@@ -160,6 +161,24 @@ func (c *Config) SessionTimeout() int64 {
return c.options.SessionTimeout return c.options.SessionTimeout
} }
// SessionCache returns the default session cache duration in seconds.
func (c *Config) SessionCache() int64 {
if c.options.SessionCache == 0 {
return DefaultSessionCache
} else if c.options.SessionCache < 60 {
return 60
} else if c.options.SessionCache > 3600 {
return 3600
}
return c.options.SessionCache
}
// SessionCacheDuration returns the default session cache duration.
func (c *Config) SessionCacheDuration() time.Duration {
return time.Duration(c.SessionCache()) * time.Second
}
// DownloadToken returns the DOWNLOAD api token (you can optionally use a static value for permanent caching). // DownloadToken returns the DOWNLOAD api token (you can optionally use a static value for permanent caching).
func (c *Config) DownloadToken() string { func (c *Config) DownloadToken() string {
if c.Public() { if c.Public() {

View File

@@ -2,6 +2,7 @@ package config
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -81,7 +82,7 @@ func TestLoginUri(t *testing.T) {
assert.Equal(t, "/library/login", c.LoginUri()) assert.Equal(t, "/library/login", c.LoginUri())
} }
func TestSessMaxAge(t *testing.T) { func TestSessionMaxAge(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())
assert.Equal(t, DefaultSessionMaxAge, c.SessionMaxAge()) assert.Equal(t, DefaultSessionMaxAge, c.SessionMaxAge())
c.options.SessionMaxAge = -1 c.options.SessionMaxAge = -1
@@ -90,7 +91,7 @@ func TestSessMaxAge(t *testing.T) {
assert.Equal(t, DefaultSessionMaxAge, c.SessionMaxAge()) assert.Equal(t, DefaultSessionMaxAge, c.SessionMaxAge())
} }
func TestSessTimeout(t *testing.T) { func TestSessionTimeout(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())
assert.Equal(t, DefaultSessionTimeout, c.SessionTimeout()) assert.Equal(t, DefaultSessionTimeout, c.SessionTimeout())
c.options.SessionTimeout = -1 c.options.SessionTimeout = -1
@@ -99,6 +100,18 @@ func TestSessTimeout(t *testing.T) {
assert.Equal(t, DefaultSessionTimeout, c.SessionTimeout()) assert.Equal(t, DefaultSessionTimeout, c.SessionTimeout())
} }
func TestSessionCache(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, DefaultSessionCache, c.SessionCache())
c.options.SessionCache = -1
assert.Equal(t, int64(60), c.SessionCache())
c.options.SessionCache = 100000
assert.Equal(t, int64(3600), c.SessionCache())
c.options.SessionCache = 0
assert.Equal(t, DefaultSessionCache, c.SessionCache())
assert.Equal(t, time.Duration(DefaultSessionCache)*time.Second, c.SessionCacheDuration())
}
func TestUtils_CheckPassword(t *testing.T) { func TestUtils_CheckPassword(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())

View File

@@ -2,6 +2,8 @@ package config
import ( import (
"time" "time"
"github.com/photoprism/photoprism/pkg/unix"
) )
// ApiUri defines the standard path for handling REST requests. // ApiUri defines the standard path for handling REST requests.
@@ -43,20 +45,14 @@ const DefaultResolutionLimit = 150 // 150 Megapixels
// serialName defines the name of the unique storage serial. // serialName defines the name of the unique storage serial.
const serialName = "serial" const serialName = "serial"
// UnixHour defines one hour in UnixTime.
const UnixHour int64 = 3600
// UnixDay defines one day in UnixTime.
const UnixDay = UnixHour * 24
// UnixWeek defines one week in UnixTime.
const UnixWeek = UnixDay * 7
// DefaultSessionMaxAge defines the standard session expiration time in seconds. // DefaultSessionMaxAge defines the standard session expiration time in seconds.
const DefaultSessionMaxAge = UnixWeek * 2 const DefaultSessionMaxAge = unix.Week * 2
// DefaultSessionTimeout defines the standard session idle time in seconds. // DefaultSessionTimeout defines the standard session idle time in seconds.
const DefaultSessionTimeout = UnixWeek const DefaultSessionTimeout = unix.Week
// DefaultSessionCache defines the default session cache duration in seconds.
const DefaultSessionCache = unix.Minute * 15
// Product feature tags used to automatically generate documentation. // Product feature tags used to automatically generate documentation.
const ( const (

View File

@@ -45,15 +45,21 @@ var Flags = CliFlags{
Flag: cli.Int64Flag{ Flag: cli.Int64Flag{
Name: "session-maxage", Name: "session-maxage",
Value: DefaultSessionMaxAge, Value: DefaultSessionMaxAge,
Usage: "standard session expiration time in `SECONDS`, doubled for accounts with 2FA (-1 to disable)", Usage: "session expiration time in `SECONDS`, doubled for accounts with 2FA (-1 to disable)",
EnvVar: EnvVar("SESSION_MAXAGE"), EnvVar: EnvVar("SESSION_MAXAGE"),
}}, { }}, {
Flag: cli.Int64Flag{ Flag: cli.Int64Flag{
Name: "session-timeout", Name: "session-timeout",
Value: DefaultSessionTimeout, Value: DefaultSessionTimeout,
Usage: "standard session idle time in `SECONDS`, doubled for accounts with 2FA (-1 to disable)", Usage: "session idle time in `SECONDS`, doubled for accounts with 2FA (-1 to disable)",
EnvVar: EnvVar("SESSION_TIMEOUT"), EnvVar: EnvVar("SESSION_TIMEOUT"),
}}, { }}, {
Flag: cli.Int64Flag{
Name: "session-cache",
Value: DefaultSessionCache,
Usage: "session cache duration in `SECONDS` (60-3600)",
EnvVar: EnvVar("SESSION_CACHE"),
}}, {
Flag: cli.StringFlag{ Flag: cli.StringFlag{
Name: "log-level, l", Name: "log-level, l",
Usage: "log message verbosity `LEVEL` (trace, debug, info, warning, error, fatal, panic)", Usage: "log message verbosity `LEVEL` (trace, debug, info, warning, error, fatal, panic)",

View File

@@ -33,6 +33,7 @@ type Options struct {
LoginUri string `yaml:"LoginUri" json:"-" flag:"login-uri"` LoginUri string `yaml:"LoginUri" json:"-" flag:"login-uri"`
SessionMaxAge int64 `yaml:"SessionMaxAge" json:"-" flag:"session-maxage"` SessionMaxAge int64 `yaml:"SessionMaxAge" json:"-" flag:"session-maxage"`
SessionTimeout int64 `yaml:"SessionTimeout" json:"-" flag:"session-timeout"` SessionTimeout int64 `yaml:"SessionTimeout" json:"-" flag:"session-timeout"`
SessionCache int64 `yaml:"SessionCache" json:"-" flag:"session-cache"`
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"` LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
Prod bool `yaml:"Prod" json:"Prod" flag:"prod"` Prod bool `yaml:"Prod" json:"Prod" flag:"prod"`
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"` Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`

View File

@@ -30,6 +30,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"login-uri", c.LoginUri()}, {"login-uri", c.LoginUri()},
{"session-maxage", fmt.Sprintf("%d", c.SessionMaxAge())}, {"session-maxage", fmt.Sprintf("%d", c.SessionMaxAge())},
{"session-timeout", fmt.Sprintf("%d", c.SessionTimeout())}, {"session-timeout", fmt.Sprintf("%d", c.SessionTimeout())},
{"session-cache", fmt.Sprintf("%d", c.SessionCache())},
// Logging. // Logging.
{"log-level", c.LogLevel().String()}, {"log-level", c.LogLevel().String()},

View File

@@ -176,7 +176,7 @@ func (m *Session) CacheDuration(d time.Duration) {
// Cache caches the session with the default expiration duration. // Cache caches the session with the default expiration duration.
func (m *Session) Cache() { func (m *Session) Cache() {
m.CacheDuration(sessionCacheExpiration) m.CacheDuration(SessionCacheDuration)
} }
// ClearCache deletes the session from the cache. // ClearCache deletes the session from the cache.

View File

@@ -11,9 +11,9 @@ import (
"github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/rnd"
) )
// Create a new session cache with an expiration time of 15 minutes. // SessionCacheDuration specifies how long sessions are cached.
var sessionCacheExpiration = 15 * time.Minute var SessionCacheDuration = 15 * time.Minute
var sessionCache = gc.New(sessionCacheExpiration, 5*time.Minute) var sessionCache = gc.New(SessionCacheDuration, time.Minute)
// FindSessionByAuthToken finds a session based on the auth token string or returns nil if it does not exist. // FindSessionByAuthToken finds a session based on the auth token string or returns nil if it does not exist.
func FindSessionByAuthToken(token string) (*Session, error) { func FindSessionByAuthToken(token string) (*Session, error) {
@@ -46,7 +46,7 @@ func FindSession(id string) (*Session, error) {
} else if !found.Expired() { } else if !found.Expired() {
// Set session activity timestamp and update the last_active column in the sessions table. // Set session activity timestamp and update the last_active column in the sessions table.
found.UpdateLastActive(true) found.UpdateLastActive(true)
CacheSession(found, sessionCacheExpiration) CacheSession(found, SessionCacheDuration)
return found, nil return found, nil
} else if err := found.Delete(); err != nil { } else if err := found.Delete(); err != nil {
event.AuditErr([]string{found.IP(), "session %s", "failed to delete after expiration", "%s"}, found.RefID, err) event.AuditErr([]string{found.IP(), "session %s", "failed to delete after expiration", "%s"}, found.RefID, err)
@@ -69,7 +69,7 @@ func CacheSession(s *Session, d time.Duration) {
} }
if d == 0 { if d == 0 {
d = sessionCacheExpiration d = SessionCacheDuration
} }
if s.PreviewToken != "" { if s.PreviewToken != "" {