Files
photoprism/internal/entity/auth_session_delete.go
2025-10-21 14:42:05 +02:00

142 lines
3.6 KiB
Go

package entity
import (
"fmt"
"github.com/dustin/go-humanize/english"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/log/status"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/time/unix"
)
// DeleteSession permanently deletes the session and any sessions that were derived from it.
func DeleteSession(s *Session) error {
if s == nil {
return nil
} else if !rnd.IsSessionID(s.ID) {
return fmt.Errorf("invalid session id")
}
// Delete any other sessions that were authenticated with the specified session.
if n := DeleteChildSessions(s); n > 0 {
event.AuditInfo([]string{s.IP(), "session %s", "deleted %s"}, s.RefID, english.Plural(n, "child session", "child sessions"))
}
// Delete session from cache.
DeleteFromSessionCache(s.ID)
if s.PreviewToken != "" {
PreviewToken.Set(s.PreviewToken, s.ID)
}
if s.DownloadToken != "" {
DownloadToken.Set(s.DownloadToken, s.ID)
}
return UnscopedDb().Delete(s).Error
}
// DeleteChildSessions deletes sessions that authenticated via the provided parent session ID.
func DeleteChildSessions(s *Session) (deleted int) {
if s == nil {
return 0
} else if !rnd.IsSessionID(s.ID) || s.GetMethod().Is(authn.MethodSession) {
return 0
}
found := Sessions{}
if err := Db().Where("auth_id = ? AND auth_method = ?", s.ID, authn.MethodSession.String()).Find(&found).Error; err != nil {
event.AuditErr([]string{"failed to find child sessions", status.Error(err)})
return deleted
}
for _, sess := range found {
if err := sess.Delete(); err != nil {
event.AuditErr([]string{sess.IP(), "session %s", "failed to delete child session %s", status.Error(err)}, s.RefID, sess.RefID)
} else {
deleted++
}
}
return deleted
}
// DeleteClientSessions deletes sessions for the client beyond the configured retention limit.
func DeleteClientSessions(client *Client, authMethod authn.MethodType, limit int64) (deleted int) {
if limit < 0 {
return 0
} else if client == nil {
return 0
}
q := Db()
if client.HasUID() {
q = q.Where("client_uid = ?", client.GetUID())
} else if client.HasName() {
q = q.Where("client_name = ?", client.Name())
} else {
return 0
}
if client.HasUser() {
q = q.Where("user_uid = ?", client.UserUID)
}
if !authMethod.IsUndefined() {
q = q.Where("auth_method = ?", authMethod.String())
}
q = q.Order("created_at DESC").Limit(1000000000).Offset(limit)
found := Sessions{}
if err := q.Find(&found).Error; err != nil {
event.AuditErr([]string{"failed to fetch client sessions", status.Error(err)})
return deleted
}
for _, sess := range found {
if err := sess.Delete(); err != nil {
event.AuditErr([]string{sess.IP(), "session %s", "failed to delete", status.Error(err)}, sess.RefID)
} else {
deleted++
}
}
return deleted
}
// DeleteExpiredSessions removes sessions whose expiration timestamp has passed.
func DeleteExpiredSessions() (deleted int) {
found := Sessions{}
if err := Db().Where("sess_expires > 0 AND sess_expires < ?", unix.Now()).Find(&found).Error; err != nil {
event.AuditErr([]string{"failed to fetch expired sessions", status.Error(err)})
return deleted
}
for _, sess := range found {
if err := sess.Delete(); err != nil {
event.AuditErr([]string{sess.IP(), "session %s", "failed to delete", status.Error(err)}, sess.RefID)
} else {
deleted++
}
}
return deleted
}
// DeleteFromSessionCache deletes a session from the cache.
func DeleteFromSessionCache(id string) {
if id == "" {
return
}
sessionCache.Delete(id)
}