This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/internal/outpost/proxyv2/application/session.go

148 lines
4.1 KiB
Go

package application
import (
"fmt"
"math"
"net/url"
"os"
"path"
"strconv"
"strings"
"github.com/garyburd/redigo/redis"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
"goauthentik.io/api/v3"
"goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/proxyv2/constants"
"gopkg.in/boj/redistore.v1"
)
const RedisKeyPrefix = "authentik_proxy_session_"
func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) sessions.Store {
var store sessions.Store
if config.Get().Redis.Host != "" {
rs, err := redistore.NewRediStoreWithDB(10, "tcp", fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), config.Get().Redis.Password, strconv.Itoa(config.Get().Redis.DB), []byte(*p.CookieSecret))
if err != nil {
panic(err)
}
rs.SetMaxLength(math.MaxInt)
rs.SetKeyPrefix(RedisKeyPrefix)
if p.TokenValidity.IsSet() {
t := p.TokenValidity.Get()
// Add one to the validity to ensure we don't have a session with indefinite length
rs.SetMaxAge(int(*t) + 1)
} else {
rs.SetMaxAge(0)
}
rs.Options.Domain = *p.CookieDomain
a.log.Trace("using redis session backend")
store = rs
} else {
dir := os.TempDir()
cs := sessions.NewFilesystemStore(dir, []byte(*p.CookieSecret))
// https://github.com/markbates/goth/commit/7276be0fdf719ddff753f3574ef0f967e4a5a5f7
// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with:
// securecookie: the value is too long
// when using OpenID Connect , since this can contain a large amount of extra information in the id_token
// Note, when using the FilesystemStore only the session.ID is written to a browser cookie, so this is explicit for the storage on disk
cs.MaxLength(math.MaxInt)
if p.TokenValidity.IsSet() {
t := p.TokenValidity.Get()
// Add one to the validity to ensure we don't have a session with indefinite length
cs.MaxAge(int(*t) + 1)
} else {
cs.MaxAge(0)
}
cs.Options.Domain = *p.CookieDomain
a.log.WithField("dir", dir).Trace("using filesystem session backend")
store = cs
}
return store
}
func (a *Application) Logout(sub string) error {
if fs, ok := a.sessions.(*sessions.FilesystemStore); ok {
files, err := os.ReadDir(os.TempDir())
if err != nil {
return err
}
for _, file := range files {
s := sessions.Session{}
if !strings.HasPrefix(file.Name(), "session_") {
continue
}
fullPath := path.Join(os.TempDir(), file.Name())
data, err := os.ReadFile(fullPath)
if err != nil {
a.log.WithError(err).Warning("failed to read file")
continue
}
err = securecookie.DecodeMulti(
constants.SessionName, string(data),
&s.Values, fs.Codecs...,
)
if err != nil {
a.log.WithError(err).Trace("failed to decode session")
continue
}
claims := s.Values[constants.SessionClaims].(Claims)
if claims.Sub == sub {
a.log.WithField("path", fullPath).Trace("deleting session")
err := os.Remove(fullPath)
if err != nil {
a.log.WithError(err).Warning("failed to delete session")
continue
}
}
}
}
if rs, ok := a.sessions.(*redistore.RediStore); ok {
pool := rs.Pool.Get()
defer pool.Close()
rep, err := pool.Do("KEYS", fmt.Sprintf("%s*", RedisKeyPrefix))
if err != nil {
return err
}
keys, err := redis.Strings(rep, err)
if err != nil {
return err
}
ser := redistore.GobSerializer{}
for _, key := range keys {
v, err := pool.Do("GET", key)
if err != nil {
a.log.WithError(err).Warning("failed to get value")
continue
}
b, err := redis.Bytes(v, err)
if err != nil {
a.log.WithError(err).Warning("failed to load value")
continue
}
s := sessions.Session{}
err = ser.Deserialize(b, &s)
if err != nil {
a.log.WithError(err).Warning("failed to deserialize")
continue
}
c := s.Values[constants.SessionClaims]
if c == nil {
continue
}
claims := c.(Claims)
if claims.Sub == sub {
a.log.WithField("key", key).Trace("deleting session")
_, err := pool.Do("DEL", key)
if err != nil {
a.log.WithError(err).Warning("failed to delete key")
continue
}
}
}
}
return nil
}