Flush al sessions in logout all

This commit is contained in:
Felipe Fey 2025-06-09 11:14:21 -03:00
parent 5a1d721a60
commit c454e8d4d9
No known key found for this signature in database
GPG Key ID: E0AFBAF11D95CBE3
9 changed files with 124 additions and 6 deletions

View File

@ -564,6 +564,10 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques
return p.sessionStore.Clear(rw, req)
}
func (p *OAuthProxy) ClearAllSessions(req *http.Request, session *sessionsapi.SessionState) error {
return p.sessionStore.ClearAll(req, session)
}
// LoadCookiedSession reads the user's authentication details from the request
func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.SessionState, error) {
return p.sessionStore.Load(req)
@ -774,7 +778,14 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request, signOutA
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
return
}
session, err := p.getAuthenticatedSession(rw, req)
if err != nil {
logger.Errorf("Error clearing all sessions cookie: %v", err)
}
err = p.ClearSessionCookie(rw, req)
if signOutAllSessions {
err = p.ClearAllSessions(req, session)
}
if err != nil {
logger.Errorf("Error clearing session cookie: %v", err)
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())

View File

@ -12,6 +12,7 @@ type SessionStore interface {
Save(rw http.ResponseWriter, req *http.Request, s *SessionState) error
Load(req *http.Request) (*SessionState, error)
Clear(rw http.ResponseWriter, req *http.Request) error
ClearAll(req *http.Request, session *SessionState) error
VerifyConnection(ctx context.Context) error
}

View File

