package application import ( "context" "math" "net/http" "net/url" "os" "path" "strings" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" "goauthentik.io/internal/outpost/proxyv2/codecs" "goauthentik.io/internal/outpost/proxyv2/constants" "goauthentik.io/internal/outpost/proxyv2/filesystemstore" "goauthentik.io/internal/outpost/proxyv2/postgresstore" "goauthentik.io/internal/outpost/proxyv2/types" api "goauthentik.io/packages/client-go" ) const PostgresKeyPrefix = "authentik_proxy_session_" func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) (sessions.Store, error) { maxAge := 0 if p.AccessTokenValidity.IsSet() { t := p.AccessTokenValidity.Get() // Add one to the validity to ensure we don't have a session with indefinite length maxAge = int(*t) + 1 } sessionBackend := a.srv.SessionBackend() switch sessionBackend { case "postgres": // New PostgreSQL store ps, err := postgresstore.NewPostgresStore(a.log) if err != nil { return nil, err } ps.KeyPrefix(PostgresKeyPrefix) ps.Options(sessions.Options{ HttpOnly: true, Secure: strings.ToLower(externalHost.Scheme) == "https", Domain: *p.CookieDomain, SameSite: http.SameSiteLaxMode, MaxAge: maxAge, Path: "/", }) return ps, nil case "filesystem": dir := os.TempDir() cs, err := filesystemstore.GetPersistentStore(dir) if err != nil { return nil, err } cs.Codecs = codecs.CodecsFromPairs(maxAge, []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) cs.Options.HttpOnly = true cs.Options.Secure = strings.ToLower(externalHost.Scheme) == "https" cs.Options.Domain = *p.CookieDomain cs.Options.SameSite = http.SameSiteLaxMode cs.Options.MaxAge = maxAge cs.Options.Path = "/" return cs, nil default: a.log.WithField("backend", sessionBackend).Panic("unknown session backend type") return nil, nil } } func (a *Application) SessionName() string { return a.sessionName } func (a *Application) getAllCodecs() []securecookie.Codec { apps := a.srv.Apps() cs := []securecookie.Codec{} for _, app := range apps { cs = append(cs, codecs.CodecsFromPairs(0, []byte(*app.proxyConfig.CookieSecret))...) } return cs } func (a *Application) Logout(ctx context.Context, filter func(c types.Claims) bool) error { if _, ok := a.sessions.(*filesystemstore.Store); 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( a.SessionName(), string(data), &s.Values, a.getAllCodecs()..., ) if err != nil { a.log.WithError(err).Trace("failed to decode session") continue } rc, ok := s.Values[constants.SessionClaims] if !ok || rc == nil { continue } claims := s.Values[constants.SessionClaims].(types.Claims) if filter(claims) { 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 ps, ok := a.sessions.(*postgresstore.PostgresStore); ok { err := ps.LogoutSessions(ctx, func(c types.Claims) bool { return filter(types.Claims(c)) }) if err != nil { a.log.WithError(err).Warning("failed to logout sessions from PostgreSQL") return err } } return nil }