@ -96,6 +96,14 @@ func GenerateRandomASCIIString(length int) (string, error) {
return string(b), nil
}
// Encrypts a string with a secret using HMAC-SHA256 and returns a base64-encoded string.
func EncryptStringWithSecret(input, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(input))
sum := mac.Sum(nil)
return base64.RawURLEncoding.EncodeToString(sum)
}
func GenerateCodeChallenge(method, codeVerifier string) (string, error) {
switch method {
case CodeChallengeMethodPlain:

View File

@ -33,6 +33,11 @@ type SessionStore struct {
Minimal bool
}
// ClearAll implements sessions.SessionStore.
func (s *SessionStore) ClearAll(req *http.Request, session *sessions.SessionState) error {
panic("unimplemented")
}
// Save takes a sessions.SessionState and stores the information from it
// within Cookies set on the HTTP response writer
func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessions.SessionState) error {

View File

@ -13,7 +13,9 @@ import (
type Store interface {
Save(context.Context, string, []byte, time.Duration) error
Load(context.Context, string) ([]byte, error)
LoadList(ctx context.Context, key string) ([]string, error)
Clear(context.Context, string) error
Lock(key string) sessions.Lock
RPush(context.Context, string, string, time.Duration) error
VerifyConnection(context.Context) error
}

View File

@ -8,6 +8,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
)
// Manager wraps a Store and handles the implementation details of the
@ -42,9 +43,14 @@ func (m *Manager) Save(rw http.ResponseWriter, req *http.Request, s *sessions.Se
}
}
err = tckt.saveSession(s, func(key string, val []byte, exp time.Duration) error {
return m.Store.Save(req.Context(), key, val, exp)
})
err = tckt.saveSession(
s,
func(key string, val []byte, exp time.Duration, s *sessions.SessionState) error {
return m.Store.Save(req.Context(), key, val, exp)
},
func(key string, val string, exp time.Duration) error {
return m.Store.RPush(req.Context(), key, val, exp)
})
if err != nil {
return err
}
@ -68,6 +74,30 @@ func (m *Manager) Load(req *http.Request) (*sessions.SessionState, error) {
)
}
// ClearAll implements sessions.SessionStore.
func (m *Manager) ClearAll(req *http.Request, session *sessions.SessionState) error {
ticket, _ := decodeTicketFromRequest(req, m.Options)
sessionKey := encryption.EncryptStringWithSecret(session.User+session.Email, ticket.options.Secret)
keys, err := m.Store.LoadList(req.Context(), sessionKey)
if err != nil {
return fmt.Errorf("error decoding ticket to clear session: %v", err)
}
for _, key := range keys {
err = m.Store.Clear(req.Context(), key)
if err != nil {
return fmt.Errorf("error clearing session for key: %v", err)
}
}
err = m.Store.Clear(req.Context(), sessionKey)
if err != nil {
return fmt.Errorf("error clearing sessions keys: %v", err)
}
return err
}
// Clear clears any saved session information for a given ticket cookie.
// Then it clears all session data for that ticket in the Store.
func (m *Manager) Clear(rw http.ResponseWriter, req *http.Request) error {

View File

@ -20,7 +20,11 @@ import (
// saveFunc performs a persistent store's save functionality using
// a key string, value []byte & (optional) expiration time.Duration
type saveFunc func(string, []byte, time.Duration) error
type saveFunc func(string, []byte, time.Duration, *sessions.SessionState) error
// saveUserStateFunc performs a persistent store's save functionality using
// a key string, value []byte & (optional) expiration time.Duration
type saverUserMapSessionFunc func(string, string, time.Duration) error
// loadFunc performs a load from a persistent store using a
// string key and returning the stored value as []byte
@ -157,7 +161,7 @@ func decodeTicketFromRequest(req *http.Request, cookieOpts *options.Cookie) (*ti
// saveSession encodes the SessionState with the ticket's secret and persists
// it to disk via the passed saveFunc.
func (t *ticket) saveSession(s *sessions.SessionState, saver saveFunc) error {
func (t *ticket) saveSession(s *sessions.SessionState, saver saveFunc, saverUserMapSession saverUserMapSessionFunc) error {
c, err := t.makeCipher()
if err != nil {
return err
@ -166,7 +170,10 @@ func (t *ticket) saveSession(s *sessions.SessionState, saver saveFunc) error {
if err != nil {
return fmt.Errorf("failed to encode the session state with the ticket: %v", err)
}
return saver(t.id, ciphertext, t.options.Expire)
encodedUserState := encryption.EncryptStringWithSecret(s.User+s.Email, t.options.Secret)
saverUserMapSession(encodedUserState, t.id, 2*time.Hour)
return saver(t.id, ciphertext, t.options.Expire, s)
}
// loadSession loads a session from the disk store via the passed loadFunc

View File

@ -12,6 +12,9 @@ import (
type Client interface {
Get(ctx context.Context, key string) ([]byte, error)
Lock(key string) sessions.Lock
Expire(ctx context.Context, key string, expiration time.Duration) error
RPush(ctx context.Context, key string, value string) error
LRange(ctx context.Context, key string) ([]string, error)
Set(ctx context.Context, key string, value []byte, expiration time.Duration) error
Del(ctx context.Context, key string) error
Ping(ctx context.Context) error
@ -29,6 +32,18 @@ func newClient(c *redis.Client) Client {
}
}
func (c *client) Expire(ctx context.Context, key string, expiration time.Duration) error {
return c.Client.Expire(ctx, key, expiration).Err()
}
func (c *client) LRange(ctx context.Context, key string) ([]string, error) {
return c.Client.LRange(ctx, key, 0, -1).Result()
}
func (c *client) RPush(ctx context.Context, key string, value string) error {
return c.Client.RPush(ctx, key, value).Err()
}
func (c *client) Get(ctx context.Context, key string) ([]byte, error) {
return c.Client.Get(ctx, key).Bytes()
}
@ -61,6 +76,12 @@ func newClusterClient(c *redis.ClusterClient) Client {
}
}
// Expire implements Client.
// Subtle: this method shadows the method (*ClusterClient).Expire of clusterClient.ClusterClient.
func (c *clusterClient) Expire(ctx context.Context, key string, expiration time.Duration) error {
return c.ClusterClient.Expire(ctx, key, expiration).Err()
}
func (c *clusterClient) Get(ctx context.Context, key string) ([]byte, error) {
return c.ClusterClient.Get(ctx, key).Bytes()
}
@ -69,6 +90,14 @@ func (c *clusterClient) Set(ctx context.Context, key string, value []byte, expir
return c.ClusterClient.Set(ctx, key, value, expiration).Err()
}
func (c *clusterClient) RPush(ctx context.Context, key string, value string) error {
return c.ClusterClient.RPush(ctx, key, value).Err()
}
func (c *clusterClient) LRange(ctx context.Context, key string) ([]string, error) {
return c.ClusterClient.LRange(ctx, key, 0, -1).Result()
}
func (c *clusterClient) Del(ctx context.Context, key string) error {
return c.ClusterClient.Del(ctx, key).Err()
}

View File

@ -45,6 +45,31 @@ func (store *SessionStore) Save(ctx context.Context, key string, value []byte, e
return nil
}
// Save takes a sessions.SessionState and stores the information from it
// to redis, and adds a new persistence cookie on the HTTP response writer
func (store *SessionStore) RPush(ctx context.Context, key string, value string, exp time.Duration) error {
err := store.Client.RPush(ctx, key, value)
if err != nil {
return fmt.Errorf("error appending redis session: %v", err)
}
if exp > 0 {
if err := store.Client.Expire(ctx, key, exp); err != nil {
return fmt.Errorf("error settings expiration time on appending redis session: %v", err)
}
}
return nil
}
// LoadList reads a list of strings from Redis at the given key and returns.
func (store *SessionStore) LoadList(ctx context.Context, key string) ([]string, error) {
values, err := store.Client.LRange(ctx, key)
if err != nil {
return nil, fmt.Errorf("error loading redis list: %v", err)
}
return values, nil
}
// Load reads sessions.SessionState information from a persistence
// cookie within the HTTP request object
func (store *SessionStore) Load(ctx context.Context, key string) ([]byte, error) {